From 68091147df4aaddc9448d0f32b41f303af642b30 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 25 Mar 2013 22:49:16 -0300 Subject: [PATCH 001/357] Getting tests back to work and migrating to 2.10 futures and promises --- .gitignore | 3 +- LICENCE.txt | 2 +- Procfile | 2 +- pom.xml | 127 +++++++++++ project/Build.scala | 60 ------ project/plugins.sbt | 5 - .../mauricio/postgresql/Connection.scala | 8 +- .../postgresql/ConnectionObjectFactory.scala | 5 +- .../DatabaseConnectionHandler.scala | 200 ++++++++++-------- .../{Query.scala => MutableQuery.scala} | 37 ++-- .../postgresql/NotConnectedException.scala | 8 + .../mauricio/postgresql/QueryResult.scala | 2 +- .../mauricio/postgresql/ResultSet.scala | 14 ++ .../column/ColumnEncoderDecoder.scala | 1 - .../mauricio/postgresql/util/Future.scala | 19 -- .../postgresql/util/FutureResult.scala | 40 ---- .../github/mauricio/postgresql/util/Log.scala | 6 +- .../postgresql/util/SimpleFuture.scala | 93 -------- .../DatabaseConnectionHandlerSpec.scala | 126 +++++------ .../postgresql/util/FutureResultSpec.scala | 50 ----- 20 files changed, 346 insertions(+), 462 deletions(-) create mode 100644 pom.xml delete mode 100644 project/Build.scala delete mode 100644 project/plugins.sbt rename src/main/scala/com/github/mauricio/postgresql/{Query.scala => MutableQuery.scala} (54%) create mode 100644 src/main/scala/com/github/mauricio/postgresql/NotConnectedException.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/ResultSet.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/util/Future.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/util/FutureResult.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/util/SimpleFuture.scala delete mode 100644 src/test/scala/com/github/mauricio/postgresql/util/FutureResultSpec.scala diff --git a/.gitignore b/.gitignore index e8c9575d..2157db0f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ bin/* .project .settings/* project/target/* -project/project/* \ No newline at end of file +project/project/* +.rvmrc diff --git a/LICENCE.txt b/LICENCE.txt index 24c32684..ebca2379 100644 --- a/LICENCE.txt +++ b/LICENCE.txt @@ -1,4 +1,4 @@ -Copyright (C) 2012 Maurício Linhares - mauricio (dot) linhares (at) gmail +Copyright (C) 2013 Maurício Linhares - mauricio (dot) linhares (at) gmail Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/Procfile b/Procfile index 4f4624de..9c41932a 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -postgresql: /usr/local/Cellar/postgresql/9.1.2/bin/postgres -D /Users/mauricio/databases/postgresql +postgresql: /usr/local/Cellar/postgresql/9.1.5/bin/postgres -D /Users/mauricio/databases/postgresql diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..4a3fb89b --- /dev/null +++ b/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + com.github.mauricio + postgresql-netty + 0.1.0-SNAPSHOT + + + 2.10.1 + + + + + scala-tools.org + Scala-tools Maven2 Repository + https://oss.sonatype.org/content/groups/scala-tools/ + + + + + + + org.scala-tools + maven-scala-plugin + + + process-resources + + add-source + compile + + + + scala-test-compile + process-test-resources + + testCompile + + + + + ${scala.version} + true + + -target:jvm-1.7 + -g:vars + -deprecation + -dependencyfile + ${project.build.directory}/.scala_dependencies + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + + + + + + + + commons-pool + commons-pool + 1.6 + + + + ch.qos.logback + logback-classic + 1.0.9 + + + + io.netty + netty + 3.6.3.Final + + + + joda-time + joda-time + 2.2 + + + + org.joda + joda-convert + 1.3.1 + + + + org.scala-lang + scala-compiler + 2.10.1 + + + + org.scala-lang + scala-library + 2.10.1 + + + + org.specs2 + specs2_2.10 + 1.14 + test + + + + com.typesafe.akka + akka-actor_2.10 + 2.1.2 + + + + + \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala deleted file mode 100644 index ec6521da..00000000 --- a/project/Build.scala +++ /dev/null @@ -1,60 +0,0 @@ -/** - * User: Maurício Linhares - * Date: 2/18/12 - * Time: 6:34 PM - */ - -import sbt._ -import Keys._ - -object Build extends sbt.Build { - - lazy val project = Project( - "postgresql-netty", - file(".") - ).settings( - organization := "org.postgresql", - version := "1.0.0", - scalaVersion := "2.9.1", - scalacOptions := Seq("-deprecation", "-encoding", "utf8"), - resolvers ++= Configuration.resolutionRepos, - libraryDependencies ++= Configuration.dependencies) - -} - - -object Configuration { - - object Repos { - val JavaNet = "java-net-maven" at "http://download.java.net/maven/2" - val SonatypeSnapshots = "sonatype-snapshots" at "http://oss.sonatype.org/content/repositories/snapshots" - val SonatypeReleases = "sonatype-releases" at "http://oss.sonatype.org/content/repositories/releases" - val SpringSource = "spring-source" at "http://repository.springsource.com/ivy/bundles/release" - val SpringExternal = "spring-external" at "http://repository.springsource.com/maven/bundles/external" - } - - val resolutionRepos = Seq[Resolver]( - ScalaToolsReleases, - ScalaToolsSnapshots, - Repos.SonatypeReleases, - Repos.SonatypeSnapshots, - Repos.SpringExternal, - Repos.SpringSource, - Repos.JavaNet - ) - - val dependencies = Seq( - // main dependencies - "io.netty" % "netty" % "3.3.1.Final", - "commons-pool" % "commons-pool" % "1.6", - "joda-time" % "joda-time" % "2.0", - "org.joda" % "joda-convert" % "1.2", - "ch.qos.logback" % "logback-classic" % "1.0.0", - "org.scala-lang" % "scala-compiler" % "2.9.1", - "org.scala-lang" % "scala-library" % "2.9.1", - - // test dependencies - "org.specs2" % "specs2_2.9.1" % "1.9" % "test" - ) - -} \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt deleted file mode 100644 index 38e0e5a5..00000000 --- a/project/plugins.sbt +++ /dev/null @@ -1,5 +0,0 @@ -addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.0.0") - -resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/" - -addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.0.0") \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/Connection.scala b/src/main/scala/com/github/mauricio/postgresql/Connection.scala index 83b1f5c6..5f918bcf 100644 --- a/src/main/scala/com/github/mauricio/postgresql/Connection.scala +++ b/src/main/scala/com/github/mauricio/postgresql/Connection.scala @@ -1,6 +1,6 @@ package com.github.mauricio.postgresql -import util.Future +import concurrent.Future /** @@ -11,9 +11,9 @@ import util.Future trait Connection { - def disconnect + def disconnect : Future[Connection] def isConnected : Boolean - def sendQuery( query : String ) : Future[Throwable,QueryResult] - def sendPreparedStatement( query : String, values : Array[Any] = Array.empty[Any] ) : Future[Throwable, QueryResult] + def sendQuery( query : String ) : Future[QueryResult] + def sendPreparedStatement( query : String, values : Array[Any] = Array.empty[Any] ) : Future[QueryResult] } diff --git a/src/main/scala/com/github/mauricio/postgresql/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/postgresql/ConnectionObjectFactory.scala index d9f14281..0d23c52d 100644 --- a/src/main/scala/com/github/mauricio/postgresql/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/postgresql/ConnectionObjectFactory.scala @@ -1,7 +1,8 @@ package com.github.mauricio.postgresql import org.apache.commons.pool.PoolableObjectFactory -import java.util.concurrent.TimeUnit +import concurrent.duration._ +import concurrent.Await /** * User: Maurício Linhares @@ -17,7 +18,7 @@ class ConnectionObjectFactory( def makeObject(): DatabaseConnectionHandler = { val connection = new DatabaseConnectionHandler(host, port, user, database) - connection.connect.get(5, TimeUnit.SECONDS) + Await.result( connection.connect, 5 seconds ) connection } diff --git a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala index 31cdd05f..d3db9b5a 100644 --- a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala @@ -8,9 +8,10 @@ import java.net.InetSocketAddress import parsers.{ColumnData, ProcessData} import scala.collection.JavaConversions._ import org.jboss.netty.buffer.ChannelBuffer -import util.{Future, SimpleFuture, Log} +import util.{Log} import java.util.concurrent.ConcurrentHashMap import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} +import concurrent.{Future, Promise} object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -19,11 +20,11 @@ object DatabaseConnectionHandler { } class DatabaseConnectionHandler - ( - val host : String, - val port : Int, - val user: String, - val database: String) extends SimpleChannelHandler with Connection { +( + val host: String, + val port: Int, + val user: String, + val database: String) extends SimpleChannelHandler with Connection { import DatabaseConnectionHandler._ @@ -37,65 +38,93 @@ class DatabaseConnectionHandler private var readyForQuery = false private val parameterStatus = new ConcurrentHashMap[String, String]() - private val parsedStatements = new ConcurrentHashMap[String,Array[ColumnData]]() - private var _processData : Option[ProcessData] = None + private val parsedStatements = new ConcurrentHashMap[String, Array[ColumnData]]() + private var _processData: Option[ProcessData] = None private val factory = new NioClientSocketChannelFactory( ExecutorServiceUtils.CachedThreadPool, ExecutorServiceUtils.CachedThreadPool) private val bootstrap = new ClientBootstrap(this.factory) - private var channelFuture : ChannelFuture = null + private val connectionFuture = Promise[Map[String, String]]() - @volatile private var connected = false - @volatile private var connectionFuture : Option[SimpleFuture[Throwable,Map[String, String]]] = None - @volatile private var queryFuture : Option[SimpleFuture[Throwable,QueryResult]] = None - @volatile private var currentQuery : Option[Query] = None - @volatile private var currentPreparedStatement : Option[String] = None + private var connected = false + private var queryFuture: Option[Promise[QueryResult]] = None + private var currentQuery: Option[MutableQuery] = None + private var currentPreparedStatement: Option[String] = None + private var _currentChannel: Option[Channel] = None - def isReadyForQuery : Boolean = this.readyForQuery + def isReadyForQuery: Boolean = this.readyForQuery - def connect : Future[Throwable,Map[String,String]] = { + def connect: Future[Map[String, String]] = { this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { override def getPipeline(): ChannelPipeline = { - Channels.pipeline( MessageDecoder, MessageEncoder, DatabaseConnectionHandler.this ) + Channels.pipeline(MessageDecoder, MessageEncoder, DatabaseConnectionHandler.this) } - }); + }) this.bootstrap.setOption("child.tcpNoDelay", true) this.bootstrap.setOption("child.keepAlive", true) - this.connectionFuture = Some( new SimpleFuture[Throwable, Map[String, String]]() ) + this.bootstrap.connect(new InetSocketAddress(this.host, this.port)).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { - this.channelFuture = this.bootstrap.connect(new InetSocketAddress( this.host, this.port)).awaitUninterruptibly() + if (future.isSuccess) { + connected = true + _currentChannel = Some(future.getChannel) + } else { + connectionFuture.failure(future.getCause) + } + + } + }) - this.connectionFuture.get + this.connectionFuture.future } - override def disconnect : Unit = { + override def disconnect: Future[Connection] = { + + val closingPromise = Promise[Connection]() + + this.currentChannel.write(CloseMessage.Instance).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + + if (!future.isSuccess) { + closingPromise.failure(future.getCause) + } else { + future.getChannel.close().addListener(new ChannelFutureListener { + def operationComplete(internalFuture: ChannelFuture) { + if ( internalFuture.isSuccess ) { + closingPromise.success(DatabaseConnectionHandler.this) + } else { + closingPromise.failure(internalFuture.getCause) + } + } + }) + } - if ( this.connected ) { - this.channelFuture.getChannel.write( CloseMessage.Instance ) - } + } + }) + closingPromise.future } - override def isConnected : Boolean = { + override def isConnected: Boolean = { this.connected } - def parameterStatuses : scala.collection.immutable.Map[String, String] = this.parameterStatus.toMap + def parameterStatuses: scala.collection.immutable.Map[String, String] = this.parameterStatus.toMap - def processData : Option[ProcessData] = { + def processData: Option[ProcessData] = { _processData } override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { this.connected = true - e.getChannel().write( new StartupMessage( this.properties ) ) + e.getChannel().write(new StartupMessage(this.properties)) } override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { @@ -105,10 +134,10 @@ class DatabaseConnectionHandler m.name match { case Message.AuthenticationOk => { - log.info( "Authenticated to the database" ) + log.info("Authenticated to the database") } case Message.BackendKeyData => { - this._processData = Option( m.content.asInstanceOf[ProcessData] ) + this._processData = Option(m.content.asInstanceOf[ProcessData]) } case Message.BindComplete => { log.debug("Finished binding statement") @@ -126,7 +155,7 @@ class DatabaseConnectionHandler this.onError(m) } case Message.Notice => { - log.info( "notice -> {}", m.content.asInstanceOf[List[(Char,String)]].mkString(" ") ) + log.info("notice -> {}", m.content.asInstanceOf[List[(Char, String)]].mkString(" ")) } case Message.ParameterStatus => { this.onParameterStatus(m) @@ -138,44 +167,44 @@ class DatabaseConnectionHandler this.onReadyForQuery } case Message.RowDescription => { - this.onRowDescription( m.content.asInstanceOf[Array[ColumnData]] ) + this.onRowDescription(m.content.asInstanceOf[Array[ColumnData]]) } - case _ => { - throw new IllegalStateException("Handler not implemented for message %s".format( m.name )) + case _ => { + throw new IllegalStateException("Handler not implemented for message %s".format(m.name)) } } } case _ => { - log.error( "Unknown message type {} -> {}", e.getMessage.getClass, e.getMessage ) - throw new IllegalArgumentException( "Unknown message type - %s".format( e.getMessage() ) ) + log.error("Unknown message type {}", e.getMessage) + throw new IllegalArgumentException("Unknown message type - %s".format(e.getMessage())) } } } - override def sendQuery( query : String ) : Future[Throwable,QueryResult] = { + override def sendQuery(query: String): Future[QueryResult] = { this.readyForQuery = false - this.queryFuture = Option(new SimpleFuture[Throwable,QueryResult]()) - this.channelFuture.getChannel.write( new QueryMessage( query ) ) - this.queryFuture.get + this.queryFuture = Option(Promise[QueryResult]()) + this.currentChannel.write(new QueryMessage(query)) + this.queryFuture.get.future } - override def sendPreparedStatement( query : String, values : Array[Any] = Array.empty[Any] ) : Future[Throwable,QueryResult] = { + override def sendPreparedStatement(query: String, values: Array[Any] = Array.empty[Any]): Future[QueryResult] = { this.readyForQuery = false - this.queryFuture = Some(new SimpleFuture[Throwable,QueryResult]()) + this.queryFuture = Some(Promise[QueryResult]()) var paramsCount = 0 - val realQuery = if ( query.contains("?") ) { - query.foldLeft( new StringBuilder() ) { + val realQuery = if (query.contains("?")) { + query.foldLeft(new StringBuilder()) { (builder, char) => - if ( char == '?' ) { + if (char == '?') { paramsCount += 1 builder.append("$" + paramsCount) } else { - builder.append( char ) + builder.append(char) } builder }.toString() @@ -185,105 +214,106 @@ class DatabaseConnectionHandler this.currentPreparedStatement = Some(realQuery) - if ( !this.isParsed(realQuery) ) { - log.debug("Query is not parsed yet -> {}",realQuery) - this.channelFuture.getChannel.write( new PreparedStatementOpeningMessage( realQuery, values ) ) + if (!this.isParsed(realQuery)) { + log.debug("MutableQuery is not parsed yet -> {}", realQuery) + this.currentChannel.write(new PreparedStatementOpeningMessage(realQuery, values)) } else { - this.currentQuery = Some( new Query(this.parsedStatements.get(realQuery)) ) - this.channelFuture.getChannel.write( new PreparedStatementExecuteMessage( realQuery, values ) ) + this.currentQuery = Some(new MutableQuery(this.parsedStatements.get(realQuery))) + this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values)) } - this.queryFuture.get + this.queryFuture.get.future } override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { this.setErrorOnFutures(e.getCause) } - private def setErrorOnFutures( e : Throwable ) { + private def setErrorOnFutures(e: Throwable) { - log.error( "Error on connection", e ) + log.error("Error on connection", e) - if ( this.connectionFuture.isDefined ) { - this.connectionFuture.get.setError(e) - this.connectionFuture = None - } - - if ( this.queryFuture.isDefined ) { - log.error( "Setting error on future {}", this.queryFuture.get ) - this.queryFuture.get.setError( e.getCause ) + if (this.queryFuture.isDefined) { + log.error("Setting error on future {}", this.queryFuture.get) + this.queryFuture.get.failure(e) this.queryFuture = None this.currentPreparedStatement = None } } - override def channelDisconnected( ctx : ChannelHandlerContext, e : ChannelStateEvent ) : Unit = { - log.warn( "Connection disconnected - {} - {}", e.getState, e.getValue ) + override def channelDisconnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { + log.warn("Connection disconnected - {}", ctx.getChannel.getRemoteAddress) this.connected = false } private def onReadyForQuery { - this.readyForQuery = true - if ( this.connectionFuture.isDefined ) { - this.connectionFuture.get.set( this.parameterStatus.toMap ) - this.connectionFuture = None + if (!this.connectionFuture.isCompleted) { + this.connectionFuture.success(this.parameterStatus.toMap) } } - private def onError( m : Message ) { + private def onError(m: Message) { log.error("Error with message -> {}", m.content) - val error = new IllegalStateException( m.content.toString ) + val error = new IllegalStateException(m.content.toString) error.fillInStackTrace() this.setErrorOnFutures(error) } - private def onCommandComplete(m : Message) { + private def onCommandComplete(m: Message) { - if ( this.queryFuture.isDefined ) { + if (this.queryFuture.isDefined) { - val result = m.content.asInstanceOf[(Int,String)] + val result = m.content.asInstanceOf[(Int, String)] - val queryResult = if ( this.currentQuery.isDefined ) { - new QueryResult( result._1, result._2, Some(this.currentQuery.get) ) + val queryResult = if (this.currentQuery.isDefined) { + new QueryResult(result._1, result._2, Some(this.currentQuery.get)) } else { - new QueryResult( result._1, result._2, None ) + new QueryResult(result._1, result._2, None) } - this.queryFuture.get.set(queryResult) + this.queryFuture.get.success(queryResult) this.queryFuture = None this.currentPreparedStatement = None } } - private def onParameterStatus(m : Message) { + private def onParameterStatus(m: Message) { val pair = m.content.asInstanceOf[(String, String)] - this.parameterStatus.put( pair._1, pair._2 ) + this.parameterStatus.put(pair._1, pair._2) } - private def onDataRow(m : Message) { + private def onDataRow(m: Message) { this.currentQuery.get.addRawRow(m.content.asInstanceOf[Array[ChannelBuffer]]) } - private def onRowDescription( values : Array[ColumnData] ) { + private def onRowDescription(values: Array[ColumnData]) { log.debug("received query description") - this.currentQuery = Option( new Query( values ) ) + this.currentQuery = Option(new MutableQuery(values)) log.debug("Current prepared statement is {}", this.currentPreparedStatement) - if ( this.currentPreparedStatement.isDefined ) { + if (this.currentPreparedStatement.isDefined) { this.parsedStatements.put(this.currentPreparedStatement.get, values) log.debug("parsed statements are -> {}", this.parsedStatements) } } - private def isParsed( query : String ) : Boolean = { + private def isParsed(query: String): Boolean = { this.parsedStatements.containsKey(query) } + private def currentChannel: Channel = { + if (this._currentChannel.isDefined) { + return this._currentChannel.get + } else { + throw new NotConnectedException("This object is not connected") + } + } + } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/Query.scala b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala similarity index 54% rename from src/main/scala/com/github/mauricio/postgresql/Query.scala rename to src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala index 95b10029..49899cc4 100644 --- a/src/main/scala/com/github/mauricio/postgresql/Query.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala @@ -3,7 +3,7 @@ package com.github.mauricio.postgresql import parsers.ColumnData import org.jboss.netty.buffer.ChannelBuffer import util.Log -import java.util.ArrayList +import collection.mutable.ArrayBuffer /** * User: Maurício Linhares @@ -11,27 +11,25 @@ import java.util.ArrayList * Time: 12:42 AM */ -object Query { - val log = Log.get[Query] +object MutableQuery { + val log = Log.get[MutableQuery] } -class Query ( val columnTypes : Array[ColumnData] ) extends IndexedSeq[Array[Any]] { +class MutableQuery ( val columnTypes : Array[ColumnData] ) extends ResultSet { import CharsetHelper._ - private val rows = new ArrayList[Array[Any]]() + private val rows = new ArrayBuffer[Array[Any]]() private val columnMapping : Map[String, Int] = this.columnTypes.map { columnData => (columnData.name, columnData.columnNumber - 1) }.toMap - def length: Int = this.rows.size() + override def length: Int = this.rows.length - def apply(idx: Int): Array[Any] = this.rows.get(idx) + override def apply(idx: Int): Array[Any] = this.rows(idx) - def update(idx: Int, elem: Array[Any]) { - this.rows.set( idx, elem ) - } + def update(idx: Int, elem: Array[Any]) = this.rows(idx) = elem def addRawRow( row : Array[ChannelBuffer] ) { @@ -48,28 +46,19 @@ class Query ( val columnTypes : Array[ColumnData] ) extends IndexedSeq[Array[Any } - this.rows.add(realRow) - + this.rows += realRow } def getValue( columnNumber : Int, rowNumber : Int ) : Any = { - this.rows.get( rowNumber )(columnNumber) + this.rows( rowNumber )(columnNumber) } def getValue( columnName : String, rowNumber : Int ) : Any = { - this.rows.get( rowNumber )( this.columnMapping( columnName ) ) - } - - def apply( name : String, row : Int ) : Any = { - this.getValue( name, row) + this.rows( rowNumber )( this.columnMapping( columnName ) ) } - def apply( column : Int, row : Int ) : Any = { - this.getValue(column, row) - } + def apply( name : String, row : Int ) : Any = this.getValue( name, row) - def count : Int = { - this.rows.size() - } + def apply( column : Int, row : Int ) : Any = this.getValue(column, row) } diff --git a/src/main/scala/com/github/mauricio/postgresql/NotConnectedException.scala b/src/main/scala/com/github/mauricio/postgresql/NotConnectedException.scala new file mode 100644 index 00000000..d9b01368 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/NotConnectedException.scala @@ -0,0 +1,8 @@ +package com.github.mauricio.postgresql + +/** + * User: mauricio + * Date: 3/24/13 + * Time: 7:14 PM + */ +class NotConnectedException ( message : String ) extends IllegalStateException (message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/QueryResult.scala b/src/main/scala/com/github/mauricio/postgresql/QueryResult.scala index 6c42cdcb..89785ecc 100644 --- a/src/main/scala/com/github/mauricio/postgresql/QueryResult.scala +++ b/src/main/scala/com/github/mauricio/postgresql/QueryResult.scala @@ -6,7 +6,7 @@ package com.github.mauricio.postgresql * Time: 4:01 PM */ -class QueryResult ( val rowsAffected : Int, val statusMessage : String, val rows : Option[Query] ) { +class QueryResult ( val rowsAffected : Int, val statusMessage : String, val rows : Option[ResultSet] ) { override def toString : String = { "QueryResult{rows -> %s,status -> %s}".format( this.rowsAffected, this.statusMessage ) diff --git a/src/main/scala/com/github/mauricio/postgresql/ResultSet.scala b/src/main/scala/com/github/mauricio/postgresql/ResultSet.scala new file mode 100644 index 00000000..e7d03bb7 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/ResultSet.scala @@ -0,0 +1,14 @@ +package com.github.mauricio.postgresql + +/** + * User: mauricio + * Date: 3/25/13 + * Time: 10:24 PM + */ +trait ResultSet extends IndexedSeq[Array[Any]] { + + def apply( name : String, row : Int ) : Any + + def apply( column : Int, row : Int ) : Any + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala index d86318d3..aedf54b2 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala @@ -77,7 +77,6 @@ object ColumnEncoderDecoder { case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder case Date => DateEncoderDecoder case Time => TimeEncoderDecoder - case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder case _ => StringEncoderDecoder } } diff --git a/src/main/scala/com/github/mauricio/postgresql/util/Future.scala b/src/main/scala/com/github/mauricio/postgresql/util/Future.scala deleted file mode 100644 index f902e8df..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/util/Future.scala +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.mauricio.postgresql.util - -import java.util.concurrent.TimeUnit - -/** - * User: Maurício Linhares - * Date: 3/10/12 - * Time: 6:44 PM - */ - -trait Future[L,R] { - - def get : Either[L,R] - def get( time : Long, unit : TimeUnit ) : Either[L,R] - def isDone : Boolean - def isError : Boolean - def onComplete( fn : Either[L,R] => Unit ) - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/util/FutureResult.scala b/src/main/scala/com/github/mauricio/postgresql/util/FutureResult.scala deleted file mode 100644 index 1d3b32f0..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/util/FutureResult.scala +++ /dev/null @@ -1,40 +0,0 @@ -package com.github.mauricio.postgresql.util - -/** - * User: Maurício Linhares - * Date: 3/10/12 - * Time: 10:41 AM - */ - -sealed abstract class FutureResult[+S] { - - def apply() : S = this.get - def isSuccess : Boolean - def isFailure : Boolean - def get : S - def getFailure : Throwable - - def fold[R]( failure : Throwable => R, success : S => R ) = this match { - case FutureSuccess( a ) => success(a) - case FutureFailure( e ) => failure(e) - } - -} - -final case class FutureSuccess[+S]( value : S ) extends FutureResult[S] { - - override def isSuccess : Boolean = true - override def isFailure : Boolean = false - override def get = value - override def getFailure = throw new IllegalStateException("This option is a success, there is no failure here") - -} - -final case class FutureFailure[+S]( value : Throwable ) extends FutureResult[S] { - - override def isSuccess : Boolean = true - override def isFailure : Boolean = false - override def get = throw new IllegalStateException("This option is an error, there is no success here", value) - override def getFailure = value - -} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/util/Log.scala b/src/main/scala/com/github/mauricio/postgresql/util/Log.scala index 3bff5e58..ce13d685 100644 --- a/src/main/scala/com/github/mauricio/postgresql/util/Log.scala +++ b/src/main/scala/com/github/mauricio/postgresql/util/Log.scala @@ -1,6 +1,6 @@ package com.github.mauricio.postgresql.util -import org.slf4j.{Logger, LoggerFactory} +import org.slf4j.LoggerFactory /** @@ -11,8 +11,8 @@ import org.slf4j.{Logger, LoggerFactory} object Log { - def get[T](implicit manifest : Manifest[T] ) = { - LoggerFactory.getLogger( manifest.erasure.getName ) + def get[T](implicit tag : reflect.ClassTag[T]) = { + LoggerFactory.getLogger( tag.runtimeClass.getName ) } def getByName( name : String ) = { diff --git a/src/main/scala/com/github/mauricio/postgresql/util/SimpleFuture.scala b/src/main/scala/com/github/mauricio/postgresql/util/SimpleFuture.scala deleted file mode 100644 index a5f2c127..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/util/SimpleFuture.scala +++ /dev/null @@ -1,93 +0,0 @@ -package com.github.mauricio.postgresql.util - -import java.util.concurrent.{ConcurrentLinkedQueue, TimeoutException, TimeUnit} -import scala.collection.JavaConversions._ - -/** - * User: Maurício Linhares - * Date: 3/10/12 - * Time: 12:29 AM - */ - -object SimpleFuture { - val log = Log.get[SimpleFuture[Throwable,Nothing]] -} - -class SimpleFuture[L >: Throwable,R] extends Future[L,R] { - - import SimpleFuture._ - - @volatile private var done = false - @volatile private var result : R = _ - @volatile private var error : L = _ - @volatile private var callbacks = new ConcurrentLinkedQueue[Either[L,R] => Unit]() - - override def onComplete( fn : Either[L,R] => Unit ) { - this.callbacks.add( fn ) - if (this.isDone) { - fn(this.getResult) - } - } - - override def isDone = this.done - override def isError = this.error != null - - def set( value : R ) { - this.result = value - this.done = true - this.fireCallbacks - } - - def setError( error : L ) { - this.error = error - this.done = true - this.fireCallbacks - - log.error("Received error", error) - } - - override def get : Either[L,R] = { - while ( !this.done ) { - ThreadHelpers.safeSleep(500) - } - this.getResult - } - - override def get( time : Long, unit : TimeUnit ) : Either[L,R] = { - val totalTime = unit.toMillis(time) - val increment = 500 - var sum = 0 - - while (!this.done && sum <= totalTime) { - ThreadHelpers.safeSleep(increment) - sum += increment - } - - log.debug("Done is {}", this.done) - - if (this.done) { - return this.getResult - } else { - throw new TimeoutException("Lock reached the timeout limit") - } - - } - - private def fireCallbacks { - - val either = this.getResult - - for ( callback <- this.callbacks ) { - callback(either) - } - } - - private def getResult : Either[L,R] = { - if ( this.error != null ) { - Left(this.error) - } else { - Right(this.result) - } - } - -} diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index 2a9ce164..4eb2e06e 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -2,7 +2,8 @@ package com.github.mauricio.postgresql import column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} import org.specs2.mutable.Specification -import java.util.concurrent.TimeUnit +import scala.concurrent.duration._ +import concurrent.Await /** * User: Maurício Linhares @@ -55,7 +56,7 @@ class DatabaseConnectionHandlerSpec extends Specification { '22:13:45.888888', TRUE ) - """ + """ val select = "select * from type_test_table" @@ -70,12 +71,12 @@ class DatabaseConnectionHandlerSpec extends Specification { val preparedStatementInsert3 = " insert into prepared_statement_test (name) values ('Peter Parker')" val preparedStatementSelect = "select * from prepared_statement_test" - def withHandler[T]( fn : (DatabaseConnectionHandler) => T ) : T = { + def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { - val handler = new DatabaseConnectionHandler( "localhost", 5433, "postgres", "netty_driver_test" ) + val handler = new DatabaseConnectionHandler("localhost", 5433, "postgres", "netty_driver_test") try { - handler.connect.get + Await.result(handler.connect, Duration(5, SECONDS)) fn(handler) } finally { handler.disconnect @@ -99,12 +100,8 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - val result = handler.sendQuery( this.create ).get( 5, TimeUnit.SECONDS ) - - result match { - case Right( result ) => result.rowsAffected === 0 - } - + val result = Await.result(handler.sendQuery(this.create), Duration(5, SECONDS)) + result.rowsAffected === 0 } } @@ -113,12 +110,12 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - handler.sendQuery( this.create ).get( 5, TimeUnit.SECONDS ) - val result = handler.sendQuery( this.insert ).get( 5, TimeUnit.SECONDS ) - result match { - case Right( result ) => result.rowsAffected === 1 - } + Await.result(handler.sendQuery(this.create), Duration(5, SECONDS)) + val result = Await.result(handler.sendQuery(this.insert), Duration(5, SECONDS)) + + result.rowsAffected === 1 + } } @@ -127,35 +124,28 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - handler.sendQuery( this.create ).get( 5, TimeUnit.SECONDS) - handler.sendQuery( this.insert ).get( 5, TimeUnit.SECONDS ) - val result = handler.sendQuery( this.select ).get( 5, TimeUnit.SECONDS ) - - result match { - case Right( queryResult ) => { - - val rows = queryResult.rows.get + Await.result(handler.sendQuery(this.create), Duration(5, SECONDS)) + Await.result(handler.sendQuery(this.insert), Duration(5, SECONDS)) + val result = Await.result(handler.sendQuery(this.select), Duration(5, SECONDS)) - List( - rows(0,0) === 1, - rows(1,0) === 10, - rows(2,0) === 11, - rows(3,0) === 14.999, - rows(4,0).toString === 78.34.toString, - rows(5,0) === 15.68, - rows(6,0) === 1, - rows(7,0) === "this is a varchar field", - rows(8,0) === "this is a long text field", - rows(9,0) === TimestampEncoderDecoder.decode("1984-08-06 22:13:45.888888"), - rows(10,0) === DateEncoderDecoder.decode("1984-08-06"), - rows(11,0) === TimeEncoderDecoder.decode("22:13:45.888888"), - rows(12,0) === true - ) - - } + val rows = result.rows.get + List( + rows(0, 0) === 1, + rows(1, 0) === 10, + rows(2, 0) === 11, + rows(3, 0) === 14.999, + rows(4, 0).toString === 78.34.toString, + rows(5, 0) === 15.68, + rows(6, 0) === 1, + rows(7, 0) === "this is a varchar field", + rows(8, 0) === "this is a long text field", + rows(9, 0) === TimestampEncoderDecoder.decode("1984-08-06 22:13:45.888888"), + rows(10, 0) === DateEncoderDecoder.decode("1984-08-06"), + rows(11, 0) === TimeEncoderDecoder.decode("22:13:45.888888"), + rows(12, 0) === true + ) - } } @@ -165,20 +155,16 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - handler.sendQuery(this.preparedStatementCreate).get(5, TimeUnit.SECONDS) - handler.sendQuery(this.preparedStatementInsert).get(5, TimeUnit.SECONDS) - val result = handler.sendPreparedStatement( this.preparedStatementSelect ).get(5, TimeUnit.SECONDS) + Await.result(handler.sendQuery(this.preparedStatementCreate), Duration(5, SECONDS)) + Await.result(handler.sendQuery(this.preparedStatementInsert), Duration(5, SECONDS)) + val result = Await.result(handler.sendPreparedStatement(this.preparedStatementSelect), Duration(5, SECONDS)) + val rows = result.rows.get - result match { - case Right( queryResult ) => { - val rows = queryResult.rows.get - List( - rows(0,0) === 1, - rows(1,0) === "John Doe" - ) - } - } + List( + rows(0, 0) === 1, + rows(1, 0) === "John Doe" + ) } @@ -188,30 +174,26 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - handler.sendQuery(this.preparedStatementCreate).get(5, TimeUnit.SECONDS) - handler.sendQuery(this.preparedStatementInsert).get(5, TimeUnit.SECONDS) - handler.sendQuery(this.preparedStatementInsert2).get(5, TimeUnit.SECONDS) - handler.sendQuery(this.preparedStatementInsert3).get(5, TimeUnit.SECONDS) + Await.result(handler.sendQuery(this.preparedStatementCreate), Duration(5, SECONDS)) + Await.result(handler.sendQuery(this.preparedStatementInsert), Duration(5, SECONDS)) + Await.result(handler.sendQuery(this.preparedStatementInsert2), Duration(5, SECONDS)) + Await.result(handler.sendQuery(this.preparedStatementInsert3), Duration(5, SECONDS)) val select = "select * from prepared_statement_test where name like ?" - val queryResult = handler.sendPreparedStatement( select, Array("Peter Parker") ).get(5, TimeUnit.SECONDS) - val rows = queryResult match { - case Right( result ) => { result.rows.get } - } + val queryResult = Await.result(handler.sendPreparedStatement(select, Array("Peter Parker")), Duration(5, SECONDS)) + val rows = queryResult.rows.get - val queryResult2 = handler.sendPreparedStatement( select, Array("Mary Jane") ).get(5, TimeUnit.SECONDS) - val rows2 = queryResult2 match { - case Right( result ) => { result.rows.get } - } + val queryResult2 = Await.result(handler.sendPreparedStatement(select, Array("Mary Jane")), Duration(5, SECONDS)) + val rows2 = queryResult2.rows.get List( - rows(0,0) === 3, - rows(1,0) === "Peter Parker", - rows.count === 1, - rows2.count === 1, - rows2(0,0) === 2, - rows2(1,0) === "Mary Jane" + rows(0, 0) === 3, + rows(1, 0) === "Peter Parker", + rows.length === 1, + rows2.length === 1, + rows2(0, 0) === 2, + rows2(1, 0) === "Mary Jane" ) } diff --git a/src/test/scala/com/github/mauricio/postgresql/util/FutureResultSpec.scala b/src/test/scala/com/github/mauricio/postgresql/util/FutureResultSpec.scala deleted file mode 100644 index f05d0c3a..00000000 --- a/src/test/scala/com/github/mauricio/postgresql/util/FutureResultSpec.scala +++ /dev/null @@ -1,50 +0,0 @@ -package com.github.mauricio.postgresql.util - -import org.specs2.mutable.Specification - -/** - * User: Maurício Linhares - * Date: 3/10/12 - * Time: 1:32 PM - */ - -class FutureResultSpec extends Specification { - - "future" should { - - "return the value when it is a success" in { - - val success = FutureSuccess("ME!") - success() === "ME!" - } - - "raise an exception when trying to get an error a success" in { - - try { - val result = FutureSuccess("ME!") - result.getFailure - } catch { - case e : IllegalStateException => success - } - } - - "grab an exception from the failure" in { - val exception = new RuntimeException() - val failure = FutureFailure(exception) - failure.getFailure === exception - } - - "raise an exception when trying to grab a value from a failure" in { - val exception = new RuntimeException() - val failure = FutureFailure(exception) - - try { - failure() - } catch { - case e : IllegalStateException => success - } - } - - } - -} From 6bf2d983cd03adb5839fdcefe6d2fd50d2ed7dbf Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 25 Mar 2013 23:13:47 -0300 Subject: [PATCH 002/357] Cleaning up tests --- .../DatabaseConnectionHandlerSpec.scala | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index 4eb2e06e..5aa2d6e8 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -84,6 +84,20 @@ class DatabaseConnectionHandlerSpec extends Specification { } + def executeDdl( handler : DatabaseConnectionHandler, data : String, count : Int = 0 ) = { + Await.result(handler.sendQuery(data), Duration(5, SECONDS)).rowsAffected === count + } + + def executeQuery( handler : DatabaseConnectionHandler, data : String ) = { + Await.result(handler.sendQuery(data), Duration(5, SECONDS)) + } + + def executePreparedStatement( + handler : DatabaseConnectionHandler, + statement : String, + values : Array[Any] = Array.empty[Any] ) = { + Await.result( handler.sendPreparedStatement(statement, values), Duration(5, SECONDS) ) + } "handler" should { @@ -100,8 +114,7 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - val result = Await.result(handler.sendQuery(this.create), Duration(5, SECONDS)) - result.rowsAffected === 0 + executeDdl(handler, this.create) } } @@ -110,11 +123,8 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - - Await.result(handler.sendQuery(this.create), Duration(5, SECONDS)) - val result = Await.result(handler.sendQuery(this.insert), Duration(5, SECONDS)) - - result.rowsAffected === 1 + executeDdl(handler, this.create) + executeDdl(handler, this.insert, 1) } @@ -124,9 +134,9 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - Await.result(handler.sendQuery(this.create), Duration(5, SECONDS)) - Await.result(handler.sendQuery(this.insert), Duration(5, SECONDS)) - val result = Await.result(handler.sendQuery(this.select), Duration(5, SECONDS)) + executeDdl(handler, this.create) + executeDdl(handler, this.insert, 1) + val result = executeQuery(handler, this.select) val rows = result.rows.get @@ -155,11 +165,11 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - Await.result(handler.sendQuery(this.preparedStatementCreate), Duration(5, SECONDS)) - Await.result(handler.sendQuery(this.preparedStatementInsert), Duration(5, SECONDS)) - val result = Await.result(handler.sendPreparedStatement(this.preparedStatementSelect), Duration(5, SECONDS)) - val rows = result.rows.get + executeDdl(handler, this.preparedStatementCreate) + executeDdl(handler, this.preparedStatementInsert) + val result = executePreparedStatement(handler, this.preparedStatementSelect) + val rows = result.rows.get List( rows(0, 0) === 1, @@ -174,17 +184,17 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => - Await.result(handler.sendQuery(this.preparedStatementCreate), Duration(5, SECONDS)) - Await.result(handler.sendQuery(this.preparedStatementInsert), Duration(5, SECONDS)) - Await.result(handler.sendQuery(this.preparedStatementInsert2), Duration(5, SECONDS)) - Await.result(handler.sendQuery(this.preparedStatementInsert3), Duration(5, SECONDS)) + executeDdl(handler, this.preparedStatementCreate) + executeDdl(handler, this.preparedStatementInsert, 1) + executeDdl(handler, this.preparedStatementInsert2, 1) + executeDdl(handler, this.preparedStatementInsert3, 1) val select = "select * from prepared_statement_test where name like ?" - val queryResult = Await.result(handler.sendPreparedStatement(select, Array("Peter Parker")), Duration(5, SECONDS)) + val queryResult = executePreparedStatement(handler, select, Array("Peter Parker") ) val rows = queryResult.rows.get - val queryResult2 = Await.result(handler.sendPreparedStatement(select, Array("Mary Jane")), Duration(5, SECONDS)) + val queryResult2 = executePreparedStatement(handler, select, Array("Mary Jane") ) val rows2 = queryResult2.rows.get List( From d0357f8b562eea7267da339e836628d7e36fd4a2 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 25 Mar 2013 23:31:21 -0300 Subject: [PATCH 003/357] More tests fixed --- pom.xml | 30 +++++++++++++++++-- .../DatabaseConnectionHandlerSpec.scala | 4 +-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 4a3fb89b..47511f07 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ 2.10.1 + utf-8 @@ -25,6 +26,7 @@ org.scala-tools maven-scala-plugin + 2.14.2 process-resources @@ -45,7 +47,7 @@ ${scala.version} true - -target:jvm-1.7 + -target:jvm-1.6 -g:vars -deprecation -dependencyfile @@ -58,10 +60,32 @@ maven-compiler-plugin 2.3.2 - 1.7 - 1.7 + 1.6 + 1.6 + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + ${project.encoding} + + + + com.mmakowski + maven-specs2-plugin + 0.4.1 + + + verify + verify + + run-specs + + + + diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index 5aa2d6e8..dd98ef84 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -144,7 +144,7 @@ class DatabaseConnectionHandlerSpec extends Specification { rows(0, 0) === 1, rows(1, 0) === 10, rows(2, 0) === 11, - rows(3, 0) === 14.999, + rows(3, 0).toString === "14.9990", rows(4, 0).toString === 78.34.toString, rows(5, 0) === 15.68, rows(6, 0) === 1, @@ -166,7 +166,7 @@ class DatabaseConnectionHandlerSpec extends Specification { withHandler { handler => executeDdl(handler, this.preparedStatementCreate) - executeDdl(handler, this.preparedStatementInsert) + executeDdl(handler, this.preparedStatementInsert, 1) val result = executePreparedStatement(handler, this.preparedStatementSelect) val rows = result.rows.get From 82007537f84bf7de7c22a271f0b1087b9e747cae Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 25 Mar 2013 23:50:45 -0300 Subject: [PATCH 004/357] checking junit tests --- pom.xml | 293 +++++++++--------- .../DatabaseConnectionHandlerSpec.scala | 3 + 2 files changed, 148 insertions(+), 148 deletions(-) diff --git a/pom.xml b/pom.xml index 47511f07..009435ec 100644 --- a/pom.xml +++ b/pom.xml @@ -1,151 +1,148 @@ - - 4.0.0 - - com.github.mauricio - postgresql-netty - 0.1.0-SNAPSHOT - - - 2.10.1 - utf-8 - - - - - scala-tools.org - Scala-tools Maven2 Repository - https://oss.sonatype.org/content/groups/scala-tools/ - - - - - - - org.scala-tools - maven-scala-plugin - 2.14.2 - - - process-resources - - add-source - compile - - - - scala-test-compile - process-test-resources - - testCompile - - - - - ${scala.version} - true - - -target:jvm-1.6 - -g:vars - -deprecation - -dependencyfile - ${project.build.directory}/.scala_dependencies - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.6 - - ${project.encoding} - - - - com.mmakowski - maven-specs2-plugin - 0.4.1 - - - verify - verify - - run-specs - - - - - - - - - - - commons-pool - commons-pool - 1.6 - - - - ch.qos.logback - logback-classic - 1.0.9 - - - - io.netty - netty - 3.6.3.Final - - - - joda-time - joda-time - 2.2 - - - - org.joda - joda-convert - 1.3.1 - - - - org.scala-lang - scala-compiler - 2.10.1 - - - - org.scala-lang - scala-library - 2.10.1 - - - - org.specs2 - specs2_2.10 - 1.14 - test - - - - com.typesafe.akka - akka-actor_2.10 - 2.1.2 - - - + + 4.0.0 + + com.github.mauricio + postgresql-netty + 0.1.0-SNAPSHOT + + + 2.10.1 + utf-8 + + + + + scala-tools.org + Scala-tools Maven2 Repository + https://oss.sonatype.org/content/groups/scala-tools/ + + + + + + + org.scala-tools + maven-scala-plugin + 2.14.2 + + + + compile + testCompile + + + + + ${scala.version} + true + + -target:jvm-1.6 + -g:vars + -deprecation + -dependencyfile + ${project.build.directory}/.scala_dependencies + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + ${project.encoding} + + + + com.mmakowski + maven-specs2-plugin + 0.4.1 + + + verify + verify + + run-specs + + + + + + + + + + + commons-pool + commons-pool + 1.6 + + + + ch.qos.logback + logback-classic + 1.0.9 + + + + io.netty + netty + 3.6.3.Final + + + + joda-time + joda-time + 2.2 + + + + org.joda + joda-convert + 1.3.1 + + + + org.scala-lang + scala-compiler + 2.10.1 + + + + org.scala-lang + scala-library + 2.10.1 + + + + org.specs2 + specs2_2.10 + 1.14 + test + + + + com.typesafe.akka + akka-actor_2.10 + 2.1.2 + + + + junit + junit + 4.11 + test + + \ No newline at end of file diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index dd98ef84..21e7692a 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -4,6 +4,9 @@ import column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} import org.specs2.mutable.Specification import scala.concurrent.duration._ import concurrent.Await +import org.specs2.runner.JUnitRunner +import org.junit.runner.RunWith +import org.specs2.mutable.SpecificationWithJUnit /** * User: Maurício Linhares From 9ccaf2cf0742a7ec01c5f05b68d085b9bfb0d43a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Thu, 28 Mar 2013 17:27:35 -0300 Subject: [PATCH 005/357] Update README.markdown Prepared statements are supported. --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 8f72305d..8dbf1c09 100644 --- a/README.markdown +++ b/README.markdown @@ -14,10 +14,10 @@ to PostgreSQL. - execute direct queries (without portals/prepared statements) - parses all basic PostgreSQL types, other types are parsed as string - date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects +- portals/prepared statements ## What is missing? -- portals/prepared statements - stored procedures - authentication mechanisms - benchmarks and more testing @@ -38,4 +38,4 @@ to PostgreSQL. - check the **What is missing** piece - send a pull request with specs -This project is freely available under the MIT licence, use it at your own risk. \ No newline at end of file +This project is freely available under the MIT licence, use it at your own risk. From 19c817b4889fbca463b2002009c4e984e90ebdca Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 31 Mar 2013 02:04:12 -0300 Subject: [PATCH 006/357] Correctly implemented notice and error parser, started authentication implementation, refactored and moved classes --- .../mauricio/postgresql/Configuration.scala | 23 +++ .../DatabaseConnectionHandler.scala | 136 +++++++++++------- .../mauricio/postgresql/MessageEncoder.scala | 3 + .../mauricio/postgresql/MutableQuery.scala | 2 +- .../mauricio/postgresql/RowDescription.scala | 11 -- .../encoders/CloseMessageEncoder.scala | 2 +- .../postgresql/encoders/Encoder.scala | 2 +- .../ExecutePreparedStatementEncoder.scala | 5 +- .../PreparedStatementOpeningEncoder.scala | 8 +- .../encoders/QueryMessageEncoder.scala | 5 +- .../encoders/StartupMessageEncoder.scala | 20 ++- .../exceptions/DatabaseException.scala | 11 ++ .../EncoderNotAvailableException.scala | 4 +- ...issingCredentialInformationException.scala | 20 +++ .../NotConnectedException.scala | 2 +- .../ParserNotAvailableException.scala | 2 +- ...pportedAuthenticationMethodException.scala | 9 ++ .../postgresql/messages/StartupMessage.scala | 12 -- ...henticationChallengeCleartextMessage.scala | 14 ++ .../backend/AuthenticationChallengeMD5.scala | 14 ++ .../AuthenticationChallengeMessage.scala | 9 ++ .../backend/AuthenticationMessage.scala | 8 ++ .../backend/AuthenticationOkMessage.scala | 13 ++ .../backend/AuthenticationResponseType.scala | 11 ++ .../messages/backend/BindComplete.scala | 15 ++ .../messages/backend/CloseComplete.scala | 14 ++ .../backend}/ColumnData.scala | 2 +- .../backend/CommandCompleteMessage.scala | 9 ++ .../messages/backend/DataRowMessage.scala | 10 ++ .../messages/backend/ErrorMessage.scala | 9 ++ .../messages/backend/InformationMessage.scala | 34 +++++ .../{ => messages/backend}/Message.scala | 23 +-- .../messages/backend/NoticeMessage.scala | 9 ++ .../backend/ParameterStatusMessage.scala | 9 ++ .../messages/backend/ParseComplete.scala | 14 ++ .../messages/backend/ProcessData.scala | 10 ++ .../backend/ReadyForQueryMessage.scala | 8 ++ .../backend/RowDescriptionMessage.scala | 9 ++ .../{ => frontend}/CloseMessage.scala | 4 +- .../messages/frontend/CredentialMessage.scala | 13 ++ .../frontend}/FrontendMessage.scala | 2 +- .../PreparedStatementExecuteMessage.scala | 4 +- .../PreparedStatementMessage.scala | 3 +- .../PreparedStatementOpeningMessage.scala | 4 +- .../{ => frontend}/QueryMessage.scala | 4 +- .../messages/frontend/StartupMessage.scala | 12 ++ .../parsers/AuthenticationStartupParser.scala | 24 ++-- .../parsers/BackendKeyDataParser.scala | 4 +- .../postgresql/parsers/BindComplete.scala | 16 --- .../postgresql/parsers/CloseComplete.scala | 17 --- .../parsers/CommandCompleteParser.scala | 11 +- .../postgresql/parsers/DataRowParser.scala | 4 +- .../postgresql/parsers/ErrorParser.scala | 10 +- .../parsers/InformationParser.scala | 35 +++++ .../postgresql/parsers/MessageParser.scala | 5 +- .../postgresql/parsers/NoticeParser.scala | 22 +-- .../parsers/ParameterStatusParser.scala | 5 +- .../postgresql/parsers/ParseComplete.scala | 15 -- .../postgresql/parsers/ProcessData.scala | 15 -- .../parsers/ReadyForQueryParser.scala | 4 +- .../parsers/ReturningMessageParser.scala | 2 +- .../parsers/RowDescriptionParser.scala | 5 +- .../{ => pool}/ConnectionObjectFactory.scala | 11 +- .../{ => pool}/ConnectionPool.scala | 11 +- .../DatabaseConnectionHandlerSpec.scala | 14 +- .../postgresql/MessageDecoderSpec.scala | 8 +- .../postgresql/parsers/ParserESpec.scala | 7 +- .../postgresql/parsers/ParserKSpec.scala | 7 +- .../postgresql/parsers/ParserSSpec.scala | 11 +- 69 files changed, 553 insertions(+), 277 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/postgresql/Configuration.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/RowDescription.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala rename src/main/scala/com/github/mauricio/postgresql/{ => exceptions}/EncoderNotAvailableException.scala (65%) create mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala rename src/main/scala/com/github/mauricio/postgresql/{ => exceptions}/NotConnectedException.scala (74%) rename src/main/scala/com/github/mauricio/postgresql/{parsers => exceptions}/ParserNotAvailableException.scala (80%) create mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/StartupMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala rename src/main/scala/com/github/mauricio/postgresql/{parsers => messages/backend}/ColumnData.scala (87%) create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/CommandCompleteMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala rename src/main/scala/com/github/mauricio/postgresql/{ => messages/backend}/Message.scala (57%) create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ParameterStatusMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ReadyForQueryMessage.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala rename src/main/scala/com/github/mauricio/postgresql/messages/{ => frontend}/CloseMessage.scala (62%) create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala rename src/main/scala/com/github/mauricio/postgresql/{ => messages/frontend}/FrontendMessage.scala (66%) rename src/main/scala/com/github/mauricio/postgresql/messages/{ => frontend}/PreparedStatementExecuteMessage.scala (64%) rename src/main/scala/com/github/mauricio/postgresql/messages/{ => frontend}/PreparedStatementMessage.scala (81%) rename src/main/scala/com/github/mauricio/postgresql/messages/{ => frontend}/PreparedStatementOpeningMessage.scala (63%) rename src/main/scala/com/github/mauricio/postgresql/messages/{ => frontend}/QueryMessage.scala (56%) create mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/BindComplete.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/CloseComplete.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/ParseComplete.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/ProcessData.scala rename src/main/scala/com/github/mauricio/postgresql/{ => pool}/ConnectionObjectFactory.scala (63%) rename src/main/scala/com/github/mauricio/postgresql/{ => pool}/ConnectionPool.scala (54%) diff --git a/src/main/scala/com/github/mauricio/postgresql/Configuration.scala b/src/main/scala/com/github/mauricio/postgresql/Configuration.scala new file mode 100644 index 00000000..a092675c --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/Configuration.scala @@ -0,0 +1,23 @@ +package com.github.mauricio.postgresql + +import java.util.concurrent.ExecutorService + +/** + * User: mauricio + * Date: 3/29/13 + * Time: 12:05 AM + */ + +object Configuration { + val Default = new Configuration("postgres") +} + + +case class Configuration ( username : String, + host : String = "localhost", + port : Int = 5432, + password : Option[String] = None, + database : Option[String] = None, + bossPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool, + workerPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool + ) diff --git a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala index d3db9b5a..103f4456 100644 --- a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala @@ -1,17 +1,20 @@ package com.github.mauricio.postgresql +import exceptions.{MissingCredentialInformationException, DatabaseException, NotConnectedException} import messages._ +import backend._ +import backend.ProcessData +import frontend._ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.channel._ import java.net.InetSocketAddress -import parsers.{ColumnData, ProcessData} import scala.collection.JavaConversions._ -import org.jboss.netty.buffer.ChannelBuffer -import util.{Log} +import util.Log import java.util.concurrent.ConcurrentHashMap import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import concurrent.{Future, Promise} +import scala.Some object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -21,16 +24,13 @@ object DatabaseConnectionHandler { class DatabaseConnectionHandler ( - val host: String, - val port: Int, - val user: String, - val database: String) extends SimpleChannelHandler with Connection { + val configuration : Configuration = Configuration.Default ) extends SimpleChannelHandler with Connection { import DatabaseConnectionHandler._ private val properties = List( - "user" -> user, - "database" -> database, + "user" -> configuration.username, + "database" -> configuration.database, "application_name" -> DatabaseConnectionHandler.Name, "client_encoding" -> "UTF8", "DateStyle" -> "ISO", @@ -49,7 +49,7 @@ class DatabaseConnectionHandler private val connectionFuture = Promise[Map[String, String]]() private var connected = false - private var queryFuture: Option[Promise[QueryResult]] = None + private var queryPromise: Option[Promise[QueryResult]] = None private var currentQuery: Option[MutableQuery] = None private var currentPreparedStatement: Option[String] = None private var _currentChannel: Option[Channel] = None @@ -69,7 +69,7 @@ class DatabaseConnectionHandler this.bootstrap.setOption("child.tcpNoDelay", true) this.bootstrap.setOption("child.keepAlive", true) - this.bootstrap.connect(new InetSocketAddress(this.host, this.port)).addListener(new ChannelFutureListener { + this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.isSuccess) { @@ -92,7 +92,7 @@ class DatabaseConnectionHandler this.currentChannel.write(CloseMessage.Instance).addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { - if (!future.isSuccess) { + if ( future.getCause != null ) { closingPromise.failure(future.getCause) } else { future.getChannel.close().addListener(new ChannelFutureListener { @@ -130,35 +130,36 @@ class DatabaseConnectionHandler override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { e.getMessage() match { + case m: Message => { m.name match { - case Message.AuthenticationOk => { - log.info("Authenticated to the database") - } case Message.BackendKeyData => { - this._processData = Option(m.content.asInstanceOf[ProcessData]) + this._processData = Some(m.asInstanceOf[ProcessData]) } case Message.BindComplete => { - log.debug("Finished binding statement") + log.debug("Finished binding statement - {}", this.currentPreparedStatement) + } + case Message.AuthenticationResponse => { + this.onAuthenticationResponse(ctx.getChannel, m.asInstanceOf[AuthenticationMessage]) } case Message.CommandComplete => { - this.onCommandComplete(m) + this.onCommandComplete(m.asInstanceOf[CommandCompleteMessage]) } case Message.CloseComplete => { log.debug("Successfully closed portal for [{}]", this.currentPreparedStatement) } case Message.DataRow => { - this.onDataRow(m) + this.onDataRow(m.asInstanceOf[DataRowMessage]) } case Message.Error => { - this.onError(m) + this.onError(m.asInstanceOf[ErrorMessage]) } case Message.Notice => { - log.info("notice -> {}", m.content.asInstanceOf[List[(Char, String)]].mkString(" ")) + log.info("notice -> {}", m.asInstanceOf[NoticeMessage]) } case Message.ParameterStatus => { - this.onParameterStatus(m) + this.onParameterStatus(m.asInstanceOf[ParameterStatusMessage]) } case Message.ParseComplete => { log.debug("Finished parsing statement") @@ -167,7 +168,7 @@ class DatabaseConnectionHandler this.onReadyForQuery } case Message.RowDescription => { - this.onRowDescription(m.content.asInstanceOf[Array[ColumnData]]) + this.onRowDescription(m.asInstanceOf[RowDescriptionMessage]) } case _ => { throw new IllegalStateException("Handler not implemented for message %s".format(m.name)) @@ -186,14 +187,14 @@ class DatabaseConnectionHandler override def sendQuery(query: String): Future[QueryResult] = { this.readyForQuery = false - this.queryFuture = Option(Promise[QueryResult]()) + this.queryPromise = Option(Promise[QueryResult]()) this.currentChannel.write(new QueryMessage(query)) - this.queryFuture.get.future + this.queryPromise.get.future } override def sendPreparedStatement(query: String, values: Array[Any] = Array.empty[Any]): Future[QueryResult] = { this.readyForQuery = false - this.queryFuture = Some(Promise[QueryResult]()) + this.queryPromise = Some(Promise[QueryResult]()) var paramsCount = 0 @@ -222,7 +223,7 @@ class DatabaseConnectionHandler this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values)) } - this.queryFuture.get.future + this.queryPromise.get.future } override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { @@ -233,10 +234,14 @@ class DatabaseConnectionHandler log.error("Error on connection", e) - if (this.queryFuture.isDefined) { - log.error("Setting error on future {}", this.queryFuture.get) - this.queryFuture.get.failure(e) - this.queryFuture = None + if ( !this.connectionFuture.isCompleted ) { + this.connectionFuture.failure(e) + } + + if (this.queryPromise.isDefined) { + log.error("Setting error on future {}", this.queryPromise.get) + this.queryPromise.get.failure(e) + this.queryPromise = None this.currentPreparedStatement = None } @@ -255,52 +260,48 @@ class DatabaseConnectionHandler } } - private def onError(m: Message) { - log.error("Error with message -> {}", m.content) + private def onError(m: ErrorMessage) { + log.error("Error with message -> {}", m) - val error = new IllegalStateException(m.content.toString) + val error = new DatabaseException(m) error.fillInStackTrace() this.setErrorOnFutures(error) } - private def onCommandComplete(m: Message) { + private def onCommandComplete(m: CommandCompleteMessage) { - if (this.queryFuture.isDefined) { - - val result = m.content.asInstanceOf[(Int, String)] + if (this.queryPromise.isDefined) { val queryResult = if (this.currentQuery.isDefined) { - new QueryResult(result._1, result._2, Some(this.currentQuery.get)) + new QueryResult(m.rowsAffected, m.statusMessage, Some(this.currentQuery.get)) } else { - new QueryResult(result._1, result._2, None) + new QueryResult(m.rowsAffected, m.statusMessage, None) } - this.queryFuture.get.success(queryResult) - this.queryFuture = None + this.queryPromise.get.success(queryResult) + this.queryPromise = None this.currentPreparedStatement = None } } - private def onParameterStatus(m: Message) { - val pair = m.content.asInstanceOf[(String, String)] - this.parameterStatus.put(pair._1, pair._2) + private def onParameterStatus(m: ParameterStatusMessage) { + this.parameterStatus.put( m.key, m.value ) } - private def onDataRow(m: Message) { - this.currentQuery.get.addRawRow(m.content.asInstanceOf[Array[ChannelBuffer]]) + private def onDataRow(m: DataRowMessage) { + this.currentQuery.get.addRawRow(m.values) } - private def onRowDescription(values: Array[ColumnData]) { - log.debug("received query description") - this.currentQuery = Option(new MutableQuery(values)) + private def onRowDescription(m: RowDescriptionMessage) { + log.debug("received query description {}", m) + this.currentQuery = Option(new MutableQuery(m.columnDatas)) log.debug("Current prepared statement is {}", this.currentPreparedStatement) if (this.currentPreparedStatement.isDefined) { - this.parsedStatements.put(this.currentPreparedStatement.get, values) - log.debug("parsed statements are -> {}", this.parsedStatements) + this.parsedStatements.put(this.currentPreparedStatement.get, m.columnDatas) } } @@ -308,6 +309,22 @@ class DatabaseConnectionHandler this.parsedStatements.containsKey(query) } + private def onAuthenticationResponse( channel : Channel, message : AuthenticationMessage ) { + + message match { + case m : AuthenticationOkMessage => { + log.debug("Successfully logged in to database") + } + case m : AuthenticationChallengeCleartextMessage => { + channel.write(this.credential(m)) + } + case m : AuthenticationChallengeMD5 => { + channel.write(this.credential(m)) + } + } + + } + private def currentChannel: Channel = { if (this._currentChannel.isDefined) { return this._currentChannel.get @@ -316,4 +333,19 @@ class DatabaseConnectionHandler } } + private def credential( authenticationMessage : AuthenticationChallengeMessage ) : CredentialMessage = { + if ( configuration.username != null && configuration.password.isDefined ) { + new CredentialMessage( + configuration.username, + configuration.password.get, + authenticationMessage.challengeType + ) + } else { + throw new MissingCredentialInformationException( + this.configuration.username, + this.configuration.password, + authenticationMessage.challengeType ) + } + } + } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala index 9c551f71..3e8d0d46 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala @@ -1,6 +1,9 @@ package com.github.mauricio.postgresql import encoders._ +import exceptions.EncoderNotAvailableException +import messages.backend.Message +import messages.frontend.FrontendMessage import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.buffer.ChannelBuffer diff --git a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala index 49899cc4..563cfb69 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala @@ -1,6 +1,6 @@ package com.github.mauricio.postgresql -import parsers.ColumnData +import messages.backend.ColumnData import org.jboss.netty.buffer.ChannelBuffer import util.Log import collection.mutable.ArrayBuffer diff --git a/src/main/scala/com/github/mauricio/postgresql/RowDescription.scala b/src/main/scala/com/github/mauricio/postgresql/RowDescription.scala deleted file mode 100644 index 49743df2..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/RowDescription.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mauricio.postgresql - -/** - * User: Maurício Linhares - * Date: 3/1/12 - * Time: 1:58 AM - */ - -class RowDescription { - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala index e087c8c5..831473bd 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.encoders import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.FrontendMessage +import com.github.mauricio.postgresql.messages.frontend.FrontendMessage /** diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/Encoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/Encoder.scala index 429b325e..3c9c974c 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/Encoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/Encoder.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.encoders import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.FrontendMessage +import com.github.mauricio.postgresql.messages.frontend.FrontendMessage /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 41983f21..c2878e59 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -1,9 +1,10 @@ package com.github.mauricio.postgresql.encoders import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.{ChannelUtils, Message, CharsetHelper, FrontendMessage} +import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} import com.github.mauricio.postgresql.column.ColumnEncoderDecoder -import com.github.mauricio.postgresql.messages.PreparedStatementExecuteMessage +import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} +import com.github.mauricio.postgresql.messages.backend.Message /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 83030077..ff8758f7 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -1,10 +1,10 @@ package com.github.mauricio.postgresql.encoders import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.util.Log -import com.github.mauricio.postgresql.messages.{PreparedStatementOpeningMessage} -import com.github.mauricio.postgresql.{Message, ChannelUtils, FrontendMessage, CharsetHelper} +import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} import com.github.mauricio.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} +import com.github.mauricio.postgresql.messages.backend.Message /** @@ -15,8 +15,6 @@ import com.github.mauricio.postgresql.column.ColumnEncoderDecoder object PreparedStatementOpeningEncoder extends Encoder { - private val log = Log.getByName("PreparedStatementOpeningEncoder") - override def encode(message: FrontendMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementOpeningMessage] diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala index 13c5ba6b..b9da4338 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala @@ -1,8 +1,9 @@ package com.github.mauricio.postgresql.encoders -import com.github.mauricio.postgresql.messages.QueryMessage import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.{FrontendMessage, ChannelUtils, CharsetHelper, Message} +import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} +import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, QueryMessage} +import com.github.mauricio.postgresql.messages.backend.Message /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala index 22b9ae5b..8857d3e6 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala @@ -1,9 +1,8 @@ package com.github.mauricio.postgresql.encoders import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.messages.StartupMessage -import com.github.mauricio.postgresql.util.Log -import com.github.mauricio.postgresql.{FrontendMessage, ChannelUtils} +import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, StartupMessage} /** @@ -14,7 +13,7 @@ import com.github.mauricio.postgresql.{FrontendMessage, ChannelUtils} object StartupMessageEncoder extends Encoder { - private val log = Log.getByName("StartupMessageEncoder") + //private val log = Log.getByName("StartupMessageEncoder") override def encode(message: FrontendMessage): ChannelBuffer = { @@ -27,8 +26,17 @@ object StartupMessageEncoder extends Encoder { startup.parameters.foreach { pair => - ChannelUtils.writeCString( pair._1, buffer ) - ChannelUtils.writeCString( pair._2, buffer ) + pair._2 match { + case value : String => { + ChannelUtils.writeCString( pair._1, buffer ) + ChannelUtils.writeCString( value, buffer ) + } + case Some(value) => { + ChannelUtils.writeCString( pair._1, buffer ) + ChannelUtils.writeCString( value.toString, buffer ) + } + case _ => {} + } } buffer.writeByte(0) diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala new file mode 100644 index 00000000..003c5e6f --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala @@ -0,0 +1,11 @@ +package com.github.mauricio.postgresql.exceptions + +import com.github.mauricio.postgresql.messages.backend.ErrorMessage + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:00 AM + */ +class DatabaseException( val errorMessage : ErrorMessage ) + extends IllegalStateException( errorMessage.message ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/EncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/EncoderNotAvailableException.scala similarity index 65% rename from src/main/scala/com/github/mauricio/postgresql/EncoderNotAvailableException.scala rename to src/main/scala/com/github/mauricio/postgresql/exceptions/EncoderNotAvailableException.scala index 40bed2f3..6d8f13de 100644 --- a/src/main/scala/com/github/mauricio/postgresql/EncoderNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/EncoderNotAvailableException.scala @@ -1,4 +1,6 @@ -package com.github.mauricio.postgresql +package com.github.mauricio.postgresql.exceptions + +import com.github.mauricio.postgresql.messages.frontend.FrontendMessage /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala new file mode 100644 index 00000000..2a4ba2a2 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala @@ -0,0 +1,20 @@ +package com.github.mauricio.postgresql.exceptions + +import com.github.mauricio.postgresql.messages.backend.{AuthenticationResponseType, AuthenticationMessage} + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:39 AM + */ +class MissingCredentialInformationException ( + val username : String, + val password : Option[String], + val authenticationResponseType : AuthenticationResponseType.AuthenticationResponseType ) + extends IllegalStateException ( + "Username and password were required by auth type %s but are not available (username=[%s] password=[%s]".format( + authenticationResponseType, + username, + password + ) + ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/NotConnectedException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/NotConnectedException.scala similarity index 74% rename from src/main/scala/com/github/mauricio/postgresql/NotConnectedException.scala rename to src/main/scala/com/github/mauricio/postgresql/exceptions/NotConnectedException.scala index d9b01368..a6065c07 100644 --- a/src/main/scala/com/github/mauricio/postgresql/NotConnectedException.scala +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/NotConnectedException.scala @@ -1,4 +1,4 @@ -package com.github.mauricio.postgresql +package com.github.mauricio.postgresql.exceptions /** * User: mauricio diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ParserNotAvailableException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/ParserNotAvailableException.scala similarity index 80% rename from src/main/scala/com/github/mauricio/postgresql/parsers/ParserNotAvailableException.scala rename to src/main/scala/com/github/mauricio/postgresql/exceptions/ParserNotAvailableException.scala index 7a8dc6ba..8cf26732 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ParserNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/ParserNotAvailableException.scala @@ -1,4 +1,4 @@ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.postgresql.exceptions /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala new file mode 100644 index 00000000..def32b5f --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.exceptions + +/** + * User: mauricio + * Date: 3/30/13 + * Time: 11:23 PM + */ +class UnsupportedAuthenticationMethodException ( val authenticationType : Int ) + extends IllegalArgumentException ( "Unknown authentication method -> '%s'".format(authenticationType) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/StartupMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/StartupMessage.scala deleted file mode 100644 index 542c009a..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/StartupMessage.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.mauricio.postgresql.messages - -import com.github.mauricio.postgresql.{Message, FrontendMessage} - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 7:34 PM - */ - -class StartupMessage ( val parameters : List[(String, String)] ) extends FrontendMessage(Message.Startup) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala new file mode 100644 index 00000000..02e96edc --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala @@ -0,0 +1,14 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:31 AM + */ + +object AuthenticationChallengeCleartextMessage { + val Instance = new AuthenticationChallengeCleartextMessage() +} + +class AuthenticationChallengeCleartextMessage + extends AuthenticationChallengeMessage( AuthenticationResponseType.Cleartext ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala new file mode 100644 index 00000000..c8963f99 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala @@ -0,0 +1,14 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:32 AM + */ + +object AuthenticationChallengeMD5 { + val Instance = new AuthenticationChallengeMD5() +} + +class AuthenticationChallengeMD5 + extends AuthenticationChallengeMessage( AuthenticationResponseType.MD5 ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala new file mode 100644 index 00000000..e4d27977 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:45 AM + */ +class AuthenticationChallengeMessage ( val challengeType : AuthenticationResponseType.AuthenticationResponseType ) + extends AuthenticationMessage \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala new file mode 100644 index 00000000..5d07bd68 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala @@ -0,0 +1,8 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:30 AM + */ +abstract class AuthenticationMessage extends Message( Message.AuthenticationResponse ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala new file mode 100644 index 00000000..9732ffc8 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala @@ -0,0 +1,13 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:30 AM + */ + +object AuthenticationOkMessage { + val Instance = new AuthenticationOkMessage() +} + +class AuthenticationOkMessage extends AuthenticationMessage diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala new file mode 100644 index 00000000..9a79095a --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala @@ -0,0 +1,11 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 12:22 AM + */ +object AuthenticationResponseType extends Enumeration { + type AuthenticationResponseType = Value + val MD5, Cleartext, Ok = Value +} diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala new file mode 100644 index 00000000..8060d257 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala @@ -0,0 +1,15 @@ +package com.github.mauricio.postgresql.messages.backend + + +/** + * User: Maurício Linhares + * Date: 3/12/12 + * Time: 2:32 AM + */ + +object BindComplete { + val Instance = new BindComplete() +} + + +class BindComplete extends Message( Message.BindComplete ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala new file mode 100644 index 00000000..f1769975 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala @@ -0,0 +1,14 @@ +package com.github.mauricio.postgresql.messages.backend + + +/** + * User: Maurício Linhares + * Date: 3/12/12 + * Time: 11:37 PM + */ + +object CloseComplete { + val Instance = new CloseComplete() +} + +class CloseComplete extends Message(Message.CloseComplete) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ColumnData.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ColumnData.scala similarity index 87% rename from src/main/scala/com/github/mauricio/postgresql/parsers/ColumnData.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/backend/ColumnData.scala index f4706bf2..bb410204 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ColumnData.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ColumnData.scala @@ -1,4 +1,4 @@ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.postgresql.messages.backend import com.github.mauricio.postgresql.column.ColumnEncoderDecoder diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/CommandCompleteMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/CommandCompleteMessage.scala new file mode 100644 index 00000000..ec346394 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/CommandCompleteMessage.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:04 AM + */ +case class CommandCompleteMessage ( val rowsAffected : Int, val statusMessage : String ) + extends Message( Message.CommandComplete ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala new file mode 100644 index 00000000..10e05ebd --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala @@ -0,0 +1,10 @@ +package com.github.mauricio.postgresql.messages.backend + +import org.jboss.netty.buffer.ChannelBuffer + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:10 AM + */ +case class DataRowMessage( val values : Array[ChannelBuffer] ) extends Message( Message.DataRow ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala new file mode 100644 index 00000000..a93bc456 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 12:57 AM + */ +class ErrorMessage ( fields : Map[String,String] ) + extends InformationMessage( Message.Error, fields ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala new file mode 100644 index 00000000..3fafb66e --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala @@ -0,0 +1,34 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 12:42 AM + */ + +object InformationMessage { + + val Fields = Map( + 'S' -> "Severity", + 'C' -> "SQLSTATE", + 'M' -> "Message", + 'D' -> "Detail", + 'H' -> "Hint", + 'P' -> "Position", + 'q' -> "Internal Query", + 'W' -> "Where", + 'F' -> "File", + 'L' -> "Line", + 'R' -> "Routine" + ) + + def fieldName( name : Char ) : String = Fields.getOrElse(name, { name.toString } ) + +} + +abstract case class InformationMessage ( statusCode : Char, val fields : Map[String,String] ) + extends Message( statusCode ) { + + def message : String = this.fields( "Message" ) + +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/Message.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala similarity index 57% rename from src/main/scala/com/github/mauricio/postgresql/Message.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala index b9545351..f33b590f 100644 --- a/src/main/scala/com/github/mauricio/postgresql/Message.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala @@ -1,7 +1,7 @@ -package com.github.mauricio.postgresql +package com.github.mauricio.postgresql.messages.backend object Message { - val AuthenticationOk = 'R' + val AuthenticationResponse = 'R' val BackendKeyData = 'K' val Bind = 'B' val BindComplete = '2' @@ -28,21 +28,4 @@ object Message { val Sync = 'S' } -class Message ( val name : Char, val content : Any ) { - - override def hashCode : Int = { - "%s-%s".format( this.name, this.content ).hashCode() - } - - override def equals( other : Any ) : Boolean = { - - other match { - case o : Message => { - this.name.equals(o.name) && this.content.equals( o.content ) - } - case _ => false - } - - } - -} \ No newline at end of file +class Message ( val name : Char ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala new file mode 100644 index 00000000..15dcf891 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 12:45 AM + */ +class NoticeMessage ( fields : Map[String,String] ) + extends InformationMessage( Message.Notice, fields ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParameterStatusMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParameterStatusMessage.scala new file mode 100644 index 00000000..b31c2bb6 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParameterStatusMessage.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:13 AM + */ +case class ParameterStatusMessage ( val key : String, val value : String ) + extends Message( Message.ParameterStatus ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala new file mode 100644 index 00000000..f4876b83 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala @@ -0,0 +1,14 @@ +package com.github.mauricio.postgresql.messages.backend + + +/** + * User: Maurício Linhares + * Date: 3/12/12 + * Time: 2:29 AM + */ + +object ParseComplete { + val Instance = new ParseComplete() +} + +class ParseComplete extends Message(Message.ParseComplete) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala new file mode 100644 index 00000000..9566d67a --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala @@ -0,0 +1,10 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: Maurício Linhares + * Date: 2/28/12 + * Time: 11:13 PM + */ + +case class ProcessData ( val processId : Int, val secretKey : Int ) + extends Message( Message.BackendKeyData ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ReadyForQueryMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ReadyForQueryMessage.scala new file mode 100644 index 00000000..257a1d48 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ReadyForQueryMessage.scala @@ -0,0 +1,8 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:26 AM + */ +class ReadyForQueryMessage ( transactionStatus : Char ) extends Message ( Message.ReadyForQuery ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala new file mode 100644 index 00000000..ebcd6903 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.messages.backend + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:15 AM + */ +case class RowDescriptionMessage ( val columnDatas : Array[ColumnData] ) + extends Message( Message.RowDescription ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/CloseMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CloseMessage.scala similarity index 62% rename from src/main/scala/com/github/mauricio/postgresql/messages/CloseMessage.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/frontend/CloseMessage.scala index 481cc64c..4140a782 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/CloseMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CloseMessage.scala @@ -1,6 +1,6 @@ -package com.github.mauricio.postgresql.messages +package com.github.mauricio.postgresql.messages.frontend -import com.github.mauricio.postgresql.{FrontendMessage, Message} +import com.github.mauricio.postgresql.messages.backend.Message /** diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala new file mode 100644 index 00000000..6d12c8f7 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala @@ -0,0 +1,13 @@ +package com.github.mauricio.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.AuthenticationResponseType + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 1:43 AM + */ +class CredentialMessage( + val username : String, + val password : String, + val kind : AuthenticationResponseType.AuthenticationResponseType ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/FrontendMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/FrontendMessage.scala similarity index 66% rename from src/main/scala/com/github/mauricio/postgresql/FrontendMessage.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/frontend/FrontendMessage.scala index eab893e8..0baceaf2 100644 --- a/src/main/scala/com/github/mauricio/postgresql/FrontendMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/FrontendMessage.scala @@ -1,4 +1,4 @@ -package com.github.mauricio.postgresql +package com.github.mauricio.postgresql.messages.frontend /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementExecuteMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala similarity index 64% rename from src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementExecuteMessage.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index f380b4b2..4c854490 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementExecuteMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -1,6 +1,6 @@ -package com.github.mauricio.postgresql.messages +package com.github.mauricio.postgresql.messages.frontend -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.Message /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementMessage.scala similarity index 81% rename from src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementMessage.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementMessage.scala index 0df1a7c2..bd6269b4 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -1,7 +1,6 @@ -package com.github.mauricio.postgresql.messages +package com.github.mauricio.postgresql.messages.frontend import com.github.mauricio.postgresql.column.ColumnEncoderDecoder -import com.github.mauricio.postgresql.FrontendMessage /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementOpeningMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala similarity index 63% rename from src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementOpeningMessage.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index c7f9138a..0a4ae568 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/PreparedStatementOpeningMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -1,6 +1,6 @@ -package com.github.mauricio.postgresql.messages +package com.github.mauricio.postgresql.messages.frontend -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.Message /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/QueryMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/QueryMessage.scala similarity index 56% rename from src/main/scala/com/github/mauricio/postgresql/messages/QueryMessage.scala rename to src/main/scala/com/github/mauricio/postgresql/messages/frontend/QueryMessage.scala index 50eb11bf..2be2f212 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/QueryMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/QueryMessage.scala @@ -1,6 +1,6 @@ -package com.github.mauricio.postgresql.messages +package com.github.mauricio.postgresql.messages.frontend -import com.github.mauricio.postgresql.{FrontendMessage, Message} +import com.github.mauricio.postgresql.messages.backend.Message /** diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala new file mode 100644 index 00000000..b2a79b56 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala @@ -0,0 +1,12 @@ +package com.github.mauricio.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.Message + + +/** + * User: Maurício Linhares + * Date: 3/3/12 + * Time: 7:34 PM + */ + +class StartupMessage ( val parameters : List[(String, Any)] ) extends FrontendMessage(Message.Startup) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala index dc36c504..0aa28240 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala @@ -1,26 +1,30 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.exceptions.UnsupportedAuthenticationMethodException +import com.github.mauricio.postgresql.messages.backend._ object AuthenticationStartupParser extends MessageParser { - private[parsers] val AUTH_REQ_OK = 0 - private[parsers] val AUTH_REQ_PASSWORD = 3 + val AuthenticationOk = 0 + val AuthenticationKerberosV5 = 2 + val AuthenticationCleartextPassword = 3 + val AuthenticationMD5Password = 5 + val AuthenticationSCMCredential = 6 + val AuthenticationGSS = 7 + val AuthenticationGSSContinue = 8 + val AuthenticationSSPI = 9 override def parseMessage(b: ChannelBuffer): Message = { val authenticationType = b.readInt() authenticationType match { - case AUTH_REQ_OK => { - new Message( Message.AuthenticationOk, authenticationType) - } - case AUTH_REQ_PASSWORD => { - new Message( Message.AuthenticationOk , authenticationType) - } + case AuthenticationOk => AuthenticationOkMessage.Instance + case AuthenticationCleartextPassword => AuthenticationChallengeCleartextMessage.Instance + case AuthenticationMD5Password => AuthenticationChallengeMD5.Instance case _ => { - throw new IllegalArgumentException("Unknown authentication method -> '%s'".format(authenticationType)) + throw new UnsupportedAuthenticationMethodException(authenticationType) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala index 32a0642e..55e05dd0 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} /** * User: Maurício Linhares @@ -12,7 +12,7 @@ import com.github.mauricio.postgresql.Message object BackendKeyDataParser extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { - new Message( Message.BackendKeyData, new ProcessData( b.readInt(), b.readInt() ) ) + new ProcessData( b.readInt(), b.readInt() ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/BindComplete.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/BindComplete.scala deleted file mode 100644 index ef286204..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/BindComplete.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import com.github.mauricio.postgresql.Message - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 2:32 AM - */ - -object BindComplete { - val Instance = new BindComplete() -} - - -class BindComplete extends Message( Message.BindComplete, true ) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/CloseComplete.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/CloseComplete.scala deleted file mode 100644 index 54d76be7..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/CloseComplete.scala +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import com.github.mauricio.postgresql.Message - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 11:37 PM - */ - -object CloseComplete { - val Instance = new CloseComplete() -} - -class CloseComplete extends Message(Message.CloseComplete, true) { - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala index 48416442..3d612561 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala @@ -1,7 +1,8 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.{QueryResult, ChannelUtils, Message} +import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.backend.{CommandCompleteMessage, Message} /** * User: Maurício Linhares @@ -15,17 +16,17 @@ object CommandCompleteParser extends MessageParser { val result = ChannelUtils.readCString(b) - val possibleRowCount = result.splitAt( result.lastIndexOf(" ") + 1 ) + val possibleRowCount = result.substring(result.lastIndexOf(" ")) val rowCount : Int = try { - possibleRowCount._2.toInt + possibleRowCount.toInt } catch { - case e : Exception => { + case e : NumberFormatException => { 0 } } - new Message( Message.CommandComplete, ( rowCount, result ) ) + new CommandCompleteMessage(rowCount, result) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala index 68b1da20..ceb86fdf 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.parsers -import com.github.mauricio.postgresql.Message import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.postgresql.messages.backend.{DataRowMessage, Message} /** * User: Maurício Linhares @@ -28,7 +28,7 @@ object DataRowParser extends MessageParser { } } - new Message(Message.DataRow, row) + new DataRowMessage(row) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala index e25c697d..7be9efc4 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala @@ -1,13 +1,9 @@ package com.github.mauricio.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.Message -import java.nio.charset.Charset +import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} -object ErrorParser extends MessageParser { +object ErrorParser extends InformationParser { - override def parseMessage(b: ChannelBuffer): Message = { - new Message( Message.Error , b.toString( Charset.forName("UTF-8") )) - } + def createMessage(fields: Map[String, String]): Message = new ErrorMessage(fields) } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala new file mode 100644 index 00000000..959d7a6f --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala @@ -0,0 +1,35 @@ +package com.github.mauricio.postgresql.parsers + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, InformationMessage, Message} +import com.github.mauricio.postgresql.ChannelUtils + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 12:54 AM + */ +abstract class InformationParser extends MessageParser { + + override def parseMessage(b: ChannelBuffer): Message = { + + val fields = scala.collection.mutable.Map[String,String]() + + while ( b.readable() ) { + val kind = b.readByte() + + if ( kind != 0 ) { + fields.put( + InformationMessage.fieldName(kind.asInstanceOf[Char]), + ChannelUtils.readCString(b) + ) + } + + } + + createMessage(fields.toMap) + } + + def createMessage( fields : Map[String,String] ) : Message + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala index afdfbdf7..f40bcb44 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala @@ -1,12 +1,13 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.{CloseComplete, BindComplete, ParseComplete, Message} +import com.github.mauricio.postgresql.exceptions.ParserNotAvailableException object MessageParser { private val parsers = Map( - Message.AuthenticationOk -> AuthenticationStartupParser, + Message.AuthenticationResponse -> AuthenticationStartupParser, Message.BackendKeyData -> BackendKeyDataParser, Message.BindComplete -> new ReturningMessageParser(BindComplete.Instance), Message.CloseComplete -> new ReturningMessageParser(CloseComplete.Instance), diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala index 0e032aec..488203c6 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala @@ -2,7 +2,8 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer import collection.mutable.ListBuffer -import com.github.mauricio.postgresql.{ChannelUtils, Message} +import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.backend.{InformationMessage, NoticeMessage, Message} /** * User: Maurício Linhares @@ -10,23 +11,8 @@ import com.github.mauricio.postgresql.{ChannelUtils, Message} * Time: 10:06 PM */ -object NoticeParser extends MessageParser { +object NoticeParser extends InformationParser { - override def parseMessage(b: ChannelBuffer): Message = { - - val listBuffer = ListBuffer[(Char, String)]() - - while ( b.readable() ) { - val kind = b.readByte() - - if ( kind != 0 ) { - listBuffer.append( ( kind.asInstanceOf[Char], ChannelUtils.readCString(b) ) ) - } - - } - - new Message( Message.Notice, listBuffer.toList ) - - } + def createMessage(fields: Map[String, String]): Message = new NoticeMessage(fields) } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala index 1d8df757..e2f7aaf1 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala @@ -1,7 +1,8 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.{ChannelUtils, Message} +import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} /** * User: Maurício Linhares @@ -14,7 +15,7 @@ object ParameterStatusParser extends MessageParser { import ChannelUtils._ override def parseMessage(b: ChannelBuffer): Message = { - new Message( Message.ParameterStatus, ( readCString(b), readCString(b) ) ) + new ParameterStatusMessage( readCString(b), readCString(b) ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ParseComplete.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ParseComplete.scala deleted file mode 100644 index b5e3f4e6..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ParseComplete.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import com.github.mauricio.postgresql.Message - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 2:29 AM - */ - -object ParseComplete { - val Instance = new ParseComplete() -} - -class ParseComplete extends Message(Message.ParseComplete, true) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ProcessData.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ProcessData.scala deleted file mode 100644 index 03715299..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ProcessData.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 11:13 PM - */ - -class ProcessData ( val processId : Int, val secretKey : Int ) { - - override def toString : String = { - "ProcessData: %s - %s".format( this.processId, secretKey) - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala index 345c5872..0fc9b4e2 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.parsers -import com.github.mauricio.postgresql.{Message} import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.postgresql.messages.backend.{ReadyForQueryMessage, Message} /** * User: Maurício Linhares @@ -12,7 +12,7 @@ import org.jboss.netty.buffer.ChannelBuffer object ReadyForQueryParser extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { - new Message( Message.ReadyForQuery , b.readByte().asInstanceOf[Char]) + new ReadyForQueryMessage( b.readByte().asInstanceOf[Char] ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala index 6c34fac9..fea3a142 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.Message /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala index 5b131854..99c9c29e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala @@ -1,7 +1,8 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.{ChannelUtils, Message} +import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} /** * User: Maurício Linhares @@ -66,7 +67,7 @@ object RowDescriptionParser extends MessageParser { ) } - new Message( Message.RowDescription, columns ) + new RowDescriptionMessage( columns ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionObjectFactory.scala similarity index 63% rename from src/main/scala/com/github/mauricio/postgresql/ConnectionObjectFactory.scala rename to src/main/scala/com/github/mauricio/postgresql/pool/ConnectionObjectFactory.scala index 0d23c52d..e0d82f1e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionObjectFactory.scala @@ -1,8 +1,9 @@ -package com.github.mauricio.postgresql +package com.github.mauricio.postgresql.pool import org.apache.commons.pool.PoolableObjectFactory import concurrent.duration._ import concurrent.Await +import com.github.mauricio.postgresql.{Configuration, DatabaseConnectionHandler} /** * User: Maurício Linhares @@ -11,13 +12,11 @@ import concurrent.Await */ class ConnectionObjectFactory( - val host : String, - val port : Int, - val user: String, - val database: String) extends PoolableObjectFactory[DatabaseConnectionHandler] { + configuration : Configuration) + extends PoolableObjectFactory[DatabaseConnectionHandler] { def makeObject(): DatabaseConnectionHandler = { - val connection = new DatabaseConnectionHandler(host, port, user, database) + val connection = new DatabaseConnectionHandler(configuration) Await.result( connection.connect, 5 seconds ) connection } diff --git a/src/main/scala/com/github/mauricio/postgresql/ConnectionPool.scala b/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionPool.scala similarity index 54% rename from src/main/scala/com/github/mauricio/postgresql/ConnectionPool.scala rename to src/main/scala/com/github/mauricio/postgresql/pool/ConnectionPool.scala index fdcdfd95..f0857195 100644 --- a/src/main/scala/com/github/mauricio/postgresql/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionPool.scala @@ -1,6 +1,7 @@ -package com.github.mauricio.postgresql +package com.github.mauricio.postgresql.pool import org.apache.commons.pool.impl.StackObjectPool +import com.github.mauricio.postgresql.{Configuration, Connection} /** * User: Maurício Linhares @@ -8,13 +9,9 @@ import org.apache.commons.pool.impl.StackObjectPool * Time: 10:38 PM */ -class ConnectionPool( - val host : String, - val port : Int, - val user: String, - val database: String) { +class ConnectionPool( val configuration : Configuration ) { - private val factory = new ConnectionObjectFactory(host, port, user, database) + private val factory = new ConnectionObjectFactory(configuration) private val pool = new StackObjectPool( this.factory, 1 ) def doWithConnection[T]( fn : Connection => T ) : T = { diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index 21e7692a..ea24624b 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -4,9 +4,6 @@ import column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} import org.specs2.mutable.Specification import scala.concurrent.duration._ import concurrent.Await -import org.specs2.runner.JUnitRunner -import org.junit.runner.RunWith -import org.specs2.mutable.SpecificationWithJUnit /** * User: Maurício Linhares @@ -75,8 +72,17 @@ class DatabaseConnectionHandlerSpec extends Specification { val preparedStatementSelect = "select * from prepared_statement_test" def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { + val configuration = new Configuration( + host = "localhost", + port = 5433, + username = "postgres", + database = Some("netty_driver_test") ) + withHandler( configuration, fn ) + } + + def withHandler[T]( configuration : Configuration, fn: (DatabaseConnectionHandler) => T): T = { - val handler = new DatabaseConnectionHandler("localhost", 5433, "postgres", "netty_driver_test") + val handler = new DatabaseConnectionHandler(configuration) try { Await.result(handler.connect, Duration(5, SECONDS)) diff --git a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala b/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala index ad092765..037950f1 100644 --- a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala @@ -1,5 +1,6 @@ package com.github.mauricio.postgresql +import messages.backend.{ErrorMessage, Message} import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers @@ -47,13 +48,14 @@ class MessageDecoderSpec extends Specification { val textBytes = text.getBytes( CharsetHelper.Unicode ) buffer.writeByte('E') - buffer.writeInt( textBytes.length + 4 ) + buffer.writeInt( textBytes.length + 4 + 1 ) + buffer.writeByte('M') buffer.writeBytes( textBytes ) - val result = this.decoder.decode( null, null, buffer ).asInstanceOf[Message] + val result = this.decoder.decode( null, null, buffer ).asInstanceOf[ErrorMessage] List( - result.content === text, + result.message === text, buffer.readerIndex() === (textBytes.length + 5 ) ) } diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala index 383cb9ba..2c84c2e8 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala @@ -2,7 +2,7 @@ package com.github.mauricio.postgresql.parsers import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} /** * User: Maurício Linhares @@ -18,11 +18,12 @@ class ParserESpec extends Specification { val error = "this is my error message" val buffer = ChannelBuffers.dynamicBuffer() + buffer.writeByte('M') buffer.writeBytes( error.getBytes ) - val message = ErrorParser.parseMessage( buffer ) + val message = ErrorParser.parseMessage( buffer ).asInstanceOf[ErrorMessage] - List(message.content === error, message.name === Message.Error) + List(message.message === error, message.name === Message.Error) } } diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserKSpec.scala b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserKSpec.scala index db64770d..ba4db061 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserKSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserKSpec.scala @@ -2,7 +2,7 @@ package com.github.mauricio.postgresql.parsers import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} /** * User: Maurício Linhares @@ -22,11 +22,10 @@ class ParserKSpec extends Specification { buffer.writeInt(10) buffer.writeInt(20) - val message = parser.parseMessage( buffer ) - val data = message.content.asInstanceOf[ProcessData] + val data = parser.parseMessage( buffer ).asInstanceOf[ProcessData] List( - message.name === Message.BackendKeyData, + data.name === Message.BackendKeyData, data.processId === 10, data.secretKey === 20 ) diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala index 32dfde2a..0c7229c3 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala @@ -3,7 +3,7 @@ package com.github.mauricio.postgresql.parsers import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers import java.nio.charset.Charset -import com.github.mauricio.postgresql.Message +import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} /** * User: Maurício Linhares @@ -29,13 +29,12 @@ class ParserSSpec extends Specification { buffer.writeBytes( value.getBytes( Charset.forName("UTF-8") ) ) buffer.writeByte(0) - val message = this.parser.parseMessage( buffer ) - val content = message.content.asInstanceOf[(String,String)] + val content = this.parser.parseMessage( buffer ).asInstanceOf[ParameterStatusMessage] List( - content._1 === key, - content._2 === value, - message.name === Message.ParameterStatus, + content.key === key, + content.value === value, + content.name === Message.ParameterStatus, buffer.readerIndex() === buffer.writerIndex() ) } From 13114fc5bedc72d5f424d3df3a4a0b579c0051c3 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 31 Mar 2013 23:37:49 -0300 Subject: [PATCH 007/357] Implemented cleartext and MD5 authentication methods, fixed error message parser and notice parser to correctly detect all fields, added specs for authentication methods --- README.markdown | 4 +- .../postgresql/util/PostgreSQLMD5Digest.java | 95 +++++++++++++++++++ .../mauricio/postgresql/ChannelUtils.scala | 5 +- .../mauricio/postgresql/CharsetHelper.scala | 6 +- .../DatabaseConnectionHandler.scala | 57 ++++++----- .../mauricio/postgresql/MessageDecoder.scala | 14 +-- .../mauricio/postgresql/MessageEncoder.scala | 19 ++-- .../mauricio/postgresql/MutableQuery.scala | 3 +- .../encoders/CredentialEncoder.scala | 41 ++++++++ .../ExecutePreparedStatementEncoder.scala | 3 +- .../PreparedStatementOpeningEncoder.scala | 3 +- .../encoders/QueryMessageEncoder.scala | 3 +- .../exceptions/DatabaseException.scala | 2 +- ...issingCredentialInformationException.scala | 2 +- ...henticationChallengeCleartextMessage.scala | 2 +- .../backend/AuthenticationChallengeMD5.scala | 8 +- .../AuthenticationChallengeMessage.scala | 4 +- .../backend/AuthenticationMessage.scala | 2 +- .../messages/backend/ErrorMessage.scala | 2 +- .../messages/backend/InformationMessage.scala | 42 +++++--- .../postgresql/messages/backend/Message.scala | 3 +- .../messages/backend/NoticeMessage.scala | 2 +- .../messages/frontend/CredentialMessage.scala | 7 +- .../parsers/AuthenticationStartupParser.scala | 6 +- .../postgresql/parsers/ErrorParser.scala | 2 +- .../parsers/InformationParser.scala | 8 +- .../postgresql/parsers/MessageParser.scala | 2 +- .../postgresql/parsers/NoticeParser.scala | 7 +- .../mauricio/postgresql/util/MD5Digest.scala | 62 ++++++++++++ .../DatabaseConnectionHandlerSpec.scala | 85 ++++++++++++++++- .../postgresql/MessageDecoderSpec.scala | 7 +- 31 files changed, 408 insertions(+), 100 deletions(-) create mode 100644 src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java create mode 100644 src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala create mode 100644 src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala diff --git a/README.markdown b/README.markdown index 8dbf1c09..7d88a933 100644 --- a/README.markdown +++ b/README.markdown @@ -15,12 +15,14 @@ to PostgreSQL. - parses all basic PostgreSQL types, other types are parsed as string - date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects - portals/prepared statements +- MD5 and cleartext password authentication methods ## What is missing? - stored procedures -- authentication mechanisms +- more authentication mechanisms - benchmarks and more testing +- timeout handler for initial handshare and queries ## What are the design goals? diff --git a/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java b/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java new file mode 100644 index 00000000..d9841067 --- /dev/null +++ b/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java @@ -0,0 +1,95 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2003-2011, PostgreSQL Global Development Group +* +* IDENTIFICATION +* $PostgreSQL: pgjdbc/org/postgresql/util/MD5Digest.java,v 1.13 2011/08/02 13:50:29 davecramer Exp $ +* +*------------------------------------------------------------------------- +*/ +package com.github.mauricio.postgresql.util; + +import org.jboss.netty.util.CharsetUtil; + +import java.security.MessageDigest; + +/** + * MD5-based utility function to obfuscate passwords before network + * transmission. + * + * @author Jeremy Wohl + */ + +public class PostgreSQLMD5Digest +{ + private static final byte[] lookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' }; + + private PostgreSQLMD5Digest() + { + } + + /* + * Encodes user/password/salt information in the following way: + * MD5(MD5(password + user) + salt) + * + * @param user The connecting user. + * @param password The connecting user's password. + * @param salt A four-salt sent by the server. + * + * @return A 35-byte array, comprising the string "md5" and an MD5 digest. + */ + public static byte[] encode(String userStr, String passwordStr, byte[] salt) + { + byte[] user = userStr.getBytes(CharsetUtil.UTF_8); + byte[] password = passwordStr.getBytes(CharsetUtil.UTF_8); + + MessageDigest md; + byte[] temp_digest, pass_digest; + byte[] hex_digest = new byte[35]; + + try + { + md = MessageDigest.getInstance("MD5"); + + md.update(password); + md.update(user); + temp_digest = md.digest(); + + bytesToHex(temp_digest, hex_digest, 0); + md.update(hex_digest, 0, 32); + md.update(salt); + pass_digest = md.digest(); + + bytesToHex(pass_digest, hex_digest, 3); + hex_digest[0] = 'm'; + hex_digest[1] = 'd'; + hex_digest[2] = '5'; + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + + return hex_digest; + } + + + /* + * Turn 16-byte stream into a human-readable 32-byte hex string + */ + private static void bytesToHex(byte[] bytes, byte[] hex, int offset) + { + + int i, c, j, pos = offset; + + for (i = 0; i < 16; i++) + { + c = bytes[i] & 0xFF; + j = c >> 4; + hex[pos++] = lookup[j]; + j = (c & 0xF); + hex[pos++] = lookup[j]; + } + } +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala b/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala index 434dced6..4014181f 100644 --- a/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala +++ b/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala @@ -2,6 +2,7 @@ package com.github.mauricio.postgresql import org.jboss.netty.buffer.ChannelBuffer import util.Log +import org.jboss.netty.util.CharsetUtil /** * User: Maurício Linhares @@ -36,7 +37,7 @@ object ChannelUtils { } def writeCString( content : String, b : ChannelBuffer ) : Unit = { - b.writeBytes( content.getBytes( CharsetHelper.Unicode ) ) + b.writeBytes( content.getBytes( CharsetUtil.UTF_8 ) ) b.writeByte(0) } @@ -54,7 +55,7 @@ object ChannelUtils { b.resetReaderIndex() - val result = b.toString( b.readerIndex(), count - 1, CharsetHelper.Unicode ) + val result = b.toString( b.readerIndex(), count - 1, CharsetUtil.UTF_8 ) b.readerIndex( b.readerIndex() + count) diff --git a/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala b/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala index c69b6c02..f5f2be8f 100644 --- a/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala +++ b/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala @@ -1,6 +1,6 @@ package com.github.mauricio.postgresql -import java.nio.charset.Charset +import org.jboss.netty.util.CharsetUtil /** * User: Maurício Linhares @@ -10,10 +10,8 @@ import java.nio.charset.Charset object CharsetHelper { - val Unicode = Charset.forName("UTF-8") - def toBytes( content : String ) : Array[Byte] = { - content.getBytes( Unicode ) + content.getBytes( CharsetUtil.UTF_8 ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala index 103f4456..d6c484d2 100644 --- a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala @@ -40,6 +40,7 @@ class DatabaseConnectionHandler private val parameterStatus = new ConcurrentHashMap[String, String]() private val parsedStatements = new ConcurrentHashMap[String, Array[ColumnData]]() private var _processData: Option[ProcessData] = None + private var authenticated = false private val factory = new NioClientSocketChannelFactory( ExecutorServiceUtils.CachedThreadPool, @@ -89,25 +90,30 @@ class DatabaseConnectionHandler val closingPromise = Promise[Connection]() - this.currentChannel.write(CloseMessage.Instance).addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { + if ( this.currentChannel.isConnected ) { + this.currentChannel.write(CloseMessage.Instance).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { - if ( future.getCause != null ) { - closingPromise.failure(future.getCause) - } else { - future.getChannel.close().addListener(new ChannelFutureListener { - def operationComplete(internalFuture: ChannelFuture) { - if ( internalFuture.isSuccess ) { - closingPromise.success(DatabaseConnectionHandler.this) - } else { - closingPromise.failure(internalFuture.getCause) + if ( future.getCause != null ) { + closingPromise.failure(future.getCause) + } else { + future.getChannel.close().addListener(new ChannelFutureListener { + def operationComplete(internalFuture: ChannelFuture) { + if ( internalFuture.isSuccess ) { + closingPromise.success(DatabaseConnectionHandler.this) + } else { + closingPromise.failure(internalFuture.getCause) + } } - } - }) + }) + } + } + }) - } - }) + } else { + closingPromise.success(this) + } closingPromise.future } @@ -140,7 +146,7 @@ class DatabaseConnectionHandler case Message.BindComplete => { log.debug("Finished binding statement - {}", this.currentPreparedStatement) } - case Message.AuthenticationResponse => { + case Message.Authentication => { this.onAuthenticationResponse(ctx.getChannel, m.asInstanceOf[AuthenticationMessage]) } case Message.CommandComplete => { @@ -236,13 +242,14 @@ class DatabaseConnectionHandler if ( !this.connectionFuture.isCompleted ) { this.connectionFuture.failure(e) - } - - if (this.queryPromise.isDefined) { - log.error("Setting error on future {}", this.queryPromise.get) - this.queryPromise.get.failure(e) - this.queryPromise = None - this.currentPreparedStatement = None + this.disconnect + } else { + if (this.queryPromise.isDefined) { + log.error("Setting error on future {}", this.queryPromise.get) + this.queryPromise.get.failure(e) + this.queryPromise = None + this.currentPreparedStatement = None + } } } @@ -314,6 +321,7 @@ class DatabaseConnectionHandler message match { case m : AuthenticationOkMessage => { log.debug("Successfully logged in to database") + this.authenticated = true } case m : AuthenticationChallengeCleartextMessage => { channel.write(this.credential(m)) @@ -338,7 +346,8 @@ class DatabaseConnectionHandler new CredentialMessage( configuration.username, configuration.password.get, - authenticationMessage.challengeType + authenticationMessage.challengeType, + authenticationMessage.salt ) } else { throw new MissingCredentialInformationException( diff --git a/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala index 4cc6d192..65cca944 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala @@ -4,23 +4,23 @@ import org.jboss.netty.handler.codec.frame.FrameDecoder import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.parsers.{MessageParser, AuthenticationStartupParser} import org.jboss.netty.channel.{ChannelHandlerContext, Channel} +import util.Log object MessageDecoder extends FrameDecoder { - - override def decode(ctx: ChannelHandlerContext, c: Channel, b: ChannelBuffer) : Object = { - var code : Char = 0 - var length : Int = 0 + val log = Log.getByName("MessageDecoder") + + override def decode(ctx: ChannelHandlerContext, c: Channel, b: ChannelBuffer) : Object = { if ( b.readableBytes() >= 5 ) { b.markReaderIndex() - code = b.readByte().asInstanceOf[Char] - length = b.readInt() - 4 + val code = b.readByte().asInstanceOf[Char] + val lengthWithSelf = b.readInt() + val length = lengthWithSelf - 4 if ( b.readableBytes() >= length ) { - code match { case 'R' => { AuthenticationStartupParser.parseMessage( b ) diff --git a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala index 3e8d0d46..83bcb8ea 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala @@ -2,8 +2,7 @@ package com.github.mauricio.postgresql import encoders._ import exceptions.EncoderNotAvailableException -import messages.backend.Message -import messages.frontend.FrontendMessage +import messages.frontend._ import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.buffer.ChannelBuffer @@ -19,19 +18,20 @@ object MessageEncoder extends OneToOneEncoder { val log = Log.getByName("MessageEncoder") - val encoders = Map( - Message.Close -> CloseMessageEncoder, - Message.Execute -> ExecutePreparedStatementEncoder, - Message.Parse -> PreparedStatementOpeningEncoder, - Message.Startup -> StartupMessageEncoder, - Message.Query -> QueryMessageEncoder + val encoders : Map[Class[_],Encoder] = Map( + classOf[CloseMessage] -> CloseMessageEncoder, + classOf[PreparedStatementExecuteMessage] -> ExecutePreparedStatementEncoder, + classOf[PreparedStatementOpeningMessage] -> PreparedStatementOpeningEncoder, + classOf[StartupMessage] -> StartupMessageEncoder, + classOf[QueryMessage] -> QueryMessageEncoder, + classOf[CredentialMessage] -> CredentialEncoder ) override def encode(ctx: ChannelHandlerContext, channel: Channel, msg: AnyRef): ChannelBuffer = { val buffer = msg match { case message : FrontendMessage => { - val option = this.encoders.get( message.kind ) + val option = this.encoders.get( message.getClass ) if ( option.isDefined ) { option.get.encode(message) } else { @@ -44,7 +44,6 @@ object MessageEncoder extends OneToOneEncoder { } buffer - } } diff --git a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala index 563cfb69..87521262 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala @@ -4,6 +4,7 @@ import messages.backend.ColumnData import org.jboss.netty.buffer.ChannelBuffer import util.Log import collection.mutable.ArrayBuffer +import org.jboss.netty.util.CharsetUtil /** * User: Maurício Linhares @@ -41,7 +42,7 @@ class MutableQuery ( val columnTypes : Array[ColumnData] ) extends ResultSet { realRow(index) = if ( row(index) == null ) { null } else { - this.columnTypes(index).decoder.decode( row(index).toString(Unicode) ) + this.columnTypes(index).decoder.decode( row(index).toString(CharsetUtil.UTF_8) ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala new file mode 100644 index 00000000..ab3ed25d --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala @@ -0,0 +1,41 @@ +package com.github.mauricio.postgresql.encoders + +import com.github.mauricio.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} +import org.jboss.netty.util.CharsetUtil +import com.github.mauricio.postgresql.util.PostgreSQLMD5Digest +import com.github.mauricio.postgresql.ChannelUtils + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 6:48 PM + */ +object CredentialEncoder extends Encoder { + + def encode(message: FrontendMessage): ChannelBuffer = { + + val credentialMessage = message.asInstanceOf[CredentialMessage] + + val password = credentialMessage.authenticationType match { + case AuthenticationResponseType.Cleartext => { + credentialMessage.password.getBytes(CharsetUtil.UTF_8) + } + case AuthenticationResponseType.MD5 => { + PostgreSQLMD5Digest.encode( credentialMessage.username, credentialMessage.password, credentialMessage.salt.get ) + } + } + + val buffer = ChannelBuffers.dynamicBuffer(1 + 4 + password.size + 1) + buffer.writeByte(Message.PasswordMessage) + buffer.writeInt(0) + buffer.writeBytes(password) + buffer.writeByte(0) + + ChannelUtils.writeLength(buffer) + + buffer + } + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala index c2878e59..a11726cd 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -5,6 +5,7 @@ import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} import com.github.mauricio.postgresql.column.ColumnEncoderDecoder import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} import com.github.mauricio.postgresql.messages.backend.Message +import org.jboss.netty.util.CharsetUtil /** * User: Maurício Linhares @@ -37,7 +38,7 @@ object ExecutePreparedStatementEncoder extends Encoder { if ( value == null ) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes( CharsetHelper.Unicode ) + val encoded = ColumnEncoderDecoder.encode(value).getBytes( CharsetUtil.UTF_8 ) bindBuffer.writeInt(encoded.length) bindBuffer.writeBytes( encoded ) } diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala index ff8758f7..8ff4fe7e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -5,6 +5,7 @@ import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} import com.github.mauricio.postgresql.column.ColumnEncoderDecoder import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} import com.github.mauricio.postgresql.messages.backend.Message +import org.jboss.netty.util.CharsetUtil /** @@ -58,7 +59,7 @@ object PreparedStatementOpeningEncoder extends Encoder { if ( value == null ) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes( CharsetHelper.Unicode ) + val encoded = ColumnEncoderDecoder.encode(value).getBytes( CharsetUtil.UTF_8 ) bindBuffer.writeInt(encoded.length) bindBuffer.writeBytes( encoded ) } diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala index b9da4338..a871d901 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala @@ -20,8 +20,7 @@ object QueryMessageEncoder extends Encoder { val buffer = ChannelBuffers.dynamicBuffer() buffer.writeByte( Message.Query ) buffer.writeInt(0) - buffer.writeBytes( CharsetHelper.toBytes( m.query ) ) - buffer.writeByte(0) + ChannelUtils.writeCString(m.query, buffer) ChannelUtils.writeLength( buffer ) diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala index 003c5e6f..6432008a 100644 --- a/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala @@ -8,4 +8,4 @@ import com.github.mauricio.postgresql.messages.backend.ErrorMessage * Time: 1:00 AM */ class DatabaseException( val errorMessage : ErrorMessage ) - extends IllegalStateException( errorMessage.message ) \ No newline at end of file + extends IllegalStateException( errorMessage.toString ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala index 2a4ba2a2..4614641c 100644 --- a/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala @@ -1,6 +1,6 @@ package com.github.mauricio.postgresql.exceptions -import com.github.mauricio.postgresql.messages.backend.{AuthenticationResponseType, AuthenticationMessage} +import com.github.mauricio.postgresql.messages.backend.AuthenticationResponseType /** * User: mauricio diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala index 02e96edc..34da9338 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala @@ -11,4 +11,4 @@ object AuthenticationChallengeCleartextMessage { } class AuthenticationChallengeCleartextMessage - extends AuthenticationChallengeMessage( AuthenticationResponseType.Cleartext ) \ No newline at end of file + extends AuthenticationChallengeMessage( AuthenticationResponseType.Cleartext, None ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala index c8963f99..7bcc4732 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala @@ -6,9 +6,5 @@ package com.github.mauricio.postgresql.messages.backend * Time: 1:32 AM */ -object AuthenticationChallengeMD5 { - val Instance = new AuthenticationChallengeMD5() -} - -class AuthenticationChallengeMD5 - extends AuthenticationChallengeMessage( AuthenticationResponseType.MD5 ) \ No newline at end of file +class AuthenticationChallengeMD5( salt : Array[Byte] ) + extends AuthenticationChallengeMessage( AuthenticationResponseType.MD5, Some(salt) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala index e4d27977..508fc7ec 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala @@ -5,5 +5,7 @@ package com.github.mauricio.postgresql.messages.backend * Date: 3/31/13 * Time: 1:45 AM */ -class AuthenticationChallengeMessage ( val challengeType : AuthenticationResponseType.AuthenticationResponseType ) +class AuthenticationChallengeMessage ( + val challengeType : AuthenticationResponseType.AuthenticationResponseType, + val salt : Option[Array[Byte]] ) extends AuthenticationMessage \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala index 5d07bd68..c20e2c5c 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala @@ -5,4 +5,4 @@ package com.github.mauricio.postgresql.messages.backend * Date: 3/31/13 * Time: 1:30 AM */ -abstract class AuthenticationMessage extends Message( Message.AuthenticationResponse ) \ No newline at end of file +abstract class AuthenticationMessage extends Message( Message.Authentication ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala index a93bc456..31f67ea6 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala @@ -5,5 +5,5 @@ package com.github.mauricio.postgresql.messages.backend * Date: 3/31/13 * Time: 12:57 AM */ -class ErrorMessage ( fields : Map[String,String] ) +class ErrorMessage ( fields : Map[Char,String] ) extends InformationMessage( Message.Error, fields ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala index 3fafb66e..2e35864a 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala @@ -8,27 +8,43 @@ package com.github.mauricio.postgresql.messages.backend object InformationMessage { + val Severity = 'S' + val SQLState = 'C' + val Message = 'M' + val Detail = 'D' + val Hint = 'H' + val Position = 'P' + val InternalQuery = 'q' + val Where = 'W' + val File = 'F' + val Line = 'L' + val Routine = 'R' + val Fields = Map( - 'S' -> "Severity", - 'C' -> "SQLSTATE", - 'M' -> "Message", - 'D' -> "Detail", - 'H' -> "Hint", - 'P' -> "Position", - 'q' -> "Internal Query", - 'W' -> "Where", - 'F' -> "File", - 'L' -> "Line", - 'R' -> "Routine" + Severity -> "Severity", + SQLState -> "SQLSTATE", + Message -> "Message", + Detail -> "Detail", + Hint -> "Hint", + Position -> "Position", + InternalQuery -> "Internal Query", + Where -> "Where", + File -> "File", + Line -> "Line", + Routine -> "Routine" ) def fieldName( name : Char ) : String = Fields.getOrElse(name, { name.toString } ) } -abstract case class InformationMessage ( statusCode : Char, val fields : Map[String,String] ) +abstract class InformationMessage ( statusCode : Char, val fields : Map[Char,String] ) extends Message( statusCode ) { - def message : String = this.fields( "Message" ) + def message : String = this.fields( 'M' ) + + override def toString : String = { + "%s(fields=%s)".format( this.getClass.getSimpleName, fields.map { pair => InformationMessage.fieldName(pair._1) -> pair._2 } ) + } } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala index f33b590f..ad95bebf 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.messages.backend object Message { - val AuthenticationResponse = 'R' + val Authentication = 'R' val BackendKeyData = 'K' val Bind = 'B' val BindComplete = '2' @@ -20,6 +20,7 @@ object Message { val ParameterStatus = 'S' val Parse = 'P' val ParseComplete = '1' + val PasswordMessage = 'p' val PortalSuspended = 's' val Query = 'Q' val RowDescription = 'T' diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala index 15dcf891..2ae32eb3 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala @@ -5,5 +5,5 @@ package com.github.mauricio.postgresql.messages.backend * Date: 3/31/13 * Time: 12:45 AM */ -class NoticeMessage ( fields : Map[String,String] ) +class NoticeMessage ( fields : Map[Char,String] ) extends InformationMessage( Message.Notice, fields ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala index 6d12c8f7..408b2bba 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala +++ b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala @@ -1,6 +1,6 @@ package com.github.mauricio.postgresql.messages.frontend -import com.github.mauricio.postgresql.messages.backend.AuthenticationResponseType +import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} /** * User: mauricio @@ -10,4 +10,7 @@ import com.github.mauricio.postgresql.messages.backend.AuthenticationResponseTyp class CredentialMessage( val username : String, val password : String, - val kind : AuthenticationResponseType.AuthenticationResponseType ) \ No newline at end of file + val authenticationType : AuthenticationResponseType.AuthenticationResponseType, + val salt : Option[Array[Byte]] + ) + extends FrontendMessage( Message.PasswordMessage ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala index 0aa28240..21bb3c1e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala @@ -22,7 +22,11 @@ object AuthenticationStartupParser extends MessageParser { authenticationType match { case AuthenticationOk => AuthenticationOkMessage.Instance case AuthenticationCleartextPassword => AuthenticationChallengeCleartextMessage.Instance - case AuthenticationMD5Password => AuthenticationChallengeMD5.Instance + case AuthenticationMD5Password => { + val bytes = new Array[Byte](b.readableBytes()) + b.readBytes(bytes) + new AuthenticationChallengeMD5(bytes) + } case _ => { throw new UnsupportedAuthenticationMethodException(authenticationType) } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala index 7be9efc4..d4baae68 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala @@ -4,6 +4,6 @@ import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} object ErrorParser extends InformationParser { - def createMessage(fields: Map[String, String]): Message = new ErrorMessage(fields) + def createMessage(fields: Map[Char, String]): Message = new ErrorMessage(fields) } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala index 959d7a6f..ea3bb1bf 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, InformationMessage, Message} +import com.github.mauricio.postgresql.messages.backend.Message import com.github.mauricio.postgresql.ChannelUtils /** @@ -13,14 +13,14 @@ abstract class InformationParser extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { - val fields = scala.collection.mutable.Map[String,String]() + val fields = scala.collection.mutable.Map[Char,String]() while ( b.readable() ) { val kind = b.readByte() if ( kind != 0 ) { fields.put( - InformationMessage.fieldName(kind.asInstanceOf[Char]), + kind.toChar, ChannelUtils.readCString(b) ) } @@ -30,6 +30,6 @@ abstract class InformationParser extends MessageParser { createMessage(fields.toMap) } - def createMessage( fields : Map[String,String] ) : Message + def createMessage( fields : Map[Char,String] ) : Message } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala index f40bcb44..122fa5fc 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala @@ -7,7 +7,7 @@ import com.github.mauricio.postgresql.exceptions.ParserNotAvailableException object MessageParser { private val parsers = Map( - Message.AuthenticationResponse -> AuthenticationStartupParser, + Message.Authentication -> AuthenticationStartupParser, Message.BackendKeyData -> BackendKeyDataParser, Message.BindComplete -> new ReturningMessageParser(BindComplete.Instance), Message.CloseComplete -> new ReturningMessageParser(CloseComplete.Instance), diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala index 488203c6..fb6ae34c 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala @@ -1,9 +1,6 @@ package com.github.mauricio.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer -import collection.mutable.ListBuffer -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.{InformationMessage, NoticeMessage, Message} +import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, Message} /** * User: Maurício Linhares @@ -13,6 +10,6 @@ import com.github.mauricio.postgresql.messages.backend.{InformationMessage, Noti object NoticeParser extends InformationParser { - def createMessage(fields: Map[String, String]): Message = new NoticeMessage(fields) + def createMessage(fields: Map[Char, String]): Message = new NoticeMessage(fields) } diff --git a/src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala b/src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala new file mode 100644 index 00000000..50dcef2f --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala @@ -0,0 +1,62 @@ +package com.github.mauricio.postgresql.util + +import java.security._ +import org.jboss.netty.util.CharsetUtil + +/** + * User: mauricio + * Date: 3/31/13 + * Time: 6:19 PM + * + * Copied over and translated to Scala from org.postgresql.util.MD5Digest from the PostgreSQL JDBC driver. + * + */ +object MD5Digest { + + val Lookup = Array[Byte]( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') + + def encode( username : String, password : String, salt : Array[Byte] ) : Array[Byte] = { + + val usernameBytes = username.getBytes(CharsetUtil.UTF_8) + val passwordBytes = password.getBytes(CharsetUtil.UTF_8) + val hexDigest = new Array[Byte](35) + + val messageDigest = MessageDigest.getInstance("MD5") + + messageDigest.update(usernameBytes) + messageDigest.update(passwordBytes) + + val tempDigest = messageDigest.digest() + + bytesToHex(tempDigest, hexDigest, 0 ) + + messageDigest.update(hexDigest, 0, 32) + messageDigest.update(salt) + + val passwordDigest = messageDigest.digest() + + bytesToHex(passwordDigest, hexDigest, 3) + hexDigest(0) = 'm' + hexDigest(1) = 'd' + hexDigest(2) = '5' + + hexDigest + } + + private def bytesToHex( bytes : Array[Byte], hex : Array[Byte], offset : Int) { + var pos = offset + var i = 0 + + while ( i < 16 ) { + val c = bytes(i) & 0xFF + var j = c >> 4 + pos += 1 + hex(pos) = Lookup(j) + j = (c & 0xF) + pos += 1 + hex(pos) = Lookup(j) + i += 1 + } + } + +} \ No newline at end of file diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index ea24624b..d7cba848 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -1,6 +1,8 @@ package com.github.mauricio.postgresql import column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} +import exceptions.{DatabaseException, UnsupportedAuthenticationMethodException} +import messages.backend.InformationMessage import org.specs2.mutable.Specification import scala.concurrent.duration._ import concurrent.Await @@ -58,6 +60,9 @@ class DatabaseConnectionHandlerSpec extends Specification { ) """ + val DatabaseName = Some("netty_driver_test") + val DatabasePort = 5433 + val select = "select * from type_test_table" val preparedStatementCreate = """create temp table prepared_statement_test ( @@ -73,10 +78,9 @@ class DatabaseConnectionHandlerSpec extends Specification { def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { val configuration = new Configuration( - host = "localhost", - port = 5433, + port = DatabasePort, username = "postgres", - database = Some("netty_driver_test") ) + database = DatabaseName) withHandler( configuration, fn ) } @@ -218,6 +222,81 @@ class DatabaseConnectionHandlerSpec extends Specification { } + "login using MD5 authentication" in { + + val configuration = new Configuration( + username = "postgres_md5", + password = Some("postgres_md5"), + port = DatabasePort, + database = DatabaseName + ) + + withHandler(configuration, { + handler => + val result = executeQuery(handler, "SELECT 0") + result.rows.get.apply(0, 0) === 0 + }) + + } + + "login using cleartext authentication" in { + + val configuration = new Configuration( + username = "postgres_cleartext", + password = Some("postgres_cleartext"), + port = DatabasePort, + database = DatabaseName + ) + + withHandler(configuration, { + handler => + val result = executeQuery(handler, "SELECT 0") + result.rows.get.apply(0, 0) === 0 + }) + + } + + "fail login using kerberos authentication" in { + + val configuration = new Configuration( + username = "postgres_kerberos", + password = Some("postgres_kerberos"), + port = DatabasePort, + database = DatabaseName + ) + + withHandler(configuration, { + handler => + executeQuery(handler, "SELECT 0") + }) must throwAn[UnsupportedAuthenticationMethodException] + + } + + "fail login using with an invalid credential exception" in { + + val configuration = new Configuration( + username = "postgres_md5", + password = Some("postgres_kerberos"), + port = DatabasePort, + database = DatabaseName + ) + try { + withHandler(configuration, { + handler => + executeQuery(handler, "SELECT 0") + failure("should not have come here") + }) + } catch { + case e : DatabaseException => { + e.errorMessage.fields(InformationMessage.Routine) === "auth_failed" + } + case e : Exception => { + failure("should not have come here") + } + } + + } + } } diff --git a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala b/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala index 037950f1..b426006a 100644 --- a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala @@ -1,8 +1,9 @@ package com.github.mauricio.postgresql -import messages.backend.{ErrorMessage, Message} +import messages.backend.ErrorMessage import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers +import org.jboss.netty.util.CharsetUtil /** * User: Maurício Linhares @@ -33,7 +34,7 @@ class MessageDecoderSpec extends Specification { buffer.writeByte('R') buffer.writeInt( 30 ) - buffer.writeBytes( "my-name".getBytes(CharsetHelper.Unicode) ) + buffer.writeBytes( "my-name".getBytes( CharsetUtil.UTF_8 ) ) List( this.decoder.decode( null, null, buffer ) must beNull, @@ -45,7 +46,7 @@ class MessageDecoderSpec extends Specification { val buffer = ChannelBuffers.dynamicBuffer() val text = "This is an error message" - val textBytes = text.getBytes( CharsetHelper.Unicode ) + val textBytes = text.getBytes( CharsetUtil.UTF_8 ) buffer.writeByte('E') buffer.writeInt( textBytes.length + 4 + 1 ) From e5e53893285d474a3bfa7b28497039d08020b000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Sun, 31 Mar 2013 23:49:33 -0300 Subject: [PATCH 008/357] Update README.markdown Updating README --- README.markdown | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.markdown b/README.markdown index 7d88a933..199bc8f4 100644 --- a/README.markdown +++ b/README.markdown @@ -8,14 +8,13 @@ to PostgreSQL. ## What can it do now? -- connect to a database without authentication (it only connects if it gets an AuthenticationOk message) -- receive parameters +- connect to a database with or without authentication (supports MD5 and cleartext authentication methods) +- receive parameters database parameters - receive database notices - execute direct queries (without portals/prepared statements) -- parses all basic PostgreSQL types, other types are parsed as string -- date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects - portals/prepared statements -- MD5 and cleartext password authentication methods +- parses all basic (non-array) PostgreSQL types, other types are parsed as string +- date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects ## What is missing? @@ -23,6 +22,7 @@ to PostgreSQL. - more authentication mechanisms - benchmarks and more testing - timeout handler for initial handshare and queries +- array types support ## What are the design goals? From 0b33b301a6678cb7c8b5442869f66f0ac8311a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Sun, 31 Mar 2013 23:51:30 -0300 Subject: [PATCH 009/357] Update README.markdown README again --- README.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 199bc8f4..b1d72137 100644 --- a/README.markdown +++ b/README.markdown @@ -9,12 +9,13 @@ to PostgreSQL. ## What can it do now? - connect to a database with or without authentication (supports MD5 and cleartext authentication methods) -- receive parameters database parameters +- receive database parameters - receive database notices - execute direct queries (without portals/prepared statements) - portals/prepared statements - parses all basic (non-array) PostgreSQL types, other types are parsed as string - date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects +- all work is done using the new `scala.concurrent.Future` and `scala.concurrent.Promise` objects ## What is missing? From 8670ffb3d69a3c4c72340224dfc415889936e3be Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 4 Apr 2013 00:25:56 -0300 Subject: [PATCH 010/357] Making charsets configurable, but still defaults to UTF_8 --- .../mauricio/postgresql/parsers/Decoder.scala | 10 +++ .../mauricio/postgresql/util/MD5Digest.scala | 62 ------------------- .../postgresql/util/ThreadHelpers.scala | 24 ------- 3 files changed, 10 insertions(+), 86 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/util/ThreadHelpers.scala diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala new file mode 100644 index 00000000..399aff56 --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala @@ -0,0 +1,10 @@ +package com.github.mauricio.postgresql.parsers + +/** + * User: mauricio + * Date: 4/4/13 + * Time: 12:12 AM + */ +class Decoder { + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala b/src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala deleted file mode 100644 index 50dcef2f..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/util/MD5Digest.scala +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.mauricio.postgresql.util - -import java.security._ -import org.jboss.netty.util.CharsetUtil - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 6:19 PM - * - * Copied over and translated to Scala from org.postgresql.util.MD5Digest from the PostgreSQL JDBC driver. - * - */ -object MD5Digest { - - val Lookup = Array[Byte]( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') - - def encode( username : String, password : String, salt : Array[Byte] ) : Array[Byte] = { - - val usernameBytes = username.getBytes(CharsetUtil.UTF_8) - val passwordBytes = password.getBytes(CharsetUtil.UTF_8) - val hexDigest = new Array[Byte](35) - - val messageDigest = MessageDigest.getInstance("MD5") - - messageDigest.update(usernameBytes) - messageDigest.update(passwordBytes) - - val tempDigest = messageDigest.digest() - - bytesToHex(tempDigest, hexDigest, 0 ) - - messageDigest.update(hexDigest, 0, 32) - messageDigest.update(salt) - - val passwordDigest = messageDigest.digest() - - bytesToHex(passwordDigest, hexDigest, 3) - hexDigest(0) = 'm' - hexDigest(1) = 'd' - hexDigest(2) = '5' - - hexDigest - } - - private def bytesToHex( bytes : Array[Byte], hex : Array[Byte], offset : Int) { - var pos = offset - var i = 0 - - while ( i < 16 ) { - val c = bytes(i) & 0xFF - var j = c >> 4 - pos += 1 - hex(pos) = Lookup(j) - j = (c & 0xF) - pos += 1 - hex(pos) = Lookup(j) - i += 1 - } - } - -} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/util/ThreadHelpers.scala b/src/main/scala/com/github/mauricio/postgresql/util/ThreadHelpers.scala deleted file mode 100644 index 864bf564..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/util/ThreadHelpers.scala +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.mauricio.postgresql.util - -/** - * User: Maurício Linhares - * Date: 3/10/12 - * Time: 10:06 AM - */ - -object ThreadHelpers { - - def safeSleep( milis : Long ) { - - val current = System.currentTimeMillis() - var slept = false - while ( !slept ) { - Thread.sleep(milis) - if ( (current + milis) <= System.currentTimeMillis() ) { - slept = true - } - } - - } - -} From 595c3e82c3d0e7d5249c5837de74a773fa6a0905 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 4 Apr 2013 01:14:20 -0300 Subject: [PATCH 011/357] Added support for setting the connection encoding and encoders/decoders for Java date objects --- .../postgresql/util/PostgreSQLMD5Digest.java | 9 +++--- .../mauricio/postgresql/ChannelUtils.scala | 9 +++--- .../mauricio/postgresql/CharsetHelper.scala | 5 +-- .../mauricio/postgresql/Configuration.scala | 17 ++++++---- .../DatabaseConnectionHandler.scala | 11 ++++--- .../mauricio/postgresql/MessageDecoder.scala | 14 +++++--- .../mauricio/postgresql/MessageEncoder.scala | 13 ++++---- .../mauricio/postgresql/MutableQuery.scala | 7 ++-- .../column/ColumnEncoderDecoder.scala | 32 ++++++++++++++----- .../column/DateEncoderDecoder.scala | 13 +++++--- .../column/StringEncoderDecoder.scala | 4 +-- .../column/TimeEncoderDecoder.scala | 10 ++++-- .../TimeWithTimezoneEncoderDecoder.scala | 12 ++----- .../column/TimestampEncoderDecoder.scala | 25 +++++++++++---- .../TimestampWithTimezoneEncoderDecoder.scala | 13 ++------ .../encoders/CredentialEncoder.scala | 12 ++++--- .../ExecutePreparedStatementEncoder.scala | 9 +++--- .../PreparedStatementOpeningEncoder.scala | 7 ++-- .../encoders/QueryMessageEncoder.scala | 5 +-- .../encoders/StartupMessageEncoder.scala | 11 ++++--- .../DateEncoderNotAvailableException.scala | 9 ++++++ .../parsers/AuthenticationStartupParser.scala | 2 +- .../parsers/BackendKeyDataParser.scala | 2 +- .../parsers/CommandCompleteParser.scala | 5 +-- .../postgresql/parsers/DataRowParser.scala | 2 +- .../mauricio/postgresql/parsers/Decoder.scala | 7 +++- .../postgresql/parsers/ErrorParser.scala | 3 +- .../parsers/InformationParser.scala | 5 +-- .../postgresql/parsers/MessageParser.scala | 21 +++++------- .../postgresql/parsers/NoticeParser.scala | 3 +- .../parsers/ParameterStatusParser.scala | 5 +-- .../parsers/ReadyForQueryParser.scala | 2 +- .../parsers/ReturningMessageParser.scala | 2 +- .../parsers/RowDescriptionParser.scala | 5 +-- .../DatabaseConnectionHandlerSpec.scala | 4 +-- .../postgresql/MessageDecoderSpec.scala | 2 +- .../postgresql/parsers/ParserESpec.scala | 3 +- .../postgresql/parsers/ParserSSpec.scala | 3 +- 38 files changed, 193 insertions(+), 130 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/DateEncoderNotAvailableException.scala diff --git a/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java b/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java index d9841067..7ed8689c 100644 --- a/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java +++ b/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java @@ -9,8 +9,7 @@ */ package com.github.mauricio.postgresql.util; -import org.jboss.netty.util.CharsetUtil; - +import java.nio.charset.Charset; import java.security.MessageDigest; /** @@ -39,10 +38,10 @@ private PostgreSQLMD5Digest() * * @return A 35-byte array, comprising the string "md5" and an MD5 digest. */ - public static byte[] encode(String userStr, String passwordStr, byte[] salt) + public static byte[] encode(String userStr, String passwordStr, byte[] salt, Charset charset) { - byte[] user = userStr.getBytes(CharsetUtil.UTF_8); - byte[] password = passwordStr.getBytes(CharsetUtil.UTF_8); + byte[] user = userStr.getBytes( charset ); + byte[] password = passwordStr.getBytes( charset ); MessageDigest md; byte[] temp_digest, pass_digest; diff --git a/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala b/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala index 4014181f..811b5a63 100644 --- a/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala +++ b/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala @@ -3,6 +3,7 @@ package com.github.mauricio.postgresql import org.jboss.netty.buffer.ChannelBuffer import util.Log import org.jboss.netty.util.CharsetUtil +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -36,12 +37,12 @@ object ChannelUtils { } - def writeCString( content : String, b : ChannelBuffer ) : Unit = { - b.writeBytes( content.getBytes( CharsetUtil.UTF_8 ) ) + def writeCString( content : String, b : ChannelBuffer, charset : Charset ) : Unit = { + b.writeBytes( content.getBytes( charset ) ) b.writeByte(0) } - def readCString( b : ChannelBuffer ) : String = { + def readCString( b : ChannelBuffer, charset : Charset ) : String = { b.markReaderIndex() @@ -55,7 +56,7 @@ object ChannelUtils { b.resetReaderIndex() - val result = b.toString( b.readerIndex(), count - 1, CharsetUtil.UTF_8 ) + val result = b.toString( b.readerIndex(), count - 1, charset ) b.readerIndex( b.readerIndex() + count) diff --git a/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala b/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala index f5f2be8f..02e50889 100644 --- a/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala +++ b/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala @@ -1,6 +1,7 @@ package com.github.mauricio.postgresql import org.jboss.netty.util.CharsetUtil +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -10,8 +11,8 @@ import org.jboss.netty.util.CharsetUtil object CharsetHelper { - def toBytes( content : String ) : Array[Byte] = { - content.getBytes( CharsetUtil.UTF_8 ) + def toBytes( content : String, charset : Charset ) : Array[Byte] = { + content.getBytes( charset ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/Configuration.scala b/src/main/scala/com/github/mauricio/postgresql/Configuration.scala index a092675c..d58f10cf 100644 --- a/src/main/scala/com/github/mauricio/postgresql/Configuration.scala +++ b/src/main/scala/com/github/mauricio/postgresql/Configuration.scala @@ -1,6 +1,8 @@ package com.github.mauricio.postgresql import java.util.concurrent.ExecutorService +import java.nio.charset.Charset +import org.jboss.netty.util.CharsetUtil /** * User: mauricio @@ -13,11 +15,12 @@ object Configuration { } -case class Configuration ( username : String, - host : String = "localhost", - port : Int = 5432, - password : Option[String] = None, - database : Option[String] = None, - bossPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool, - workerPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool +case class Configuration ( val username : String, + val host : String = "localhost", + val port : Int = 5432, + val password : Option[String] = None, + val database : Option[String] = None, + val bossPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool, + val workerPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool, + val charset : Charset = CharsetUtil.UTF_8 ) diff --git a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala index d6c484d2..1cb91956 100644 --- a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala @@ -32,7 +32,7 @@ class DatabaseConnectionHandler "user" -> configuration.username, "database" -> configuration.database, "application_name" -> DatabaseConnectionHandler.Name, - "client_encoding" -> "UTF8", + "client_encoding" -> configuration.charset.name() , "DateStyle" -> "ISO", "extra_float_digits" -> "2") @@ -62,7 +62,10 @@ class DatabaseConnectionHandler this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { override def getPipeline(): ChannelPipeline = { - Channels.pipeline(MessageDecoder, MessageEncoder, DatabaseConnectionHandler.this) + Channels.pipeline( + new MessageDecoder(configuration.charset), + new MessageEncoder( configuration.charset ), + DatabaseConnectionHandler.this) } }) @@ -225,7 +228,7 @@ class DatabaseConnectionHandler log.debug("MutableQuery is not parsed yet -> {}", realQuery) this.currentChannel.write(new PreparedStatementOpeningMessage(realQuery, values)) } else { - this.currentQuery = Some(new MutableQuery(this.parsedStatements.get(realQuery))) + this.currentQuery = Some(new MutableQuery(this.parsedStatements.get(realQuery), configuration.charset)) this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values)) } @@ -303,7 +306,7 @@ class DatabaseConnectionHandler private def onRowDescription(m: RowDescriptionMessage) { log.debug("received query description {}", m) - this.currentQuery = Option(new MutableQuery(m.columnDatas)) + this.currentQuery = Option(new MutableQuery(m.columnDatas, configuration.charset)) log.debug("Current prepared statement is {}", this.currentPreparedStatement) diff --git a/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala index 65cca944..81739f06 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala @@ -1,14 +1,20 @@ package com.github.mauricio.postgresql +import messages.backend.Message import org.jboss.netty.handler.codec.frame.FrameDecoder import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.parsers.{MessageParser, AuthenticationStartupParser} import org.jboss.netty.channel.{ChannelHandlerContext, Channel} import util.Log +import java.nio.charset.Charset -object MessageDecoder extends FrameDecoder { - +object MessageDecoder { val log = Log.getByName("MessageDecoder") +} + +class MessageDecoder ( charset : Charset ) extends FrameDecoder { + + private val parser = new MessageParser(charset) override def decode(ctx: ChannelHandlerContext, c: Channel, b: ChannelBuffer) : Object = { @@ -22,11 +28,11 @@ object MessageDecoder extends FrameDecoder { if ( b.readableBytes() >= length ) { code match { - case 'R' => { + case Message.Authentication => { AuthenticationStartupParser.parseMessage( b ) } case _ => { - MessageParser.parse( code, b.readSlice( length ) ) + parser.parse( code, b.readSlice( length ) ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala index 83bcb8ea..baa7d98b 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala @@ -7,6 +7,7 @@ import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.buffer.ChannelBuffer import util.Log +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -14,17 +15,17 @@ import util.Log * Time: 7:14 PM */ -object MessageEncoder extends OneToOneEncoder { +class MessageEncoder( charset : Charset ) extends OneToOneEncoder { val log = Log.getByName("MessageEncoder") val encoders : Map[Class[_],Encoder] = Map( classOf[CloseMessage] -> CloseMessageEncoder, - classOf[PreparedStatementExecuteMessage] -> ExecutePreparedStatementEncoder, - classOf[PreparedStatementOpeningMessage] -> PreparedStatementOpeningEncoder, - classOf[StartupMessage] -> StartupMessageEncoder, - classOf[QueryMessage] -> QueryMessageEncoder, - classOf[CredentialMessage] -> CredentialEncoder + classOf[PreparedStatementExecuteMessage] -> new ExecutePreparedStatementEncoder( charset ), + classOf[PreparedStatementOpeningMessage] -> new PreparedStatementOpeningEncoder( charset ), + classOf[StartupMessage] -> new StartupMessageEncoder(charset), + classOf[QueryMessage] -> new QueryMessageEncoder(charset), + classOf[CredentialMessage] -> new CredentialEncoder( charset ) ) override def encode(ctx: ChannelHandlerContext, channel: Channel, msg: AnyRef): ChannelBuffer = { diff --git a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala index 87521262..50de4a2a 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala @@ -5,6 +5,7 @@ import org.jboss.netty.buffer.ChannelBuffer import util.Log import collection.mutable.ArrayBuffer import org.jboss.netty.util.CharsetUtil +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -16,9 +17,7 @@ object MutableQuery { val log = Log.get[MutableQuery] } -class MutableQuery ( val columnTypes : Array[ColumnData] ) extends ResultSet { - - import CharsetHelper._ +class MutableQuery ( val columnTypes : Array[ColumnData], charset : Charset ) extends ResultSet { private val rows = new ArrayBuffer[Array[Any]]() private val columnMapping : Map[String, Int] = this.columnTypes.map { @@ -42,7 +41,7 @@ class MutableQuery ( val columnTypes : Array[ColumnData] ) extends ResultSet { realRow(index) = if ( row(index) == null ) { null } else { - this.columnTypes(index).decoder.decode( row(index).toString(CharsetUtil.UTF_8) ) + this.columnTypes(index).decoder.decode( row(index).toString( charset ) ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala index aedf54b2..d200b027 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala @@ -1,6 +1,7 @@ package com.github.mauricio.postgresql.column -import org.joda.time.{DateTime, LocalTime, LocalDate} +import org.joda.time._ +import scala.Some /** @@ -54,10 +55,18 @@ object ColumnEncoderDecoder { classOf[java.math.BigDecimal] -> Numeric, classOf[LocalDate] -> Date, - classOf[LocalTime] -> Time, - - classOf[DateTime] -> Timestamp + classOf[ReadablePartial] -> Time, + classOf[ReadableDateTime] -> Timestamp, + classOf[ReadableInstant] -> Date, + classOf[DateTime] -> Timestamp, + + classOf[java.util.Date] -> Timestamp, + classOf[java.sql.Date] -> Date, + classOf[java.sql.Time] -> Time, + classOf[java.sql.Timestamp] -> Timestamp, + classOf[java.util.Calendar] -> Timestamp, + classOf[java.util.GregorianCalendar] -> Timestamp ) def decoderFor(kind: Int): ColumnEncoderDecoder = { @@ -73,16 +82,22 @@ object ColumnEncoderDecoder { case Varchar => StringEncoderDecoder case Bpchar => StringEncoderDecoder case Numeric => BigDecimalEncoderDecoder - case Timestamp => TimestampEncoderDecoder + case Timestamp => TimestampEncoderDecoder.Instance case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder case Date => DateEncoderDecoder - case Time => TimeEncoderDecoder + case Time => TimeEncoderDecoder.Instance + case TimeWithTimezone => TimeWithTimezoneEncoderDecoder case _ => StringEncoderDecoder } } def kindFor( clazz : Class[_] ) : Int = { - this.classes.get(clazz).getOrElse { 0 } + this.classes.get(clazz).getOrElse { + this.classes.find( entry => entry._1.isAssignableFrom(clazz) ) match { + case Some(parent) => parent._2 + case None => 0 + } + } } def decode( kind : Int, value : String ) : Any = { @@ -90,7 +105,7 @@ object ColumnEncoderDecoder { } def encode( value : Any ) : String = { - decoderFor( kindFor( value.asInstanceOf[AnyRef].getClass ) ).encode(value) + decoderFor( kindFor( value.getClass ) ).encode(value) } } @@ -98,6 +113,7 @@ object ColumnEncoderDecoder { trait ColumnEncoderDecoder { def decode(value: String): Any + def encode(value: Any) : String = { value.toString } diff --git a/src/main/scala/com/github/mauricio/postgresql/column/DateEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/DateEncoderDecoder.scala index d7c340be..f41f5893 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/DateEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/DateEncoderDecoder.scala @@ -1,7 +1,8 @@ package com.github.mauricio.postgresql.column -import org.joda.time.LocalDate +import org.joda.time.{ReadableInstant, LocalDate} import org.joda.time.format.DateTimeFormat +import com.github.mauricio.postgresql.exceptions.DateEncoderNotAvailableException /** * User: Maurício Linhares @@ -11,14 +12,18 @@ import org.joda.time.format.DateTimeFormat object DateEncoderDecoder extends ColumnEncoderDecoder { - private val parser = DateTimeFormat.forPattern("yyyy-MM-dd") + private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd") override def decode(value: String): LocalDate = { - this.parser.parseLocalDate(value) + this.formatter.parseLocalDate(value) } override def encode( value : Any ) : String = { - this.parser.print( value.asInstanceOf[LocalDate] ) + value match { + case d : java.sql.Date => this.formatter.print( new LocalDate(d) ) + case d : ReadableInstant => this.formatter.print(d) + case _ => throw new DateEncoderNotAvailableException(value) + } } } diff --git a/src/main/scala/com/github/mauricio/postgresql/column/StringEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/StringEncoderDecoder.scala index 1298ed61..e5964274 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/StringEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/StringEncoderDecoder.scala @@ -7,7 +7,5 @@ package com.github.mauricio.postgresql.column */ object StringEncoderDecoder extends ColumnEncoderDecoder { - override def decode(value: String): String = { - value - } + override def decode(value: String): String = value } diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala index 271d82f7..92f9aff9 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala @@ -9,9 +9,15 @@ import org.joda.time.LocalTime * Time: 6:13 PM */ -object TimeEncoderDecoder extends ColumnEncoderDecoder { +object TimeEncoderDecoder { + val Instance = new TimeEncoderDecoder() +} + +class TimeEncoderDecoder extends ColumnEncoderDecoder { + + private val parser = DateTimeFormat.forPattern("HH:mm:ss.SSSSSS") - val parser = DateTimeFormat.forPattern("HH:mm:ss.SSSSSS") + def formatter = parser def decode(value: String): LocalTime = { parser.parseLocalTime(value) diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimeWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimeWithTimezoneEncoderDecoder.scala index 5e4179a5..39498c1e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimeWithTimezoneEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/TimeWithTimezoneEncoderDecoder.scala @@ -1,6 +1,5 @@ package com.github.mauricio.postgresql.column -import org.joda.time.LocalTime import org.joda.time.format.DateTimeFormat /** @@ -9,15 +8,10 @@ import org.joda.time.format.DateTimeFormat * Time: 5:35 PM */ -object TimeWithTimezoneEncoderDecoder extends ColumnEncoderDecoder { - private val parser = DateTimeFormat.forPattern("HH:mm:ss.SSSSSSZ") +object TimeWithTimezoneEncoderDecoder extends TimeEncoderDecoder { - override def decode(value: String): LocalTime = { - parser.parseLocalTime(value) - } + private val format = DateTimeFormat.forPattern("HH:mm:ss.SSSSSSZ") - override def encode( value : Any ) : String = { - this.parser.print( value.asInstanceOf[LocalTime] ) - } + override def formatter = format } diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala index fdb18e7b..ac728a77 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala @@ -1,7 +1,9 @@ package com.github.mauricio.postgresql.column import org.joda.time.format.DateTimeFormat -import org.joda.time.DateTime +import org.joda.time.{ReadableDateTime, DateTime} +import com.github.mauricio.postgresql.exceptions.DateEncoderNotAvailableException +import java.util.{Calendar, Date} /** * User: Maurício Linhares @@ -9,16 +11,27 @@ import org.joda.time.DateTime * Time: 6:10 PM */ -object TimestampEncoderDecoder extends ColumnEncoderDecoder { +object TimestampEncoderDecoder { + val Instance = new TimestampEncoderDecoder() +} + +class TimestampEncoderDecoder extends ColumnEncoderDecoder { + + private val format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") - private val parser = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") + def formatter = format - override def decode(value: String): DateTime = { - parser.parseDateTime(value) + override def decode(value: String): ReadableDateTime = { + formatter.parseDateTime(value) } override def encode( value : Any ) : String = { - this.parser.print( value.asInstanceOf[DateTime] ) + value match { + case t : Date => this.formatter.print( new DateTime(t) ) + case t : Calendar => this.formatter.print( new DateTime(t) ) + case t : ReadableDateTime => this.formatter.print(t) + case _ => throw new DateEncoderNotAvailableException(value) + } } } diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala index 9015102c..74086227 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala @@ -1,7 +1,6 @@ package com.github.mauricio.postgresql.column import org.joda.time.format.DateTimeFormat -import org.joda.time.DateTime /** * User: Maurício Linhares @@ -9,16 +8,10 @@ import org.joda.time.DateTime * Time: 9:27 AM */ -object TimestampWithTimezoneEncoderDecoder extends ColumnEncoderDecoder { +object TimestampWithTimezoneEncoderDecoder extends TimestampEncoderDecoder { - private val parser = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSSZ") + private val format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSSZ") - override def decode(value: String): DateTime = { - parser.parseDateTime(value) - } - - override def encode( value : Any ) : String = { - this.parser.print( value.asInstanceOf[DateTime] ) - } + override def formatter = format } diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala index ab3ed25d..623baafc 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala @@ -3,16 +3,16 @@ package com.github.mauricio.postgresql.encoders import com.github.mauricio.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} -import org.jboss.netty.util.CharsetUtil import com.github.mauricio.postgresql.util.PostgreSQLMD5Digest import com.github.mauricio.postgresql.ChannelUtils +import java.nio.charset.Charset /** * User: mauricio * Date: 3/31/13 * Time: 6:48 PM */ -object CredentialEncoder extends Encoder { +class CredentialEncoder( charset : Charset ) extends Encoder { def encode(message: FrontendMessage): ChannelBuffer = { @@ -20,10 +20,14 @@ object CredentialEncoder extends Encoder { val password = credentialMessage.authenticationType match { case AuthenticationResponseType.Cleartext => { - credentialMessage.password.getBytes(CharsetUtil.UTF_8) + credentialMessage.password.getBytes(charset) } case AuthenticationResponseType.MD5 => { - PostgreSQLMD5Digest.encode( credentialMessage.username, credentialMessage.password, credentialMessage.salt.get ) + PostgreSQLMD5Digest.encode( + credentialMessage.username, + credentialMessage.password, + credentialMessage.salt.get, + charset ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala index a11726cd..fe54ef72 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -5,7 +5,7 @@ import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} import com.github.mauricio.postgresql.column.ColumnEncoderDecoder import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} import com.github.mauricio.postgresql.messages.backend.Message -import org.jboss.netty.util.CharsetUtil +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -13,12 +13,13 @@ import org.jboss.netty.util.CharsetUtil * Time: 6:45 PM */ -object ExecutePreparedStatementEncoder extends Encoder { +class ExecutePreparedStatementEncoder ( charset : Charset ) extends Encoder { + def encode(message: FrontendMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] - val queryBytes = CharsetHelper.toBytes(m.query) + val queryBytes = CharsetHelper.toBytes(m.query, charset) val bindBuffer = ChannelBuffers.dynamicBuffer(1024) @@ -38,7 +39,7 @@ object ExecutePreparedStatementEncoder extends Encoder { if ( value == null ) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes( CharsetUtil.UTF_8 ) + val encoded = ColumnEncoderDecoder.encode(value).getBytes( charset ) bindBuffer.writeInt(encoded.length) bindBuffer.writeBytes( encoded ) } diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 8ff4fe7e..ed456a8c 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -6,6 +6,7 @@ import com.github.mauricio.postgresql.column.ColumnEncoderDecoder import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} import com.github.mauricio.postgresql.messages.backend.Message import org.jboss.netty.util.CharsetUtil +import java.nio.charset.Charset /** @@ -14,13 +15,13 @@ import org.jboss.netty.util.CharsetUtil * Time: 9:20 AM */ -object PreparedStatementOpeningEncoder extends Encoder { +class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { override def encode(message: FrontendMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementOpeningMessage] - val queryBytes = CharsetHelper.toBytes(m.query) + val queryBytes = CharsetHelper.toBytes(m.query, charset) val columnCount = m.valueTypes.size val parseBuffer = ChannelBuffers.dynamicBuffer( 1024 ) @@ -59,7 +60,7 @@ object PreparedStatementOpeningEncoder extends Encoder { if ( value == null ) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes( CharsetUtil.UTF_8 ) + val encoded = ColumnEncoderDecoder.encode(value).getBytes( charset ) bindBuffer.writeInt(encoded.length) bindBuffer.writeBytes( encoded ) } diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala index a871d901..6b43c112 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala @@ -4,6 +4,7 @@ import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, QueryMessage} import com.github.mauricio.postgresql.messages.backend.Message +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -11,7 +12,7 @@ import com.github.mauricio.postgresql.messages.backend.Message * Time: 8:32 PM */ -object QueryMessageEncoder extends Encoder { +class QueryMessageEncoder ( charset : Charset ) extends Encoder { override def encode(message: FrontendMessage): ChannelBuffer = { @@ -20,7 +21,7 @@ object QueryMessageEncoder extends Encoder { val buffer = ChannelBuffers.dynamicBuffer() buffer.writeByte( Message.Query ) buffer.writeInt(0) - ChannelUtils.writeCString(m.query, buffer) + ChannelUtils.writeCString(m.query, buffer, charset) ChannelUtils.writeLength( buffer ) diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala index 8857d3e6..04c81800 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala @@ -3,6 +3,7 @@ package com.github.mauricio.postgresql.encoders import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, StartupMessage} +import java.nio.charset.Charset /** @@ -11,7 +12,7 @@ import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, Startu * Time: 7:35 PM */ -object StartupMessageEncoder extends Encoder { +class StartupMessageEncoder (charset : Charset) extends Encoder { //private val log = Log.getByName("StartupMessageEncoder") @@ -28,12 +29,12 @@ object StartupMessageEncoder extends Encoder { pair => pair._2 match { case value : String => { - ChannelUtils.writeCString( pair._1, buffer ) - ChannelUtils.writeCString( value, buffer ) + ChannelUtils.writeCString( pair._1, buffer, charset ) + ChannelUtils.writeCString( value, buffer, charset ) } case Some(value) => { - ChannelUtils.writeCString( pair._1, buffer ) - ChannelUtils.writeCString( value.toString, buffer ) + ChannelUtils.writeCString( pair._1, buffer, charset ) + ChannelUtils.writeCString( value.toString, buffer, charset ) } case _ => {} } diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/DateEncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/DateEncoderNotAvailableException.scala new file mode 100644 index 00000000..7034f41c --- /dev/null +++ b/src/main/scala/com/github/mauricio/postgresql/exceptions/DateEncoderNotAvailableException.scala @@ -0,0 +1,9 @@ +package com.github.mauricio.postgresql.exceptions + +/** + * User: mauricio + * Date: 4/4/13 + * Time: 12:36 AM + */ +class DateEncoderNotAvailableException( value : Any ) + extends IllegalArgumentException( "There is no encoder for value [%s] of type %s".format(value, value.getClass.getCanonicalName) ) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala index 21bb3c1e..ccea7df9 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala @@ -4,7 +4,7 @@ import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.postgresql.messages.backend._ -object AuthenticationStartupParser extends MessageParser { +object AuthenticationStartupParser extends Decoder { val AuthenticationOk = 0 val AuthenticationKerberosV5 = 2 diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala index 55e05dd0..ce801472 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala @@ -9,7 +9,7 @@ import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} * Time: 11:13 PM */ -object BackendKeyDataParser extends MessageParser { +object BackendKeyDataParser extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { new ProcessData( b.readInt(), b.readInt() ) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala index 3d612561..5b195193 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala @@ -3,6 +3,7 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.backend.{CommandCompleteMessage, Message} +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -10,11 +11,11 @@ import com.github.mauricio.postgresql.messages.backend.{CommandCompleteMessage, * Time: 10:33 PM */ -object CommandCompleteParser extends MessageParser { +class CommandCompleteParser (charset : Charset) extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { - val result = ChannelUtils.readCString(b) + val result = ChannelUtils.readCString(b, charset) val possibleRowCount = result.substring(result.lastIndexOf(" ")) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala index ceb86fdf..88d08cb3 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala @@ -9,7 +9,7 @@ import com.github.mauricio.postgresql.messages.backend.{DataRowMessage, Message} * Time: 10:56 AM */ -object DataRowParser extends MessageParser { +object DataRowParser extends Decoder { def parseMessage(buffer: ChannelBuffer): Message = { diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala index 399aff56..f6012ed9 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala @@ -1,10 +1,15 @@ package com.github.mauricio.postgresql.parsers +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.postgresql.messages.backend.Message + /** * User: mauricio * Date: 4/4/13 * Time: 12:12 AM */ -class Decoder { +trait Decoder { + + def parseMessage(buffer: ChannelBuffer): Message } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala index d4baae68..cec43370 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala @@ -1,8 +1,9 @@ package com.github.mauricio.postgresql.parsers import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} +import java.nio.charset.Charset -object ErrorParser extends InformationParser { +class ErrorParser( charset : Charset ) extends InformationParser(charset) { def createMessage(fields: Map[Char, String]): Message = new ErrorMessage(fields) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala index ea3bb1bf..8fa37013 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala @@ -3,13 +3,14 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.messages.backend.Message import com.github.mauricio.postgresql.ChannelUtils +import java.nio.charset.Charset /** * User: mauricio * Date: 3/31/13 * Time: 12:54 AM */ -abstract class InformationParser extends MessageParser { +abstract class InformationParser( charset : Charset ) extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { @@ -21,7 +22,7 @@ abstract class InformationParser extends MessageParser { if ( kind != 0 ) { fields.put( kind.toChar, - ChannelUtils.readCString(b) + ChannelUtils.readCString(b, charset) ) } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala index 122fa5fc..708f263e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala @@ -3,25 +3,26 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.messages.backend.{CloseComplete, BindComplete, ParseComplete, Message} import com.github.mauricio.postgresql.exceptions.ParserNotAvailableException +import java.nio.charset.Charset -object MessageParser { +class MessageParser(charset : Charset) { private val parsers = Map( Message.Authentication -> AuthenticationStartupParser, Message.BackendKeyData -> BackendKeyDataParser, Message.BindComplete -> new ReturningMessageParser(BindComplete.Instance), Message.CloseComplete -> new ReturningMessageParser(CloseComplete.Instance), - Message.CommandComplete -> CommandCompleteParser, + Message.CommandComplete -> new CommandCompleteParser(charset), Message.DataRow -> DataRowParser, - Message.Error -> ErrorParser, - Message.Notice -> NoticeParser, - Message.ParameterStatus -> ParameterStatusParser, + Message.Error -> new ErrorParser(charset), + Message.Notice -> new NoticeParser(charset), + Message.ParameterStatus -> new ParameterStatusParser(charset), Message.ParseComplete -> new ReturningMessageParser(ParseComplete.Instance), - Message.RowDescription -> RowDescriptionParser, + Message.RowDescription -> new RowDescriptionParser(charset), Message.ReadyForQuery -> ReadyForQueryParser ) - def parserFor(t: Char): MessageParser = { + def parserFor(t: Char): Decoder = { val option = this.parsers.get(t) if ( option.isDefined ) { @@ -36,10 +37,4 @@ object MessageParser { this.parserFor(t).parseMessage(b) } -} - -trait MessageParser { - - def parseMessage(buffer: ChannelBuffer): Message - } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala index fb6ae34c..971bacfd 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala @@ -1,6 +1,7 @@ package com.github.mauricio.postgresql.parsers import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, Message} +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -8,7 +9,7 @@ import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, Message} * Time: 10:06 PM */ -object NoticeParser extends InformationParser { +class NoticeParser( charset : Charset ) extends InformationParser(charset) { def createMessage(fields: Map[Char, String]): Message = new NoticeMessage(fields) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala index e2f7aaf1..b6e2f508 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala @@ -3,6 +3,7 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -10,12 +11,12 @@ import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, * Time: 7:06 PM */ -object ParameterStatusParser extends MessageParser { +class ParameterStatusParser( charset : Charset ) extends Decoder { import ChannelUtils._ override def parseMessage(b: ChannelBuffer): Message = { - new ParameterStatusMessage( readCString(b), readCString(b) ) + new ParameterStatusMessage( readCString(b, charset), readCString(b, charset) ) } } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala index 0fc9b4e2..f9580417 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala @@ -9,7 +9,7 @@ import com.github.mauricio.postgresql.messages.backend.{ReadyForQueryMessage, Me * Time: 12:33 AM */ -object ReadyForQueryParser extends MessageParser { +object ReadyForQueryParser extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { new ReadyForQueryMessage( b.readByte().asInstanceOf[Char] ) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala index fea3a142..9a0fc82e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala @@ -9,7 +9,7 @@ import com.github.mauricio.postgresql.messages.backend.Message * Time: 11:36 PM */ -class ReturningMessageParser( val message : Message ) extends MessageParser { +class ReturningMessageParser( val message : Message ) extends Decoder { def parseMessage(buffer: ChannelBuffer): Message = { this.message } diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala index 99c9c29e..3a35acc0 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala @@ -3,6 +3,7 @@ package com.github.mauricio.postgresql.parsers import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} +import java.nio.charset.Charset /** * User: Maurício Linhares @@ -45,7 +46,7 @@ The format code being used for the field. Currently will be zero (text) or one ( */ -object RowDescriptionParser extends MessageParser { +class RowDescriptionParser (charset : Charset) extends Decoder { import ChannelUtils._ @@ -57,7 +58,7 @@ object RowDescriptionParser extends MessageParser { 0.until( columnsCount ).foreach { index => columns(index) = new ColumnData( - name = readCString( b ), + name = readCString( b, charset ), tableObjectId = b.readInt(), columnNumber = b.readShort(), dataType = b.readInt(), diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index d7cba848..941b6b81 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -163,9 +163,9 @@ class DatabaseConnectionHandlerSpec extends Specification { rows(6, 0) === 1, rows(7, 0) === "this is a varchar field", rows(8, 0) === "this is a long text field", - rows(9, 0) === TimestampEncoderDecoder.decode("1984-08-06 22:13:45.888888"), + rows(9, 0) === TimestampEncoderDecoder.Instance.decode("1984-08-06 22:13:45.888888"), rows(10, 0) === DateEncoderDecoder.decode("1984-08-06"), - rows(11, 0) === TimeEncoderDecoder.decode("22:13:45.888888"), + rows(11, 0) === TimeEncoderDecoder.Instance.decode("22:13:45.888888"), rows(12, 0) === true ) diff --git a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala b/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala index b426006a..1954aaef 100644 --- a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala @@ -13,7 +13,7 @@ import org.jboss.netty.util.CharsetUtil class MessageDecoderSpec extends Specification { - val decoder = MessageDecoder + val decoder = new MessageDecoder( CharsetUtil.UTF_8 ) "message decoder" should { diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala index 2c84c2e8..b1a6c9ab 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala @@ -3,6 +3,7 @@ package com.github.mauricio.postgresql.parsers import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} +import org.jboss.netty.util.CharsetUtil /** * User: Maurício Linhares @@ -21,7 +22,7 @@ class ParserESpec extends Specification { buffer.writeByte('M') buffer.writeBytes( error.getBytes ) - val message = ErrorParser.parseMessage( buffer ).asInstanceOf[ErrorMessage] + val message = new ErrorParser(CharsetUtil.UTF_8).parseMessage( buffer ).asInstanceOf[ErrorMessage] List(message.message === error, message.name === Message.Error) } diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala index 0c7229c3..f2f30899 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala @@ -4,6 +4,7 @@ import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers import java.nio.charset.Charset import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} +import org.jboss.netty.util.CharsetUtil /** * User: Maurício Linhares @@ -13,7 +14,7 @@ import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, class ParserSSpec extends Specification { - val parser = ParameterStatusParser + val parser = new ParameterStatusParser(CharsetUtil.UTF_8) "ParameterStatusParser" should { From 04b3800afad18ac03b6467b3472b3aba2014ff20 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 4 Apr 2013 01:16:45 -0300 Subject: [PATCH 012/357] Forgot java.sql.Timestamp on timestamp column decoder --- .../mauricio/postgresql/column/TimestampEncoderDecoder.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala index ac728a77..bec15b1c 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala @@ -4,6 +4,7 @@ import org.joda.time.format.DateTimeFormat import org.joda.time.{ReadableDateTime, DateTime} import com.github.mauricio.postgresql.exceptions.DateEncoderNotAvailableException import java.util.{Calendar, Date} +import java.sql.Timestamp /** * User: Maurício Linhares @@ -30,6 +31,7 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { case t : Date => this.formatter.print( new DateTime(t) ) case t : Calendar => this.formatter.print( new DateTime(t) ) case t : ReadableDateTime => this.formatter.print(t) + case t : Timestamp => this.formatter.print( new DateTime(t) ) case _ => throw new DateEncoderNotAvailableException(value) } } From 99a18c3a08e6ecf8a5f811dd29b6f2961192da6e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 4 Apr 2013 11:23:27 -0300 Subject: [PATCH 013/357] Adding missing method to Connection and updating README --- README.markdown | 4 ++-- .../scala/com/github/mauricio/postgresql/Connection.scala | 1 + .../mauricio/postgresql/column/TimestampEncoderDecoder.scala | 2 +- .../mauricio/postgresql/DatabaseConnectionHandlerSpec.scala | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.markdown b/README.markdown index b1d72137..1d026365 100644 --- a/README.markdown +++ b/README.markdown @@ -19,9 +19,9 @@ to PostgreSQL. ## What is missing? -- stored procedures - more authentication mechanisms -- benchmarks and more testing +- benchmarks +- more tests - timeout handler for initial handshare and queries - array types support diff --git a/src/main/scala/com/github/mauricio/postgresql/Connection.scala b/src/main/scala/com/github/mauricio/postgresql/Connection.scala index 5f918bcf..2f82a29e 100644 --- a/src/main/scala/com/github/mauricio/postgresql/Connection.scala +++ b/src/main/scala/com/github/mauricio/postgresql/Connection.scala @@ -12,6 +12,7 @@ import concurrent.Future trait Connection { def disconnect : Future[Connection] + def connect : Future[Map[String,String]] def isConnected : Boolean def sendQuery( query : String ) : Future[QueryResult] def sendPreparedStatement( query : String, values : Array[Any] = Array.empty[Any] ) : Future[QueryResult] diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala index bec15b1c..f2d38068 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala @@ -28,10 +28,10 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { override def encode( value : Any ) : String = { value match { + case t : Timestamp => this.formatter.print( new DateTime(t) ) case t : Date => this.formatter.print( new DateTime(t) ) case t : Calendar => this.formatter.print( new DateTime(t) ) case t : ReadableDateTime => this.formatter.print(t) - case t : Timestamp => this.formatter.print( new DateTime(t) ) case _ => throw new DateEncoderNotAvailableException(value) } } diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index 941b6b81..5ca94b4b 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -5,7 +5,8 @@ import exceptions.{DatabaseException, UnsupportedAuthenticationMethodException} import messages.backend.InformationMessage import org.specs2.mutable.Specification import scala.concurrent.duration._ -import concurrent.Await +import concurrent.{Future, Await} +import scala.util.{Failure, Success, Try} /** * User: Maurício Linhares From e23675e4e90bf7cc5413d43ce9752c31029c66e1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 5 Apr 2013 18:24:35 -0300 Subject: [PATCH 014/357] Adding example using flatMap and manual transactions, fixing command complete parser to check if there is a value before trying to parse it --- .../parsers/CommandCompleteParser.scala | 18 ++++++++----- .../DatabaseConnectionHandlerSpec.scala | 27 ++++++++++++++----- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala index 5b195193..454bfb6d 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala +++ b/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala @@ -17,13 +17,17 @@ class CommandCompleteParser (charset : Charset) extends Decoder { val result = ChannelUtils.readCString(b, charset) - val possibleRowCount = result.substring(result.lastIndexOf(" ")) - - val rowCount : Int = try { - possibleRowCount.toInt - } catch { - case e : NumberFormatException => { - 0 + val indexOfRowCount = result.lastIndexOf(" ") + + val rowCount = if ( indexOfRowCount == -1 ) { + 0 + } else { + try { + result.substring(indexOfRowCount).trim.toInt + } catch { + case e : NumberFormatException => { + 0 + } } } diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala index 5ca94b4b..4bee24d1 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala @@ -6,7 +6,7 @@ import messages.backend.InformationMessage import org.specs2.mutable.Specification import scala.concurrent.duration._ import concurrent.{Future, Await} -import scala.util.{Failure, Success, Try} +import scala.concurrent.ExecutionContext.Implicits.global /** * User: Maurício Linhares @@ -63,6 +63,10 @@ class DatabaseConnectionHandlerSpec extends Specification { val DatabaseName = Some("netty_driver_test") val DatabasePort = 5433 + val DefaultConfiguration = new Configuration( + port = DatabasePort, + username = "postgres", + database = DatabaseName) val select = "select * from type_test_table" @@ -78,11 +82,7 @@ class DatabaseConnectionHandlerSpec extends Specification { val preparedStatementSelect = "select * from prepared_statement_test" def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { - val configuration = new Configuration( - port = DatabasePort, - username = "postgres", - database = DatabaseName) - withHandler( configuration, fn ) + withHandler( DefaultConfiguration, fn ) } def withHandler[T]( configuration : Configuration, fn: (DatabaseConnectionHandler) => T): T = { @@ -298,6 +298,21 @@ class DatabaseConnectionHandlerSpec extends Specification { } + "transaction and flatmap example" in { + + val handler : Connection = new DatabaseConnectionHandler( DefaultConfiguration ) + val result: Future[QueryResult] = handler.connect + .map( parameters => handler ) + .flatMap( connection => connection.sendQuery("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ") ) + .flatMap( query => handler.sendQuery("SELECT 0") ) + .flatMap( query => handler.sendQuery("COMMIT").map( value => query ) ) + + val queryResult: QueryResult = Await.result(result, Duration(5, SECONDS)) + + queryResult.rows.get(0, 0) === 0 + + } + } } From 406a1127b2074dc1e7f98281410838f9d04d3730 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 6 Apr 2013 00:14:14 -0300 Subject: [PATCH 015/357] Updating licence to apache, moving classes into separate packages --- LICENCE.txt | 203 +++++++++++++++++- .../postgresql/util/PostgreSQLMD5Digest.java | 2 +- .../db}/Configuration.scala | 27 ++- .../github/mauricio/async/db/Connection.scala | 29 +++ .../db}/QueryResult.scala | 3 +- .../github/mauricio/async/db/ResultSet.scala | 25 +++ .../db}/postgresql/ChannelUtils.scala | 27 ++- .../DatabaseConnectionHandler.scala | 40 +++- .../db}/postgresql/MessageDecoder.scala | 26 ++- .../db}/postgresql/MessageEncoder.scala | 30 ++- .../db}/postgresql/MutableQuery.scala | 30 ++- .../column/BigDecimalEncoderDecoder.scala | 25 +++ .../column/BooleanEncoderDecoder.scala | 39 ++++ .../column/ColumnEncoderDecoder.scala | 23 +- .../column/DateEncoderDecoder.scala | 26 ++- .../column/DoubleEncoderDecoder.scala | 0 .../column/FloatEncoderDecoder.scala | 23 ++ .../column/IntegerEncoderDecoder.scala | 0 .../column/LongEncoderDecoder.scala | 23 ++ .../column/StringEncoderDecoder.scala | 0 .../column/TimeEncoderDecoder.scala | 40 ++++ .../TimeWithTimezoneEncoderDecoder.scala | 0 .../column/TimestampEncoderDecoder.scala | 28 ++- .../TimestampWithTimezoneEncoderDecoder.scala | 27 +++ .../encoders/CloseMessageEncoder.scala | 32 +++ .../encoders/CredentialEncoder.scala | 29 ++- .../db}/postgresql/encoders/Encoder.scala | 2 +- .../ExecutePreparedStatementEncoder.scala | 30 ++- .../PreparedStatementOpeningEncoder.scala | 32 +-- .../encoders/QueryMessageEncoder.scala | 6 +- .../encoders/StartupMessageEncoder.scala | 25 ++- .../exceptions/DatabaseException.scala | 22 ++ .../DateEncoderNotAvailableException.scala | 0 .../EncoderNotAvailableException.scala | 22 ++ ...issingCredentialInformationException.scala | 0 .../exceptions/NotConnectedException.scala | 19 ++ .../ParserNotAvailableException.scala | 20 ++ ...pportedAuthenticationMethodException.scala | 20 ++ ...henticationChallengeCleartextMessage.scala | 24 +++ .../backend/AuthenticationChallengeMD5.scala | 20 ++ .../AuthenticationChallengeMessage.scala | 22 ++ .../backend/AuthenticationMessage.scala | 0 .../backend/AuthenticationOkMessage.scala | 23 ++ .../backend/AuthenticationResponseType.scala | 22 ++ .../messages/backend/BindComplete.scala | 23 ++ .../messages/backend/CloseComplete.scala | 23 ++ .../messages/backend/ColumnData.scala | 32 +++ .../backend/CommandCompleteMessage.scala | 0 .../messages/backend/DataRowMessage.scala | 21 ++ .../messages/backend/ErrorMessage.scala | 0 .../messages/backend/InformationMessage.scala | 22 +- .../postgresql/messages/backend/Message.scala | 16 ++ .../messages/backend/NoticeMessage.scala | 20 ++ .../backend/ParameterStatusMessage.scala | 0 .../messages/backend/ParseComplete.scala | 23 ++ .../messages/backend/ProcessData.scala | 20 ++ .../backend/ReadyForQueryMessage.scala | 0 .../backend/RowDescriptionMessage.scala | 20 ++ .../messages/frontend/CloseMessage.scala | 25 +++ .../messages/frontend/CredentialMessage.scala | 27 +++ .../messages/frontend/FrontendMessage.scala | 19 ++ .../PreparedStatementExecuteMessage.scala | 22 ++ .../frontend/PreparedStatementMessage.scala | 33 +++ .../PreparedStatementOpeningMessage.scala | 22 ++ .../messages/frontend/QueryMessage.scala | 21 ++ .../messages/frontend/StartupMessage.scala | 21 ++ .../parsers/AuthenticationStartupParser.scala | 18 +- .../parsers/BackendKeyDataParser.scala | 28 +++ .../parsers/CommandCompleteParser.scala | 2 +- .../postgresql/parsers/DataRowParser.scala | 24 ++- .../async/db/postgresql/parsers/Decoder.scala | 26 +++ .../db/postgresql/parsers/ErrorParser.scala | 26 +++ .../parsers/InformationParser.scala | 4 +- .../postgresql/parsers/MessageParser.scala | 20 +- .../db/postgresql/parsers/NoticeParser.scala | 26 +++ .../parsers/ParameterStatusParser.scala | 32 +++ .../parsers/ReadyForQueryParser.scala | 28 +++ .../parsers/ReturningMessageParser.scala | 2 +- .../parsers/RowDescriptionParser.scala | 25 ++- .../pool/ConnectionObjectFactory.scala | 29 ++- .../db/postgresql/pool/ConnectionPool.scala | 36 ++++ .../async/db/util/DaemonThreadsFactory.scala | 29 +++ .../async/db/util/ExecutorServiceUtils.scala | 23 ++ .../{postgresql => async/db}/util/Log.scala | 2 +- .../mauricio/postgresql/CharsetHelper.scala | 18 -- .../mauricio/postgresql/Connection.scala | 20 -- .../postgresql/ExecutorServiceUtils.scala | 14 -- .../mauricio/postgresql/ResultSet.scala | 14 -- .../column/BigDecimalEncoderDecoder.scala | 15 -- .../column/BooleanEncoderDecoder.scala | 30 --- .../column/FloatEncoderDecoder.scala | 13 -- .../column/LongEncoderDecoder.scala | 13 -- .../column/TimeEncoderDecoder.scala | 30 --- .../TimestampWithTimezoneEncoderDecoder.scala | 17 -- .../encoders/CloseMessageEncoder.scala | 23 -- .../exceptions/DatabaseException.scala | 11 - .../EncoderNotAvailableException.scala | 12 -- .../exceptions/NotConnectedException.scala | 8 - .../ParserNotAvailableException.scala | 10 - ...pportedAuthenticationMethodException.scala | 9 - ...henticationChallengeCleartextMessage.scala | 14 -- .../backend/AuthenticationChallengeMD5.scala | 10 - .../AuthenticationChallengeMessage.scala | 11 - .../backend/AuthenticationOkMessage.scala | 13 -- .../backend/AuthenticationResponseType.scala | 11 - .../messages/backend/BindComplete.scala | 15 -- .../messages/backend/CloseComplete.scala | 14 -- .../messages/backend/ColumnData.scala | 22 -- .../messages/backend/DataRowMessage.scala | 10 - .../messages/backend/NoticeMessage.scala | 9 - .../messages/backend/ParseComplete.scala | 14 -- .../messages/backend/ProcessData.scala | 10 - .../backend/RowDescriptionMessage.scala | 9 - .../messages/frontend/CloseMessage.scala | 18 -- .../messages/frontend/CredentialMessage.scala | 16 -- .../messages/frontend/FrontendMessage.scala | 9 - .../PreparedStatementExecuteMessage.scala | 12 -- .../frontend/PreparedStatementMessage.scala | 23 -- .../PreparedStatementOpeningMessage.scala | 12 -- .../messages/frontend/QueryMessage.scala | 12 -- .../messages/frontend/StartupMessage.scala | 12 -- .../parsers/BackendKeyDataParser.scala | 18 -- .../mauricio/postgresql/parsers/Decoder.scala | 15 -- .../postgresql/parsers/ErrorParser.scala | 10 - .../postgresql/parsers/NoticeParser.scala | 16 -- .../parsers/ParameterStatusParser.scala | 22 -- .../parsers/ReadyForQueryParser.scala | 18 -- .../postgresql/pool/ConnectionPool.scala | 26 --- .../util/DaemonThreadsFactory.scala | 20 -- .../DatabaseConnectionHandlerSpec.scala | 27 ++- .../db}/postgresql/MessageDecoderSpec.scala | 24 ++- .../async/db/postgresql/TestUtils.scala | 29 +++ .../db}/postgresql/parsers/ParserESpec.scala | 26 ++- .../db}/postgresql/parsers/ParserKSpec.scala | 26 ++- .../db}/postgresql/parsers/ParserSSpec.scala | 28 ++- .../mauricio/postgresql/TestUtils.scala | 19 -- 136 files changed, 1815 insertions(+), 898 deletions(-) rename src/main/java/com/github/mauricio/{ => async/db}/postgresql/util/PostgreSQLMD5Digest.java (97%) rename src/main/scala/com/github/mauricio/{postgresql => async/db}/Configuration.scala (52%) create mode 100644 src/main/scala/com/github/mauricio/async/db/Connection.scala rename src/main/scala/com/github/mauricio/{postgresql => async/db}/QueryResult.scala (88%) create mode 100644 src/main/scala/com/github/mauricio/async/db/ResultSet.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/ChannelUtils.scala (63%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/DatabaseConnectionHandler.scala (91%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/MessageDecoder.scala (62%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/MessageEncoder.scala (68%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/MutableQuery.scala (67%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/column/ColumnEncoderDecoder.scala (89%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/column/DateEncoderDecoder.scala (53%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/column/DoubleEncoderDecoder.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/column/IntegerEncoderDecoder.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/column/StringEncoderDecoder.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/column/TimeWithTimezoneEncoderDecoder.scala (100%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/column/TimestampEncoderDecoder.scala (62%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/encoders/CredentialEncoder.scala (63%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/encoders/Encoder.scala (100%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/encoders/ExecutePreparedStatementEncoder.scala (74%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/encoders/PreparedStatementOpeningEncoder.scala (80%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/encoders/QueryMessageEncoder.scala (91%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/encoders/StartupMessageEncoder.scala (67%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/exceptions/DateEncoderNotAvailableException.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/exceptions/MissingCredentialInformationException.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/messages/backend/AuthenticationMessage.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/messages/backend/CommandCompleteMessage.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/messages/backend/ErrorMessage.scala (100%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/messages/backend/InformationMessage.scala (62%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/messages/backend/Message.scala (52%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/messages/backend/ParameterStatusMessage.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/messages/backend/ReadyForQueryMessage.scala (100%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/AuthenticationStartupParser.scala (64%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/CommandCompleteParser.scala (100%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/DataRowParser.scala (52%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/InformationParser.scala (100%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/MessageParser.scala (68%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/ReturningMessageParser.scala (100%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/RowDescriptionParser.scala (74%) rename src/main/scala/com/github/mauricio/{ => async/db}/postgresql/pool/ConnectionObjectFactory.scala (52%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala rename src/main/scala/com/github/mauricio/{postgresql => async/db}/util/Log.scala (87%) delete mode 100644 src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/Connection.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/ExecutorServiceUtils.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/ResultSet.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/column/BigDecimalEncoderDecoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/column/BooleanEncoderDecoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/column/FloatEncoderDecoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/column/LongEncoderDecoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/EncoderNotAvailableException.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/NotConnectedException.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/ParserNotAvailableException.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ColumnData.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/CloseMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/FrontendMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/QueryMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/pool/ConnectionPool.scala delete mode 100644 src/main/scala/com/github/mauricio/postgresql/util/DaemonThreadsFactory.scala rename src/test/scala/com/github/mauricio/{ => async/db}/postgresql/DatabaseConnectionHandlerSpec.scala (92%) rename src/test/scala/com/github/mauricio/{ => async/db}/postgresql/MessageDecoderSpec.scala (71%) create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala rename src/test/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/ParserESpec.scala (53%) rename src/test/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/ParserKSpec.scala (51%) rename src/test/scala/com/github/mauricio/{ => async/db}/postgresql/parsers/ParserSSpec.scala (63%) delete mode 100644 src/test/scala/com/github/mauricio/postgresql/TestUtils.scala diff --git a/LICENCE.txt b/LICENCE.txt index ebca2379..61ca0ac4 100644 --- a/LICENCE.txt +++ b/LICENCE.txt @@ -1,7 +1,202 @@ -Copyright (C) 2013 Maurício Linhares - mauricio (dot) linhares (at) gmail -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2013] [Maurício Linhares] + + Licensed 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. \ No newline at end of file diff --git a/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java b/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java similarity index 97% rename from src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java rename to src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java index 7ed8689c..37f4a0e6 100644 --- a/src/main/java/com/github/mauricio/postgresql/util/PostgreSQLMD5Digest.java +++ b/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java @@ -7,7 +7,7 @@ * *------------------------------------------------------------------------- */ -package com.github.mauricio.postgresql.util; +package com.github.mauricio.async.db.postgresql.util; import java.nio.charset.Charset; import java.security.MessageDigest; diff --git a/src/main/scala/com/github/mauricio/postgresql/Configuration.scala b/src/main/scala/com/github/mauricio/async/db/Configuration.scala similarity index 52% rename from src/main/scala/com/github/mauricio/postgresql/Configuration.scala rename to src/main/scala/com/github/mauricio/async/db/Configuration.scala index d58f10cf..fb760bba 100644 --- a/src/main/scala/com/github/mauricio/postgresql/Configuration.scala +++ b/src/main/scala/com/github/mauricio/async/db/Configuration.scala @@ -1,14 +1,25 @@ -package com.github.mauricio.postgresql +/* + * 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 -import java.util.concurrent.ExecutorService import java.nio.charset.Charset +import java.util.concurrent.ExecutorService import org.jboss.netty.util.CharsetUtil - -/** - * User: mauricio - * Date: 3/29/13 - * Time: 12:05 AM - */ +import util.ExecutorServiceUtils object Configuration { val Default = new Configuration("postgres") diff --git a/src/main/scala/com/github/mauricio/async/db/Connection.scala b/src/main/scala/com/github/mauricio/async/db/Connection.scala new file mode 100644 index 00000000..1ba6658a --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/Connection.scala @@ -0,0 +1,29 @@ +/* + * 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 + +import concurrent.Future + +trait Connection { + + def disconnect : Future[Connection] + def connect : Future[Map[String,String]] + def isConnected : Boolean + def sendQuery( query : String ) : Future[QueryResult] + def sendPreparedStatement( query : String, values : Array[Any] = Array.empty[Any] ) : Future[QueryResult] + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/QueryResult.scala b/src/main/scala/com/github/mauricio/async/db/QueryResult.scala similarity index 88% rename from src/main/scala/com/github/mauricio/postgresql/QueryResult.scala rename to src/main/scala/com/github/mauricio/async/db/QueryResult.scala index 89785ecc..efc77fbc 100644 --- a/src/main/scala/com/github/mauricio/postgresql/QueryResult.scala +++ b/src/main/scala/com/github/mauricio/async/db/QueryResult.scala @@ -1,4 +1,5 @@ -package com.github.mauricio.postgresql +package com.github.mauricio.async.db + /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala new file mode 100644 index 00000000..c9ae686b --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala @@ -0,0 +1,25 @@ +/* + * 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 + +trait ResultSet extends IndexedSeq[Array[Any]] { + + def apply( name : String, row : Int ) : Any + + def apply( column : Int, row : Int ) : Any + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/ChannelUtils.scala similarity index 63% rename from src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/ChannelUtils.scala index 811b5a63..391c8aba 100644 --- a/src/main/scala/com/github/mauricio/postgresql/ChannelUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/ChannelUtils.scala @@ -1,15 +1,24 @@ +/* + * 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.postgresql -import org.jboss.netty.buffer.ChannelBuffer -import util.Log -import org.jboss.netty.util.CharsetUtil +import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset - -/** - * User: Maurício Linhares - * Date: 3/1/12 - * Time: 2:02 AM - */ +import org.jboss.netty.buffer.ChannelBuffer object ChannelUtils { diff --git a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala similarity index 91% rename from src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 1cb91956..7e69cb53 100644 --- a/src/main/scala/com/github/mauricio/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -1,19 +1,39 @@ +/* + * 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.postgresql +import com.github.mauricio.async.db.util.{Log, ExecutorServiceUtils} +import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} +import concurrent.{Future, Promise} import exceptions.{MissingCredentialInformationException, DatabaseException, NotConnectedException} -import messages._ -import backend._ -import backend.ProcessData -import frontend._ -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory -import org.jboss.netty.bootstrap.ClientBootstrap -import org.jboss.netty.channel._ import java.net.InetSocketAddress -import scala.collection.JavaConversions._ -import util.Log import java.util.concurrent.ConcurrentHashMap +import messages.backend._ +import messages.backend.CommandCompleteMessage +import messages.backend.DataRowMessage +import messages.backend.ParameterStatusMessage +import messages.backend.ProcessData +import messages.backend.RowDescriptionMessage +import messages.frontend._ +import org.jboss.netty.bootstrap.ClientBootstrap +import org.jboss.netty.channel._ +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} -import concurrent.{Future, Promise} +import scala.collection.JavaConversions._ import scala.Some object DatabaseConnectionHandler { diff --git a/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala similarity index 62% rename from src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala index 81739f06..51d872f7 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MessageDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala @@ -1,15 +1,31 @@ +/* + * 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.postgresql +import com.github.mauricio.async.db.util.Log +import com.github.mauricio.postgresql.parsers.{MessageParser, AuthenticationStartupParser} +import java.nio.charset.Charset import messages.backend.Message -import org.jboss.netty.handler.codec.frame.FrameDecoder import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.parsers.{MessageParser, AuthenticationStartupParser} import org.jboss.netty.channel.{ChannelHandlerContext, Channel} -import util.Log -import java.nio.charset.Charset +import org.jboss.netty.handler.codec.frame.FrameDecoder object MessageDecoder { - val log = Log.getByName("MessageDecoder") + val log = Log.get[MessageDecoder] } class MessageDecoder ( charset : Charset ) extends FrameDecoder { diff --git a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala similarity index 68% rename from src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index baa7d98b..83d25a74 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -1,19 +1,29 @@ +/* + * 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.postgresql +import com.github.mauricio.async.db.util.Log import encoders._ import exceptions.EncoderNotAvailableException +import java.nio.charset.Charset import messages.frontend._ -import org.jboss.netty.handler.codec.oneone.OneToOneEncoder -import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.buffer.ChannelBuffer -import util.Log -import java.nio.charset.Charset - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 7:14 PM - */ +import org.jboss.netty.channel.{Channel, ChannelHandlerContext} +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder class MessageEncoder( charset : Charset ) extends OneToOneEncoder { diff --git a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala similarity index 67% rename from src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala index 50de4a2a..33f49cfb 100644 --- a/src/main/scala/com/github/mauricio/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala @@ -1,17 +1,27 @@ +/* + * 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.postgresql -import messages.backend.ColumnData -import org.jboss.netty.buffer.ChannelBuffer -import util.Log import collection.mutable.ArrayBuffer -import org.jboss.netty.util.CharsetUtil +import com.github.mauricio.async.db.ResultSet +import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset - -/** - * User: Maurício Linhares - * Date: 3/4/12 - * Time: 12:42 AM - */ +import messages.backend.ColumnData +import org.jboss.netty.buffer.ChannelBuffer object MutableQuery { val log = Log.get[MutableQuery] diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala new file mode 100644 index 00000000..d7f8a3ec --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala @@ -0,0 +1,25 @@ +/* + * 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.postgresql.column + +object BigDecimalEncoderDecoder extends ColumnEncoderDecoder { + + def decode( value : String ) : Any = { + BigDecimal(value) + } + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala new file mode 100644 index 00000000..162999d4 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala @@ -0,0 +1,39 @@ +/* + * 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.postgresql.column + +object BooleanEncoderDecoder extends ColumnEncoderDecoder { + + override def decode(value: String): Any = { + if ( "t" == value ) { + true + } else { + false + } + } + + override def encode( value : Any ) : String = { + val result = value.asInstanceOf[Boolean] + + if (result) { + "t" + } else { + "f" + } + } + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala similarity index 89% rename from src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala index d200b027..89b606bf 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/ColumnEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala @@ -1,15 +1,24 @@ +/* + * 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.postgresql.column import org.joda.time._ import scala.Some - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:34 AM - */ - class ColumnDecoderNotFoundException( kind : Int ) extends IllegalArgumentException( "There is no decoder available for kind %s".format(kind) ) diff --git a/src/main/scala/com/github/mauricio/postgresql/column/DateEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala similarity index 53% rename from src/main/scala/com/github/mauricio/postgresql/column/DateEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala index f41f5893..a9c60917 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/DateEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala @@ -1,14 +1,24 @@ +/* + * 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.postgresql.column -import org.joda.time.{ReadableInstant, LocalDate} -import org.joda.time.format.DateTimeFormat import com.github.mauricio.postgresql.exceptions.DateEncoderNotAvailableException - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 6:12 PM - */ +import org.joda.time.format.DateTimeFormat +import org.joda.time.{ReadableInstant, LocalDate} object DateEncoderDecoder extends ColumnEncoderDecoder { diff --git a/src/main/scala/com/github/mauricio/postgresql/column/DoubleEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/column/DoubleEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala new file mode 100644 index 00000000..492dec0b --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.postgresql.column + +object FloatEncoderDecoder extends ColumnEncoderDecoder { + def decode(value: String): Float = { + value.toFloat + } +} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/IntegerEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/column/IntegerEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala new file mode 100644 index 00000000..49ad3756 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.postgresql.column + +object LongEncoderDecoder extends ColumnEncoderDecoder { + def decode(value: String): Long = { + value.toLong + } +} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/StringEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/column/StringEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala new file mode 100644 index 00000000..78f9c78b --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala @@ -0,0 +1,40 @@ +/* + * 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.postgresql.column + +import org.joda.time.LocalTime +import org.joda.time.format.DateTimeFormat + +object TimeEncoderDecoder { + val Instance = new TimeEncoderDecoder() +} + +class TimeEncoderDecoder extends ColumnEncoderDecoder { + + private val parser = DateTimeFormat.forPattern("HH:mm:ss.SSSSSS") + + def formatter = parser + + def decode(value: String): LocalTime = { + parser.parseLocalTime(value) + } + + override def encode( value : Any ) : String = { + this.parser.print( value.asInstanceOf[LocalTime] ) + } + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimeWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/column/TimeWithTimezoneEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala similarity index 62% rename from src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala index f2d38068..5e34c31c 100644 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimestampEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala @@ -1,16 +1,26 @@ +/* + * 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.postgresql.column -import org.joda.time.format.DateTimeFormat -import org.joda.time.{ReadableDateTime, DateTime} import com.github.mauricio.postgresql.exceptions.DateEncoderNotAvailableException -import java.util.{Calendar, Date} import java.sql.Timestamp - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 6:10 PM - */ +import java.util.{Calendar, Date} +import org.joda.time.format.DateTimeFormat +import org.joda.time.{ReadableDateTime, DateTime} object TimestampEncoderDecoder { val Instance = new TimestampEncoderDecoder() diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala new file mode 100644 index 00000000..5c01f129 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala @@ -0,0 +1,27 @@ +/* + * 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.postgresql.column + +import org.joda.time.format.DateTimeFormat + +object TimestampWithTimezoneEncoderDecoder extends TimestampEncoderDecoder { + + private val format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSSZ") + + override def formatter = format + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala new file mode 100644 index 00000000..b70f8c37 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala @@ -0,0 +1,32 @@ +/* + * 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.postgresql.encoders + +import com.github.mauricio.postgresql.messages.frontend.FrontendMessage +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} + +object CloseMessageEncoder extends Encoder { + + override def encode(message: FrontendMessage): ChannelBuffer = { + val buffer = ChannelBuffers.buffer(5) + buffer.writeByte('X') + buffer.writeInt(4) + + buffer + } + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala similarity index 63% rename from src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index 623baafc..77a09e5d 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/CredentialEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -1,17 +1,28 @@ +/* + * 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.postgresql.encoders -import com.github.mauricio.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} -import com.github.mauricio.postgresql.util.PostgreSQLMD5Digest import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} +import com.github.mauricio.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} import java.nio.charset.Charset +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.postgresql.util.PostgreSQLMD5Digest -/** - * User: mauricio - * Date: 3/31/13 - * Time: 6:48 PM - */ class CredentialEncoder( charset : Charset ) extends Encoder { def encode(message: FrontendMessage): ChannelBuffer = { diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/Encoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/encoders/Encoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala index 3c9c974c..ab0e6f57 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/Encoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.encoders -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.messages.frontend.FrontendMessage +import org.jboss.netty.buffer.ChannelBuffer /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala similarity index 74% rename from src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index fe54ef72..2e1f2eb0 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -1,17 +1,27 @@ +/* + * 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.postgresql.encoders -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} +import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.column.ColumnEncoderDecoder -import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} import java.nio.charset.Charset - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 6:45 PM - */ +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} class ExecutePreparedStatementEncoder ( charset : Charset ) extends Encoder { @@ -19,7 +29,7 @@ class ExecutePreparedStatementEncoder ( charset : Charset ) extends Encoder { val m = message.asInstanceOf[PreparedStatementExecuteMessage] - val queryBytes = CharsetHelper.toBytes(m.query, charset) + val queryBytes = m.query.getBytes(charset) val bindBuffer = ChannelBuffers.dynamicBuffer(1024) diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala similarity index 80% rename from src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index ed456a8c..f0609dbc 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -1,19 +1,27 @@ +/* + * 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.postgresql.encoders -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} +import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.column.ColumnEncoderDecoder -import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} import com.github.mauricio.postgresql.messages.backend.Message -import org.jboss.netty.util.CharsetUtil +import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} import java.nio.charset.Charset - - -/** - * User: Maurício Linhares - * Date: 3/7/12 - * Time: 9:20 AM - */ +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { @@ -21,7 +29,7 @@ class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { val m = message.asInstanceOf[PreparedStatementOpeningMessage] - val queryBytes = CharsetHelper.toBytes(m.query, charset) + val queryBytes = m.query.getBytes(charset) val columnCount = m.valueTypes.size val parseBuffer = ChannelBuffers.dynamicBuffer( 1024 ) diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala similarity index 91% rename from src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index 6b43c112..19c185af 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/QueryMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -1,10 +1,10 @@ package com.github.mauricio.postgresql.encoders -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.{ChannelUtils, CharsetHelper} -import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, QueryMessage} +import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, QueryMessage} import java.nio.charset.Charset +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala similarity index 67% rename from src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index 04c81800..29d62349 100644 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/StartupMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -1,16 +1,25 @@ +/* + * 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.postgresql.encoders -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, StartupMessage} import java.nio.charset.Charset - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 7:35 PM - */ +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} class StartupMessageEncoder (charset : Charset) extends Encoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala new file mode 100644 index 00000000..95456d7a --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala @@ -0,0 +1,22 @@ +/* + * 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.postgresql.exceptions + +import com.github.mauricio.postgresql.messages.backend.ErrorMessage + +class DatabaseException( val errorMessage : ErrorMessage ) + extends IllegalStateException( errorMessage.toString ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/DateEncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/exceptions/DateEncoderNotAvailableException.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala new file mode 100644 index 00000000..ba2c9dcf --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala @@ -0,0 +1,22 @@ +/* + * 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.postgresql.exceptions + +import com.github.mauricio.postgresql.messages.frontend.FrontendMessage + +class EncoderNotAvailableException( message : FrontendMessage ) + extends IllegalArgumentException( "Encoder not available for name %s".format( message.kind ) ) diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/exceptions/MissingCredentialInformationException.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala new file mode 100644 index 00000000..cd2672b3 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala @@ -0,0 +1,19 @@ +/* + * 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.postgresql.exceptions + +class NotConnectedException ( message : String ) extends IllegalStateException (message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala new file mode 100644 index 00000000..9b4a5c9c --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala @@ -0,0 +1,20 @@ +/* + * 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.postgresql.exceptions + +class ParserNotAvailableException(t: Char) + extends RuntimeException("There is no parser available for message type '%s'".format(t)) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala new file mode 100644 index 00000000..3397a432 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala @@ -0,0 +1,20 @@ +/* + * 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.postgresql.exceptions + +class UnsupportedAuthenticationMethodException ( val authenticationType : Int ) + extends IllegalArgumentException ( "Unknown authentication method -> '%s'".format(authenticationType) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala new file mode 100644 index 00000000..3fd2c4eb --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala @@ -0,0 +1,24 @@ +/* + * 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.postgresql.messages.backend + +object AuthenticationChallengeCleartextMessage { + val Instance = new AuthenticationChallengeCleartextMessage() +} + +class AuthenticationChallengeCleartextMessage + extends AuthenticationChallengeMessage( AuthenticationResponseType.Cleartext, None ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala new file mode 100644 index 00000000..b8ef1b2d --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala @@ -0,0 +1,20 @@ +/* + * 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.postgresql.messages.backend + +class AuthenticationChallengeMD5( salt : Array[Byte] ) + extends AuthenticationChallengeMessage( AuthenticationResponseType.MD5, Some(salt) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala new file mode 100644 index 00000000..b234eb4d --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala @@ -0,0 +1,22 @@ +/* + * 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.postgresql.messages.backend + +class AuthenticationChallengeMessage ( + val challengeType : AuthenticationResponseType.AuthenticationResponseType, + val salt : Option[Array[Byte]] ) + extends AuthenticationMessage \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationMessage.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala new file mode 100644 index 00000000..3ee8d7bd --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala @@ -0,0 +1,23 @@ +/* + * 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.postgresql.messages.backend + +object AuthenticationOkMessage { + val Instance = new AuthenticationOkMessage() +} + +class AuthenticationOkMessage extends AuthenticationMessage diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala new file mode 100644 index 00000000..718a89f2 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala @@ -0,0 +1,22 @@ +/* + * 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.postgresql.messages.backend + +object AuthenticationResponseType extends Enumeration { + type AuthenticationResponseType = Value + val MD5, Cleartext, Ok = Value +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala new file mode 100644 index 00000000..316e9d05 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala @@ -0,0 +1,23 @@ +/* + * 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.postgresql.messages.backend + +object BindComplete { + val Instance = new BindComplete() +} + +class BindComplete extends Message( Message.BindComplete ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala new file mode 100644 index 00000000..139e4af2 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala @@ -0,0 +1,23 @@ +/* + * 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.postgresql.messages.backend + +object CloseComplete { + val Instance = new CloseComplete() +} + +class CloseComplete extends Message(Message.CloseComplete) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala new file mode 100644 index 00000000..f9c7fefd --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala @@ -0,0 +1,32 @@ +/* + * 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.postgresql.messages.backend + +import com.github.mauricio.postgresql.column.ColumnEncoderDecoder + +class ColumnData( + val name: String, + val tableObjectId: Int, + val columnNumber: Int, + val dataType: Int, + val dataTypeSize: Int, + val dataTypeModifier: Int, + val fieldFormat: Int ) { + + val decoder = ColumnEncoderDecoder.decoderFor( this.dataType ) + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/CommandCompleteMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/messages/backend/CommandCompleteMessage.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala new file mode 100644 index 00000000..073b1d09 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala @@ -0,0 +1,21 @@ +/* + * 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.postgresql.messages.backend + +import org.jboss.netty.buffer.ChannelBuffer + +case class DataRowMessage( val values : Array[ChannelBuffer] ) extends Message( Message.DataRow ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/messages/backend/ErrorMessage.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala similarity index 62% rename from src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala index 2e35864a..269432d7 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/InformationMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala @@ -1,11 +1,21 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 12:42 AM +/* + * 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.postgresql.messages.backend + object InformationMessage { val Severity = 'S' diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala similarity index 52% rename from src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala index ad95bebf..f3c0d137 100644 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/Message.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala @@ -1,3 +1,19 @@ +/* + * 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.postgresql.messages.backend object Message { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala new file mode 100644 index 00000000..55a2dcc7 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.postgresql.messages.backend + +class NoticeMessage ( fields : Map[Char,String] ) + extends InformationMessage( Message.Notice, fields ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParameterStatusMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/messages/backend/ParameterStatusMessage.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala new file mode 100644 index 00000000..0a0e02bd --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala @@ -0,0 +1,23 @@ +/* + * 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.postgresql.messages.backend + +object ParseComplete { + val Instance = new ParseComplete() +} + +class ParseComplete extends Message(Message.ParseComplete) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala new file mode 100644 index 00000000..5dbf7105 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala @@ -0,0 +1,20 @@ +/* + * 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.postgresql.messages.backend + +case class ProcessData ( val processId : Int, val secretKey : Int ) + extends Message( Message.BackendKeyData ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ReadyForQueryMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/messages/backend/ReadyForQueryMessage.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala new file mode 100644 index 00000000..a22493ab --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.postgresql.messages.backend + +case class RowDescriptionMessage ( val columnDatas : Array[ColumnData] ) + extends Message( Message.RowDescription ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala new file mode 100644 index 00000000..86c64c62 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala @@ -0,0 +1,25 @@ +/* + * 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.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.Message + +object CloseMessage { + val Instance = new CloseMessage() +} + +class CloseMessage extends FrontendMessage( Message.Close ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala new file mode 100644 index 00000000..bf4e9455 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala @@ -0,0 +1,27 @@ +/* + * 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.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} + +class CredentialMessage( + val username : String, + val password : String, + val authenticationType : AuthenticationResponseType.AuthenticationResponseType, + val salt : Option[Array[Byte]] + ) + extends FrontendMessage( Message.PasswordMessage ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala new file mode 100644 index 00000000..91974ca0 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala @@ -0,0 +1,19 @@ +/* + * 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.postgresql.messages.frontend + +class FrontendMessage ( val kind : Char ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala new file mode 100644 index 00000000..7d2e28de --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -0,0 +1,22 @@ +/* + * 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.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.Message + +class PreparedStatementExecuteMessage( query : String, values : Seq[Any] ) + extends PreparedStatementMessage(Message.Execute, query, values) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala new file mode 100644 index 00000000..9c135f91 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -0,0 +1,33 @@ +/* + * 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.postgresql.messages.frontend + +import com.github.mauricio.postgresql.column.ColumnEncoderDecoder + +class PreparedStatementMessage( kind : Char, val query : String, val values : Seq[Any] ) extends FrontendMessage(kind) { + + val valueTypes : Seq[Int] = values.map { + value => + if ( value == null ) { + 0 + } else { + ColumnEncoderDecoder.kindFor(value.asInstanceOf[AnyRef].getClass) + } + + } + +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala new file mode 100644 index 00000000..919f6966 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -0,0 +1,22 @@ +/* + * 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.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.Message + +class PreparedStatementOpeningMessage( query : String, values : Seq[Any] ) + extends PreparedStatementMessage(Message.Parse, query, values) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala new file mode 100644 index 00000000..55e46bb3 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala @@ -0,0 +1,21 @@ +/* + * 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.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.Message + +class QueryMessage( val query : String ) extends FrontendMessage( Message.Query ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala new file mode 100644 index 00000000..ac108e60 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala @@ -0,0 +1,21 @@ +/* + * 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.postgresql.messages.frontend + +import com.github.mauricio.postgresql.messages.backend.Message + +class StartupMessage ( val parameters : List[(String, Any)] ) extends FrontendMessage(Message.Startup) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala similarity index 64% rename from src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index ccea7df9..e065be05 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/AuthenticationStartupParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -1,8 +1,24 @@ +/* + * 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.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.postgresql.messages.backend._ +import org.jboss.netty.buffer.ChannelBuffer object AuthenticationStartupParser extends Decoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala new file mode 100644 index 00000000..f4a87e65 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala @@ -0,0 +1,28 @@ +/* + * 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.postgresql.parsers + +import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} +import org.jboss.netty.buffer.ChannelBuffer + +object BackendKeyDataParser extends Decoder { + + override def parseMessage(b: ChannelBuffer): Message = { + new ProcessData( b.readInt(), b.readInt() ) + } + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 454bfb6d..1986a4d7 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/CommandCompleteParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -1,9 +1,9 @@ package com.github.mauricio.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.backend.{CommandCompleteMessage, Message} import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala similarity index 52% rename from src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala index 88d08cb3..49c639b6 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/DataRowParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala @@ -1,13 +1,23 @@ +/* + * 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.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.messages.backend.{DataRowMessage, Message} - -/** - * User: Maurício Linhares - * Date: 3/4/12 - * Time: 10:56 AM - */ +import org.jboss.netty.buffer.ChannelBuffer object DataRowParser extends Decoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala new file mode 100644 index 00000000..2e98d123 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala @@ -0,0 +1,26 @@ +/* + * 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.postgresql.parsers + +import com.github.mauricio.postgresql.messages.backend.Message +import org.jboss.netty.buffer.ChannelBuffer + +trait Decoder { + + def parseMessage(buffer: ChannelBuffer): Message + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala new file mode 100644 index 00000000..c245fa95 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala @@ -0,0 +1,26 @@ +/* + * 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.postgresql.parsers + +import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} +import java.nio.charset.Charset + +class ErrorParser( charset : Charset ) extends InformationParser(charset) { + + def createMessage(fields: Map[Char, String]): Message = new ErrorMessage(fields) + +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 8fa37013..376d5166 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/InformationParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -1,9 +1,9 @@ package com.github.mauricio.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.messages.backend.Message import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.backend.Message import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer /** * User: mauricio diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala similarity index 68% rename from src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala index 708f263e..db3d67a3 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/MessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala @@ -1,9 +1,25 @@ +/* + * 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.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.messages.backend.{CloseComplete, BindComplete, ParseComplete, Message} import com.github.mauricio.postgresql.exceptions.ParserNotAvailableException +import com.github.mauricio.postgresql.messages.backend.{CloseComplete, BindComplete, ParseComplete, Message} import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer class MessageParser(charset : Charset) { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala new file mode 100644 index 00000000..07f6b91f --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala @@ -0,0 +1,26 @@ +/* + * 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.postgresql.parsers + +import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, Message} +import java.nio.charset.Charset + +class NoticeParser( charset : Charset ) extends InformationParser(charset) { + + def createMessage(fields: Map[Char, String]): Message = new NoticeMessage(fields) + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala new file mode 100644 index 00000000..c98ddc34 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -0,0 +1,32 @@ +/* + * 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.postgresql.parsers + +import com.github.mauricio.postgresql.ChannelUtils +import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer + +class ParameterStatusParser( charset : Charset ) extends Decoder { + + import ChannelUtils._ + + override def parseMessage(b: ChannelBuffer): Message = { + new ParameterStatusMessage( readCString(b, charset), readCString(b, charset) ) + } + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala new file mode 100644 index 00000000..85df2aa8 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala @@ -0,0 +1,28 @@ +/* + * 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.postgresql.parsers + +import com.github.mauricio.postgresql.messages.backend.{ReadyForQueryMessage, Message} +import org.jboss.netty.buffer.ChannelBuffer + +object ReadyForQueryParser extends Decoder { + + override def parseMessage(b: ChannelBuffer): Message = { + new ReadyForQueryMessage( b.readByte().asInstanceOf[Char] ) + } + +} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala index 9a0fc82e..4d69061a 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ReturningMessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala @@ -1,7 +1,7 @@ package com.github.mauricio.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.messages.backend.Message +import org.jboss.netty.buffer.ChannelBuffer /** * User: Maurício Linhares diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala similarity index 74% rename from src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 3a35acc0..8e1408d3 100644 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/RowDescriptionParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -1,16 +1,29 @@ +/* + * 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.postgresql.parsers -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.postgresql.ChannelUtils import com.github.mauricio.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer /** - * User: Maurício Linhares - * Date: 3/1/12 - * Time: 1:56 AM - * - * RowDescription (B) + + RowDescription (B) Byte1('T') Identifies the message as a row description. diff --git a/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala similarity index 52% rename from src/main/scala/com/github/mauricio/postgresql/pool/ConnectionObjectFactory.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index e0d82f1e..3437f117 100644 --- a/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -1,15 +1,26 @@ +/* + * 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.postgresql.pool -import org.apache.commons.pool.PoolableObjectFactory -import concurrent.duration._ +import com.github.mauricio.async.db.Configuration +import com.github.mauricio.postgresql.DatabaseConnectionHandler import concurrent.Await -import com.github.mauricio.postgresql.{Configuration, DatabaseConnectionHandler} - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 10:40 PM - */ +import concurrent.duration._ +import org.apache.commons.pool.PoolableObjectFactory class ConnectionObjectFactory( configuration : Configuration) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala new file mode 100644 index 00000000..72eee163 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.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.postgresql.pool + +import com.github.mauricio.async.db.{Configuration, Connection} +import org.apache.commons.pool.impl.StackObjectPool + +class ConnectionPool( val configuration : Configuration ) { + + private val factory = new ConnectionObjectFactory(configuration) + private val pool = new StackObjectPool( this.factory, 1 ) + + def doWithConnection[T]( fn : Connection => T ) : T = { + val borrowed = this.pool.borrowObject() + try { + fn(borrowed) + } finally { + this.pool.returnObject(borrowed) + } + } + +} diff --git a/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala b/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala new file mode 100644 index 00000000..39cc7569 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala @@ -0,0 +1,29 @@ +/* + * 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.util + +import java.util.concurrent.{Executors, ThreadFactory} + +object DaemonThreadsFactory extends ThreadFactory { + def newThread(r: Runnable): Thread = { + + val thread = Executors.defaultThreadFactory().newThread(r) + thread.setDaemon(true) + + return thread + } +} diff --git a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala new file mode 100644 index 00000000..84d14ab2 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -0,0 +1,23 @@ +/* + * 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.util + +import java.util.concurrent.Executors + +object ExecutorServiceUtils { + val CachedThreadPool = Executors.newCachedThreadPool( DaemonThreadsFactory ) +} diff --git a/src/main/scala/com/github/mauricio/postgresql/util/Log.scala b/src/main/scala/com/github/mauricio/async/db/util/Log.scala similarity index 87% rename from src/main/scala/com/github/mauricio/postgresql/util/Log.scala rename to src/main/scala/com/github/mauricio/async/db/util/Log.scala index ce13d685..f172356b 100644 --- a/src/main/scala/com/github/mauricio/postgresql/util/Log.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/Log.scala @@ -1,4 +1,4 @@ -package com.github.mauricio.postgresql.util +package com.github.mauricio.async.db.util import org.slf4j.LoggerFactory diff --git a/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala b/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala deleted file mode 100644 index 02e50889..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/CharsetHelper.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.mauricio.postgresql - -import org.jboss.netty.util.CharsetUtil -import java.nio.charset.Charset - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 10:46 PM - */ - -object CharsetHelper { - - def toBytes( content : String, charset : Charset ) : Array[Byte] = { - content.getBytes( charset ) - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/Connection.scala b/src/main/scala/com/github/mauricio/postgresql/Connection.scala deleted file mode 100644 index 2f82a29e..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/Connection.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mauricio.postgresql - -import concurrent.Future - - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 10:40 PM - */ - -trait Connection { - - def disconnect : Future[Connection] - def connect : Future[Map[String,String]] - def isConnected : Boolean - def sendQuery( query : String ) : Future[QueryResult] - def sendPreparedStatement( query : String, values : Array[Any] = Array.empty[Any] ) : Future[QueryResult] - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/ExecutorServiceUtils.scala b/src/main/scala/com/github/mauricio/postgresql/ExecutorServiceUtils.scala deleted file mode 100644 index 09d2e3fc..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/ExecutorServiceUtils.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.mauricio.postgresql - -import java.util.concurrent.Executors -import util.DaemonThreadsFactory - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:25 PM - */ - -object ExecutorServiceUtils { - val CachedThreadPool = Executors.newCachedThreadPool( DaemonThreadsFactory ) -} diff --git a/src/main/scala/com/github/mauricio/postgresql/ResultSet.scala b/src/main/scala/com/github/mauricio/postgresql/ResultSet.scala deleted file mode 100644 index e7d03bb7..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/ResultSet.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.mauricio.postgresql - -/** - * User: mauricio - * Date: 3/25/13 - * Time: 10:24 PM - */ -trait ResultSet extends IndexedSeq[Array[Any]] { - - def apply( name : String, row : Int ) : Any - - def apply( column : Int, row : Int ) : Any - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/BigDecimalEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/BigDecimalEncoderDecoder.scala deleted file mode 100644 index 8c5f0830..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/column/BigDecimalEncoderDecoder.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.mauricio.postgresql.column - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:42 AM - */ - -object BigDecimalEncoderDecoder extends ColumnEncoderDecoder { - - def decode( value : String ) : Any = { - BigDecimal(value) - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/BooleanEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/BooleanEncoderDecoder.scala deleted file mode 100644 index d8c998e5..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/column/BooleanEncoderDecoder.scala +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.mauricio.postgresql.column - - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:50 AM - */ - -object BooleanEncoderDecoder extends ColumnEncoderDecoder { - - override def decode(value: String): Any = { - if ( "t" == value ) { - true - } else { - false - } - } - - override def encode( value : Any ) : String = { - val result = value.asInstanceOf[Boolean] - - if (result) { - "t" - } else { - "f" - } - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/FloatEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/FloatEncoderDecoder.scala deleted file mode 100644 index 0cec9882..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/column/FloatEncoderDecoder.scala +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.mauricio.postgresql.column - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:47 AM - */ - -object FloatEncoderDecoder extends ColumnEncoderDecoder { - def decode(value: String): Float = { - value.toFloat - } -} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/LongEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/LongEncoderDecoder.scala deleted file mode 100644 index dd8c9d3d..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/column/LongEncoderDecoder.scala +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.mauricio.postgresql.column - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:55 AM - */ - -object LongEncoderDecoder extends ColumnEncoderDecoder { - def decode(value: String): Long = { - value.toLong - } -} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala deleted file mode 100644 index 92f9aff9..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimeEncoderDecoder.scala +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.mauricio.postgresql.column - -import org.joda.time.format.DateTimeFormat -import org.joda.time.LocalTime - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 6:13 PM - */ - -object TimeEncoderDecoder { - val Instance = new TimeEncoderDecoder() -} - -class TimeEncoderDecoder extends ColumnEncoderDecoder { - - private val parser = DateTimeFormat.forPattern("HH:mm:ss.SSSSSS") - - def formatter = parser - - def decode(value: String): LocalTime = { - parser.parseLocalTime(value) - } - - override def encode( value : Any ) : String = { - this.parser.print( value.asInstanceOf[LocalTime] ) - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala deleted file mode 100644 index 74086227..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.mauricio.postgresql.column - -import org.joda.time.format.DateTimeFormat - -/** - * User: Maurício Linhares - * Date: 3/6/12 - * Time: 9:27 AM - */ - -object TimestampWithTimezoneEncoderDecoder extends TimestampEncoderDecoder { - - private val format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSSZ") - - override def formatter = format - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala b/src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala deleted file mode 100644 index 831473bd..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/encoders/CloseMessageEncoder.scala +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.mauricio.postgresql.encoders - -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.postgresql.messages.frontend.FrontendMessage - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 8:41 PM - */ - -object CloseMessageEncoder extends Encoder { - - override def encode(message: FrontendMessage): ChannelBuffer = { - val buffer = ChannelBuffers.buffer(5) - buffer.writeByte('X') - buffer.writeInt(4) - - buffer - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala deleted file mode 100644 index 6432008a..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/exceptions/DatabaseException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mauricio.postgresql.exceptions - -import com.github.mauricio.postgresql.messages.backend.ErrorMessage - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:00 AM - */ -class DatabaseException( val errorMessage : ErrorMessage ) - extends IllegalStateException( errorMessage.toString ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/EncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/EncoderNotAvailableException.scala deleted file mode 100644 index 6d8f13de..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/exceptions/EncoderNotAvailableException.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.mauricio.postgresql.exceptions - -import com.github.mauricio.postgresql.messages.frontend.FrontendMessage - -/** - * User: Maurício Linhares - * Date: 3/4/12 - * Time: 12:19 AM - */ - -class EncoderNotAvailableException( message : FrontendMessage ) - extends IllegalArgumentException( "Encoder not available for name %s".format( message.kind ) ) diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/NotConnectedException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/NotConnectedException.scala deleted file mode 100644 index a6065c07..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/exceptions/NotConnectedException.scala +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.mauricio.postgresql.exceptions - -/** - * User: mauricio - * Date: 3/24/13 - * Time: 7:14 PM - */ -class NotConnectedException ( message : String ) extends IllegalStateException (message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/ParserNotAvailableException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/ParserNotAvailableException.scala deleted file mode 100644 index 8cf26732..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/exceptions/ParserNotAvailableException.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.mauricio.postgresql.exceptions - -/** - * User: Maurício Linhares - * Date: 3/4/12 - * Time: 12:16 AM - */ - -class ParserNotAvailableException(t: Char) - extends RuntimeException("There is no parser available for message type '%s'".format(t)) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala b/src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala deleted file mode 100644 index def32b5f..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.mauricio.postgresql.exceptions - -/** - * User: mauricio - * Date: 3/30/13 - * Time: 11:23 PM - */ -class UnsupportedAuthenticationMethodException ( val authenticationType : Int ) - extends IllegalArgumentException ( "Unknown authentication method -> '%s'".format(authenticationType) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala deleted file mode 100644 index 34da9338..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:31 AM - */ - -object AuthenticationChallengeCleartextMessage { - val Instance = new AuthenticationChallengeCleartextMessage() -} - -class AuthenticationChallengeCleartextMessage - extends AuthenticationChallengeMessage( AuthenticationResponseType.Cleartext, None ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala deleted file mode 100644 index 7bcc4732..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMD5.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:32 AM - */ - -class AuthenticationChallengeMD5( salt : Array[Byte] ) - extends AuthenticationChallengeMessage( AuthenticationResponseType.MD5, Some(salt) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala deleted file mode 100644 index 508fc7ec..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationChallengeMessage.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:45 AM - */ -class AuthenticationChallengeMessage ( - val challengeType : AuthenticationResponseType.AuthenticationResponseType, - val salt : Option[Array[Byte]] ) - extends AuthenticationMessage \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala deleted file mode 100644 index 9732ffc8..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationOkMessage.scala +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:30 AM - */ - -object AuthenticationOkMessage { - val Instance = new AuthenticationOkMessage() -} - -class AuthenticationOkMessage extends AuthenticationMessage diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala deleted file mode 100644 index 9a79095a..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/AuthenticationResponseType.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 12:22 AM - */ -object AuthenticationResponseType extends Enumeration { - type AuthenticationResponseType = Value - val MD5, Cleartext, Ok = Value -} diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala deleted file mode 100644 index 8060d257..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/BindComplete.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 2:32 AM - */ - -object BindComplete { - val Instance = new BindComplete() -} - - -class BindComplete extends Message( Message.BindComplete ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala deleted file mode 100644 index f1769975..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/CloseComplete.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 11:37 PM - */ - -object CloseComplete { - val Instance = new CloseComplete() -} - -class CloseComplete extends Message(Message.CloseComplete) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ColumnData.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ColumnData.scala deleted file mode 100644 index bb410204..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ColumnData.scala +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -import com.github.mauricio.postgresql.column.ColumnEncoderDecoder - -/** - * User: Maurício Linhares - * Date: 3/1/12 - * Time: 10:33 PM - */ - -class ColumnData( - val name: String, - val tableObjectId: Int, - val columnNumber: Int, - val dataType: Int, - val dataTypeSize: Int, - val dataTypeModifier: Int, - val fieldFormat: Int ) { - - val decoder = ColumnEncoderDecoder.decoderFor( this.dataType ) - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala deleted file mode 100644 index 10e05ebd..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/DataRowMessage.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -import org.jboss.netty.buffer.ChannelBuffer - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:10 AM - */ -case class DataRowMessage( val values : Array[ChannelBuffer] ) extends Message( Message.DataRow ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala deleted file mode 100644 index 2ae32eb3..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/NoticeMessage.scala +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 12:45 AM - */ -class NoticeMessage ( fields : Map[Char,String] ) - extends InformationMessage( Message.Notice, fields ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala deleted file mode 100644 index f4876b83..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ParseComplete.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 2:29 AM - */ - -object ParseComplete { - val Instance = new ParseComplete() -} - -class ParseComplete extends Message(Message.ParseComplete) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala deleted file mode 100644 index 9566d67a..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/ProcessData.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 11:13 PM - */ - -case class ProcessData ( val processId : Int, val secretKey : Int ) - extends Message( Message.BackendKeyData ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala deleted file mode 100644 index ebcd6903..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/backend/RowDescriptionMessage.scala +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:15 AM - */ -case class RowDescriptionMessage ( val columnDatas : Array[ColumnData] ) - extends Message( Message.RowDescription ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CloseMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CloseMessage.scala deleted file mode 100644 index 4140a782..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CloseMessage.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -import com.github.mauricio.postgresql.messages.backend.Message - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 8:39 PM - */ - -object CloseMessage { - - val Instance = new CloseMessage() - -} - -class CloseMessage extends FrontendMessage( Message.Close ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala deleted file mode 100644 index 408b2bba..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/CredentialMessage.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:43 AM - */ -class CredentialMessage( - val username : String, - val password : String, - val authenticationType : AuthenticationResponseType.AuthenticationResponseType, - val salt : Option[Array[Byte]] - ) - extends FrontendMessage( Message.PasswordMessage ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/FrontendMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/FrontendMessage.scala deleted file mode 100644 index 0baceaf2..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/FrontendMessage.scala +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -/** - * User: Maurício Linhares - * Date: 3/7/12 - * Time: 9:30 AM - */ - -class FrontendMessage ( val kind : Char ) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala deleted file mode 100644 index 4c854490..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -import com.github.mauricio.postgresql.messages.backend.Message - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 6:50 PM - */ - -class PreparedStatementExecuteMessage( query : String, values : Seq[Any] ) - extends PreparedStatementMessage(Message.Execute, query, values) diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementMessage.scala deleted file mode 100644 index bd6269b4..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementMessage.scala +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -import com.github.mauricio.postgresql.column.ColumnEncoderDecoder - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 10:12 PM - */ - -class PreparedStatementMessage( kind : Char, val query : String, val values : Seq[Any] ) extends FrontendMessage(kind) { - - val valueTypes : Seq[Int] = values.map { - value => - if ( value == null ) { - 0 - } else { - ColumnEncoderDecoder.kindFor(value.asInstanceOf[AnyRef].getClass) - } - - } - -} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala deleted file mode 100644 index 0a4ae568..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -import com.github.mauricio.postgresql.messages.backend.Message - -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 1:00 AM - */ - -class PreparedStatementOpeningMessage( query : String, values : Seq[Any] ) - extends PreparedStatementMessage(Message.Parse, query, values) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/QueryMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/QueryMessage.scala deleted file mode 100644 index 2be2f212..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/QueryMessage.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -import com.github.mauricio.postgresql.messages.backend.Message - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 8:31 PM - */ - -class QueryMessage( val query : String ) extends FrontendMessage( Message.Query ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala b/src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala deleted file mode 100644 index b2a79b56..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/messages/frontend/StartupMessage.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.mauricio.postgresql.messages.frontend - -import com.github.mauricio.postgresql.messages.backend.Message - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 7:34 PM - */ - -class StartupMessage ( val parameters : List[(String, Any)] ) extends FrontendMessage(Message.Startup) diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala deleted file mode 100644 index ce801472..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/BackendKeyDataParser.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 11:13 PM - */ - -object BackendKeyDataParser extends Decoder { - - override def parseMessage(b: ChannelBuffer): Message = { - new ProcessData( b.readInt(), b.readInt() ) - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala deleted file mode 100644 index f6012ed9..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/Decoder.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.messages.backend.Message - -/** - * User: mauricio - * Date: 4/4/13 - * Time: 12:12 AM - */ -trait Decoder { - - def parseMessage(buffer: ChannelBuffer): Message - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala deleted file mode 100644 index cec43370..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ErrorParser.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} -import java.nio.charset.Charset - -class ErrorParser( charset : Charset ) extends InformationParser(charset) { - - def createMessage(fields: Map[Char, String]): Message = new ErrorMessage(fields) - -} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala deleted file mode 100644 index 971bacfd..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/NoticeParser.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, Message} -import java.nio.charset.Charset - -/** - * User: Maurício Linhares - * Date: 3/1/12 - * Time: 10:06 PM - */ - -class NoticeParser( charset : Charset ) extends InformationParser(charset) { - - def createMessage(fields: Map[Char, String]): Message = new NoticeMessage(fields) - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala deleted file mode 100644 index b6e2f508..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ParameterStatusParser.scala +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} -import java.nio.charset.Charset - -/** - * User: Maurício Linhares - * Date: 2/25/12 - * Time: 7:06 PM - */ - -class ParameterStatusParser( charset : Charset ) extends Decoder { - - import ChannelUtils._ - - override def parseMessage(b: ChannelBuffer): Message = { - new ParameterStatusMessage( readCString(b, charset), readCString(b, charset) ) - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala b/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala deleted file mode 100644 index f9580417..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/parsers/ReadyForQueryParser.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.mauricio.postgresql.parsers - -import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.postgresql.messages.backend.{ReadyForQueryMessage, Message} - -/** - * User: Maurício Linhares - * Date: 2/29/12 - * Time: 12:33 AM - */ - -object ReadyForQueryParser extends Decoder { - - override def parseMessage(b: ChannelBuffer): Message = { - new ReadyForQueryMessage( b.readByte().asInstanceOf[Char] ) - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionPool.scala deleted file mode 100644 index f0857195..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/pool/ConnectionPool.scala +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.mauricio.postgresql.pool - -import org.apache.commons.pool.impl.StackObjectPool -import com.github.mauricio.postgresql.{Configuration, Connection} - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 10:38 PM - */ - -class ConnectionPool( val configuration : Configuration ) { - - private val factory = new ConnectionObjectFactory(configuration) - private val pool = new StackObjectPool( this.factory, 1 ) - - def doWithConnection[T]( fn : Connection => T ) : T = { - val borrowed = this.pool.borrowObject() - try { - fn(borrowed) - } finally { - this.pool.returnObject(borrowed) - } - } - -} diff --git a/src/main/scala/com/github/mauricio/postgresql/util/DaemonThreadsFactory.scala b/src/main/scala/com/github/mauricio/postgresql/util/DaemonThreadsFactory.scala deleted file mode 100644 index a139bd90..00000000 --- a/src/main/scala/com/github/mauricio/postgresql/util/DaemonThreadsFactory.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mauricio.postgresql.util - -import java.util.concurrent.{Executors, ThreadFactory} - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 2:22 PM - */ - -object DaemonThreadsFactory extends ThreadFactory { - def newThread(r: Runnable): Thread = { - - val thread = Executors.defaultThreadFactory().newThread(r) - thread.setDaemon(true) - - return thread - } -} diff --git a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala similarity index 92% rename from src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala rename to src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 4bee24d1..2cbd5472 100644 --- a/src/test/scala/com/github/mauricio/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -1,18 +1,29 @@ +/* + * 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.postgresql import column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} +import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} +import concurrent.{Future, Await} import exceptions.{DatabaseException, UnsupportedAuthenticationMethodException} import messages.backend.InformationMessage import org.specs2.mutable.Specification -import scala.concurrent.duration._ -import concurrent.{Future, Await} import scala.concurrent.ExecutionContext.Implicits.global - -/** - * User: Maurício Linhares - * Date: 3/1/12 - * Time: 12:38 AM - */ +import scala.concurrent.duration._ class DatabaseConnectionHandlerSpec extends Specification { diff --git a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala similarity index 71% rename from src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala rename to src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index 1954aaef..b0f5d687 100644 --- a/src/test/scala/com/github/mauricio/postgresql/MessageDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -1,15 +1,25 @@ +/* + * 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.postgresql import messages.backend.ErrorMessage -import org.specs2.mutable.Specification import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 10:33 PM - */ +import org.specs2.mutable.Specification class MessageDecoderSpec extends Specification { diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala new file mode 100644 index 00000000..7d3bd17c --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala @@ -0,0 +1,29 @@ +/* + * 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.postgresql + +import java.util.concurrent.atomic.AtomicInteger + +object TestUtils { + + private val count = new AtomicInteger() + + def nextInt : Int = { + count.incrementAndGet() + } + +} diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala similarity index 53% rename from src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala rename to src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala index b1a6c9ab..fe763f27 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserESpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala @@ -1,15 +1,25 @@ +/* + * 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.postgresql.parsers -import org.specs2.mutable.Specification -import org.jboss.netty.buffer.ChannelBuffers import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} +import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 9:54 PM - */ +import org.specs2.mutable.Specification class ParserESpec extends Specification { diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserKSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala similarity index 51% rename from src/test/scala/com/github/mauricio/postgresql/parsers/ParserKSpec.scala rename to src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala index ba4db061..b4e6918b 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserKSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala @@ -1,14 +1,24 @@ +/* + * 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.postgresql.parsers -import org.specs2.mutable.Specification -import org.jboss.netty.buffer.ChannelBuffers import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 11:33 PM - */ +import org.jboss.netty.buffer.ChannelBuffers +import org.specs2.mutable.Specification class ParserKSpec extends Specification { diff --git a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala similarity index 63% rename from src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala rename to src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala index f2f30899..5928cc7b 100644 --- a/src/test/scala/com/github/mauricio/postgresql/parsers/ParserSSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala @@ -1,16 +1,26 @@ +/* + * 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.postgresql.parsers -import org.specs2.mutable.Specification -import org.jboss.netty.buffer.ChannelBuffers -import java.nio.charset.Charset import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil - -/** - * User: Maurício Linhares - * Date: 2/28/12 - * Time: 9:59 PM - */ +import org.specs2.mutable.Specification class ParserSSpec extends Specification { diff --git a/src/test/scala/com/github/mauricio/postgresql/TestUtils.scala b/src/test/scala/com/github/mauricio/postgresql/TestUtils.scala deleted file mode 100644 index cbfe4313..00000000 --- a/src/test/scala/com/github/mauricio/postgresql/TestUtils.scala +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.mauricio.postgresql - -import java.util.concurrent.atomic.AtomicInteger - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 9:49 PM - */ - -object TestUtils { - - private val count = new AtomicInteger() - - def nextInt : Int = { - count.incrementAndGet() - } - -} From 31f99ff903aaafdf935151bdd4cea2f3c18f99d1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 6 Apr 2013 10:50:40 -0300 Subject: [PATCH 016/357] Removing unecessary dependencies from maven config --- pom.xml | 12 --- .../async/db/postgresql/ArrayTypesSpec.scala | 79 +++++++++++++++++++ .../DatabaseConnectionHandlerSpec.scala | 60 +++----------- .../db/postgresql/DatabaseTestHelper.scala | 71 +++++++++++++++++ 4 files changed, 161 insertions(+), 61 deletions(-) create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala diff --git a/pom.xml b/pom.xml index 009435ec..d28dfc3d 100644 --- a/pom.xml +++ b/pom.xml @@ -131,18 +131,6 @@ test - - com.typesafe.akka - akka-actor_2.10 - 2.1.2 - - - - junit - junit - 4.11 - test - \ No newline at end of file diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala new file mode 100644 index 00000000..92f111b5 --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala @@ -0,0 +1,79 @@ +/* + * 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 + +import org.specs2.mutable.Specification + +class ArrayTypesSpec extends Specification with DatabaseTestHelper { + + val create = """create temp table type_test_table ( + bigserial_column bigserial not null, + smallint_column smallint[] not null, + integer_column integer[] not null, + decimal_column decimal(10,4)[], + real_column real[], + double_column double[] precision, + serial_column serial[] not null, + varchar_column varchar(255)[], + text_column text[], + timestamp_column timestamp[], + date_column date[], + time_column time[], + boolean_column boolean[], + constraint bigserial_column_pkey primary key (bigserial_column) + )""" + + val simpleCreate = """create temp table type_test_table ( + bigserial_column bigserial not null, + smallint_column smallint[] not null, + text_column text[] not null, + timestamp_column timestamp with time zone[] not null, + constraint bigserial_column_pkey primary key (bigserial_column) + )""" + + val insert = + """insert into type_test_table + (smallint_column, text_column, timestamp_column) + values ( + '{1,2,3,4}', + '{"some,\"comma,separated,text","another line of text"}', + '{ 2013-04-06 01:15:10.528-03, 2013-04-06 01:15:08.528-03 }' + )""" + + "connection" should { + + "correctly parse the array type" in { + + withHandler { + handler => + executeDdl(handler, simpleCreate) + executeDdl(handler, insert, 1) + val result = executeQuery(handler, "select * from type_test_table") + printf("id %s%n", result.rows.get(0,0)) + printf("array %s%n", result.rows.get.apply(1,0)) + printf("strings %s%n", result.rows.get.apply(2,0)) + printf("timestamps %s%n", result.rows.get.apply(3,0)) + + + true === false + } + + } + + } + +} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 2cbd5472..65785d47 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -24,8 +24,9 @@ import messages.backend.InformationMessage import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +import com.github.mauricio.async.db.postgresql.DatabaseTestHelper -class DatabaseConnectionHandlerSpec extends Specification { +class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { val create = """create temp table type_test_table ( bigserial_column bigserial not null, @@ -72,13 +73,6 @@ class DatabaseConnectionHandlerSpec extends Specification { ) """ - val DatabaseName = Some("netty_driver_test") - val DatabasePort = 5433 - val DefaultConfiguration = new Configuration( - port = DatabasePort, - username = "postgres", - database = DatabaseName) - val select = "select * from type_test_table" val preparedStatementCreate = """create temp table prepared_statement_test ( @@ -92,38 +86,6 @@ class DatabaseConnectionHandlerSpec extends Specification { val preparedStatementInsert3 = " insert into prepared_statement_test (name) values ('Peter Parker')" val preparedStatementSelect = "select * from prepared_statement_test" - def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { - withHandler( DefaultConfiguration, fn ) - } - - def withHandler[T]( configuration : Configuration, fn: (DatabaseConnectionHandler) => T): T = { - - val handler = new DatabaseConnectionHandler(configuration) - - try { - Await.result(handler.connect, Duration(5, SECONDS)) - fn(handler) - } finally { - handler.disconnect - } - - } - - def executeDdl( handler : DatabaseConnectionHandler, data : String, count : Int = 0 ) = { - Await.result(handler.sendQuery(data), Duration(5, SECONDS)).rowsAffected === count - } - - def executeQuery( handler : DatabaseConnectionHandler, data : String ) = { - Await.result(handler.sendQuery(data), Duration(5, SECONDS)) - } - - def executePreparedStatement( - handler : DatabaseConnectionHandler, - statement : String, - values : Array[Any] = Array.empty[Any] ) = { - Await.result( handler.sendPreparedStatement(statement, values), Duration(5, SECONDS) ) - } - "handler" should { "connect to the database" in { @@ -239,8 +201,8 @@ class DatabaseConnectionHandlerSpec extends Specification { val configuration = new Configuration( username = "postgres_md5", password = Some("postgres_md5"), - port = DatabasePort, - database = DatabaseName + port = databasePort, + database = databaseName ) withHandler(configuration, { @@ -256,8 +218,8 @@ class DatabaseConnectionHandlerSpec extends Specification { val configuration = new Configuration( username = "postgres_cleartext", password = Some("postgres_cleartext"), - port = DatabasePort, - database = DatabaseName + port = databasePort, + database = databaseName ) withHandler(configuration, { @@ -273,8 +235,8 @@ class DatabaseConnectionHandlerSpec extends Specification { val configuration = new Configuration( username = "postgres_kerberos", password = Some("postgres_kerberos"), - port = DatabasePort, - database = DatabaseName + port = databasePort, + database = databaseName ) withHandler(configuration, { @@ -289,8 +251,8 @@ class DatabaseConnectionHandlerSpec extends Specification { val configuration = new Configuration( username = "postgres_md5", password = Some("postgres_kerberos"), - port = DatabasePort, - database = DatabaseName + port = databasePort, + database = databaseName ) try { withHandler(configuration, { @@ -311,7 +273,7 @@ class DatabaseConnectionHandlerSpec extends Specification { "transaction and flatmap example" in { - val handler : Connection = new DatabaseConnectionHandler( DefaultConfiguration ) + val handler : Connection = new DatabaseConnectionHandler( defaultConfiguration ) val result: Future[QueryResult] = handler.connect .map( parameters => handler ) .flatMap( connection => connection.sendQuery("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ") ) diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala new file mode 100644 index 00000000..ae8836d7 --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -0,0 +1,71 @@ +/* + * 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 + +import com.github.mauricio.postgresql.DatabaseConnectionHandler +import com.github.mauricio.async.db.Configuration +import concurrent.Await +import concurrent.duration._ + +trait DatabaseTestHelper { + + def databaseName = Some("netty_driver_test") + def databasePort = 5433 + + def defaultConfiguration = new Configuration( + port = databasePort, + username = "postgres", + database = databaseName) + + def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { + withHandler( this.defaultConfiguration, fn ) + } + + def withHandler[T]( configuration : Configuration, fn: (DatabaseConnectionHandler) => T): T = { + + val handler = new DatabaseConnectionHandler(configuration) + + try { + Await.result(handler.connect, Duration(5, SECONDS)) + fn(handler) + } finally { + handler.disconnect + } + + } + + def executeDdl( handler : DatabaseConnectionHandler, data : String, count : Int = 0 ) = { + val rows = Await.result(handler.sendQuery(data), Duration(5, SECONDS)).rowsAffected + + if ( rows != count ) { + throw new IllegalStateException("We expected %s rows but there were %s".format(count, rows)) + } + + } + + def executeQuery( handler : DatabaseConnectionHandler, data : String ) = { + Await.result(handler.sendQuery(data), Duration(5, SECONDS)) + } + + def executePreparedStatement( + handler : DatabaseConnectionHandler, + statement : String, + values : Array[Any] = Array.empty[Any] ) = { + Await.result( handler.sendPreparedStatement(statement, values), Duration(5, SECONDS) ) + } + +} From 9e71cafe90ffe2512399205f26e0888d5549adcc Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 19 Apr 2013 18:08:10 -0300 Subject: [PATCH 017/357] Started worker on array decoders --- README.markdown | 2 +- .../DatabaseConnectionHandler.scala | 11 +-- .../async/db/postgresql/MessageEncoder.scala | 2 +- .../column/ArrayEncoderDecoder.scala | 57 +++++++++++++ .../column/BigDecimalEncoderDecoder.scala | 4 +- .../column/ColumnEncoderDecoder.scala | 6 +- .../column/DateEncoderDecoder.scala | 2 +- .../column/DoubleEncoderDecoder.scala | 4 +- .../column/FloatEncoderDecoder.scala | 4 +- .../column/IntegerEncoderDecoder.scala | 4 +- .../column/LongEncoderDecoder.scala | 4 +- .../column/TimeEncoderDecoder.scala | 2 +- .../column/TimestampEncoderDecoder.scala | 2 +- .../exceptions/DatabaseException.scala | 28 ++----- .../DateEncoderNotAvailableException.scala | 4 +- .../EncoderNotAvailableException.scala | 4 +- .../exceptions/GenericDatabaseException.scala | 22 +++++ .../exceptions/InvalidArrayException.scala | 10 +++ ...issingCredentialInformationException.scala | 4 +- .../exceptions/NotConnectedException.scala | 4 +- .../ParserNotAvailableException.scala | 4 +- ...pportedAuthenticationMethodException.scala | 4 +- .../parsers/AuthenticationStartupParser.scala | 2 +- .../db/postgresql/parsers/MessageParser.scala | 2 +- .../async/db/util/ArrayStreamingParser.scala | 80 +++++++++++++++++++ .../util/ArrayStreamingParserDelegate.scala | 15 ++++ .../DatabaseConnectionHandlerSpec.scala | 4 +- .../column/ArrayEncoderDecoderSpec.scala | 29 +++++++ .../util/ArrayStreamingParserSpec.scala | 64 +++++++++++++++ 29 files changed, 316 insertions(+), 68 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala diff --git a/README.markdown b/README.markdown index 1d026365..61dd6857 100644 --- a/README.markdown +++ b/README.markdown @@ -41,4 +41,4 @@ to PostgreSQL. - check the **What is missing** piece - send a pull request with specs -This project is freely available under the MIT licence, use it at your own risk. +This project is freely available under the Apache 2 licence, use it at your own risk. \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 7e69cb53..e00b7f8f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -19,22 +19,17 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.util.{Log, ExecutorServiceUtils} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import concurrent.{Future, Promise} -import exceptions.{MissingCredentialInformationException, DatabaseException, NotConnectedException} import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap import messages.backend._ -import messages.backend.CommandCompleteMessage -import messages.backend.DataRowMessage -import messages.backend.ParameterStatusMessage -import messages.backend.ProcessData -import messages.backend.RowDescriptionMessage import messages.frontend._ import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.channel._ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} -import scala.collection.JavaConversions._ import scala.Some +import scala.collection.JavaConversions._ +import com.github.mauricio.async.db.postgresql.exceptions.{MissingCredentialInformationException, NotConnectedException, GenericDatabaseException} object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -293,7 +288,7 @@ class DatabaseConnectionHandler private def onError(m: ErrorMessage) { log.error("Error with message -> {}", m) - val error = new DatabaseException(m) + val error = new GenericDatabaseException(m) error.fillInStackTrace() this.setErrorOnFutures(error) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index 83d25a74..6bc6531c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -18,12 +18,12 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.util.Log import encoders._ -import exceptions.EncoderNotAvailableException import java.nio.charset.Charset import messages.frontend._ import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder +import com.github.mauricio.async.db.postgresql.exceptions.EncoderNotAvailableException class MessageEncoder( charset : Charset ) extends OneToOneEncoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala new file mode 100644 index 00000000..2b48c424 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala @@ -0,0 +1,57 @@ +package com.github.mauricio.async.db.postgresql.column + +import com.github.mauricio.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer + +/** + * User: mauricio + * Date: 4/19/13 + * Time: 12:36 PM + */ +class ArrayEncoderDecoder( private val encoder : ColumnEncoderDecoder ) extends ColumnEncoderDecoder { + + override def decode(value: String): Any = { + + if ( value.charAt(0) != '{' || value.charAt(value.size - 1) != '}' ) { + throw new InvalidArrayException("The array %s is not valid".format(value)) + } + + var index = 0 + var escaping = false + val stack = new mutable.Stack[ArrayBuffer[Any]]() + var currentValue : mutable.StringBuilder = null + + while ( index < value.size ) { + + val item = value.charAt(index) + + if ( escaping ) { + currentValue.append(item) + escaping = false + } else { + item match { + case '{' => { + val currentArray = new ArrayBuffer[Any]() + + if ( !stack.isEmpty ) { + stack.top += currentArray + } + + stack.push( currentArray ) + } + case '}' => stack.pop() + case '"' => currentValue = new mutable.StringBuilder() + } + } + + index += 1 + + } + + } + + override def encode(value: Any): String = ??? + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala index d7f8a3ec..f255a161 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala @@ -18,8 +18,6 @@ package com.github.mauricio.postgresql.column object BigDecimalEncoderDecoder extends ColumnEncoderDecoder { - def decode( value : String ) : Any = { - BigDecimal(value) - } + override def decode( value : String ) : Any = BigDecimal(value) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala index 89b606bf..613e068a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala @@ -121,11 +121,9 @@ object ColumnEncoderDecoder { trait ColumnEncoderDecoder { - def decode(value: String): Any + def decode(value: String): Any = value - def encode(value: Any) : String = { - value.toString - } + def encode(value: Any) : String = value.toString } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala index a9c60917..f33230b6 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala @@ -16,9 +16,9 @@ package com.github.mauricio.postgresql.column -import com.github.mauricio.postgresql.exceptions.DateEncoderNotAvailableException import org.joda.time.format.DateTimeFormat import org.joda.time.{ReadableInstant, LocalDate} +import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException object DateEncoderDecoder extends ColumnEncoderDecoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala index cba71aed..2864e6b1 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala @@ -7,7 +7,5 @@ package com.github.mauricio.postgresql.column */ object DoubleEncoderDecoder extends ColumnEncoderDecoder { - def decode(value: String): Double = { - value.toDouble - } + override def decode(value: String): Double = value.toDouble } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala index 492dec0b..4688de78 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala @@ -17,7 +17,5 @@ package com.github.mauricio.postgresql.column object FloatEncoderDecoder extends ColumnEncoderDecoder { - def decode(value: String): Float = { - value.toFloat - } + override def decode(value: String): Float = value.toFloat } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala index 5524922d..67cf25bf 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala @@ -8,8 +8,6 @@ package com.github.mauricio.postgresql.column object IntegerEncoderDecoder extends ColumnEncoderDecoder { - def decode(value: String): Int = { - value.toInt - } + override def decode(value: String): Int = value.toInt } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala index 49ad3756..80fe9733 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala @@ -17,7 +17,5 @@ package com.github.mauricio.postgresql.column object LongEncoderDecoder extends ColumnEncoderDecoder { - def decode(value: String): Long = { - value.toLong - } + override def decode(value: String): Long = value.toLong } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala index 78f9c78b..3eadf15d 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala @@ -29,7 +29,7 @@ class TimeEncoderDecoder extends ColumnEncoderDecoder { def formatter = parser - def decode(value: String): LocalTime = { + override def decode(value: String): LocalTime = { parser.parseLocalTime(value) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala index 5e34c31c..6e88cc74 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.postgresql.column -import com.github.mauricio.postgresql.exceptions.DateEncoderNotAvailableException import java.sql.Timestamp import java.util.{Calendar, Date} import org.joda.time.format.DateTimeFormat import org.joda.time.{ReadableDateTime, DateTime} +import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException object TimestampEncoderDecoder { val Instance = new TimestampEncoderDecoder() diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala index 95456d7a..580fd571 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala @@ -1,22 +1,10 @@ -/* - * 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.postgresql.exceptions +package com.github.mauricio.async.db.postgresql.exceptions -import com.github.mauricio.postgresql.messages.backend.ErrorMessage +/** + * User: mauricio + * Date: 4/19/13 + * Time: 1:03 PM + */ +class DatabaseException(message : String) extends RuntimeException(message) { -class DatabaseException( val errorMessage : ErrorMessage ) - extends IllegalStateException( errorMessage.toString ) \ No newline at end of file +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala index 7034f41c..fc458177 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala @@ -1,4 +1,4 @@ -package com.github.mauricio.postgresql.exceptions +package com.github.mauricio.async.db.postgresql.exceptions /** * User: mauricio @@ -6,4 +6,4 @@ package com.github.mauricio.postgresql.exceptions * Time: 12:36 AM */ class DateEncoderNotAvailableException( value : Any ) - extends IllegalArgumentException( "There is no encoder for value [%s] of type %s".format(value, value.getClass.getCanonicalName) ) + extends DatabaseException( "There is no encoder for value [%s] of type %s".format(value, value.getClass.getCanonicalName) ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala index ba2c9dcf..68d18179 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.exceptions +package com.github.mauricio.async.db.postgresql.exceptions import com.github.mauricio.postgresql.messages.frontend.FrontendMessage class EncoderNotAvailableException( message : FrontendMessage ) - extends IllegalArgumentException( "Encoder not available for name %s".format( message.kind ) ) + extends DatabaseException( "Encoder not available for name %s".format( message.kind ) ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala new file mode 100644 index 00000000..58ce88b9 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala @@ -0,0 +1,22 @@ +/* + * 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.exceptions + +import com.github.mauricio.postgresql.messages.backend.ErrorMessage + +class GenericDatabaseException( val errorMessage : ErrorMessage ) + extends DatabaseException( errorMessage.toString ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala new file mode 100644 index 00000000..8f4d5494 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala @@ -0,0 +1,10 @@ +package com.github.mauricio.async.db.postgresql.exceptions + +/** + * User: mauricio + * Date: 4/19/13 + * Time: 12:54 PM + */ +class InvalidArrayException( message : String ) extends DatabaseException(message) { + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala index 4614641c..7a1e03a4 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala @@ -1,4 +1,4 @@ -package com.github.mauricio.postgresql.exceptions +package com.github.mauricio.async.db.postgresql.exceptions import com.github.mauricio.postgresql.messages.backend.AuthenticationResponseType @@ -11,7 +11,7 @@ class MissingCredentialInformationException ( val username : String, val password : Option[String], val authenticationResponseType : AuthenticationResponseType.AuthenticationResponseType ) - extends IllegalStateException ( + extends DatabaseException ( "Username and password were required by auth type %s but are not available (username=[%s] password=[%s]".format( authenticationResponseType, username, diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala index cd2672b3..2c4b0a74 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala @@ -14,6 +14,6 @@ * under the License. */ -package com.github.mauricio.postgresql.exceptions +package com.github.mauricio.async.db.postgresql.exceptions -class NotConnectedException ( message : String ) extends IllegalStateException (message) \ No newline at end of file +class NotConnectedException ( message : String ) extends DatabaseException (message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala index 9b4a5c9c..453af50f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.exceptions +package com.github.mauricio.async.db.postgresql.exceptions class ParserNotAvailableException(t: Char) - extends RuntimeException("There is no parser available for message type '%s'".format(t)) \ No newline at end of file + extends DatabaseException("There is no parser available for message type '%s'".format(t)) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala index 3397a432..15e7ca5e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.exceptions +package com.github.mauricio.async.db.postgresql.exceptions class UnsupportedAuthenticationMethodException ( val authenticationType : Int ) - extends IllegalArgumentException ( "Unknown authentication method -> '%s'".format(authenticationType) ) \ No newline at end of file + extends DatabaseException ( "Unknown authentication method -> '%s'".format(authenticationType) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index e065be05..ed24cff9 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -16,7 +16,7 @@ package com.github.mauricio.postgresql.parsers -import com.github.mauricio.postgresql.exceptions.UnsupportedAuthenticationMethodException +import com.github.mauricio.async.db.postgresql.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.postgresql.messages.backend._ import org.jboss.netty.buffer.ChannelBuffer diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala index db3d67a3..84d1468c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala @@ -16,10 +16,10 @@ package com.github.mauricio.postgresql.parsers -import com.github.mauricio.postgresql.exceptions.ParserNotAvailableException import com.github.mauricio.postgresql.messages.backend.{CloseComplete, BindComplete, ParseComplete, Message} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.postgresql.exceptions.ParserNotAvailableException class MessageParser(charset : Charset) { diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala new file mode 100644 index 00000000..ec55ef9f --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala @@ -0,0 +1,80 @@ +package com.github.mauricio.async.db.util + +import scala.collection.mutable.StringBuilder +import scala.collection.mutable +import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException + +/** + * User: mauricio + * Date: 4/19/13 + * Time: 1:31 PM + */ +class ArrayStreamingParser { + + def parse(content: String, delegate: ArrayStreamingParserDelegate) { + + var index = 0 + var escaping = false + var elementOpened = false + var currentElement: StringBuilder = null + var opens = 0 + var closes = 0 + + while (index < content.size) { + val char = content.charAt(index) + if (escaping) { + currentElement.append(char) + escaping = false + } else { + char match { + case '{' => { + delegate.arrayStarted + opens += 1 + } + case '}' => { + if (currentElement != null) { + delegate.elementFound(currentElement.toString()) + currentElement = null + } + delegate.arrayEnded + closes += 1 + } + case '"' => { + if (elementOpened) { + delegate.elementFound(currentElement.toString()) + currentElement = null + elementOpened = false + } else { + elementOpened = true + currentElement = new mutable.StringBuilder() + } + } + case ',' => { + if ( currentElement != null ) { + delegate.elementFound(currentElement.toString()) + } + currentElement = null + } + case '\\' => { + escaping = true + } + case _ => { + if ( currentElement == null ) { + currentElement = new mutable.StringBuilder() + } + currentElement.append(char) + } + } + } + + index += 1 + } + + if ( opens != closes ) { + throw new InvalidArrayException("This array is unbalanced %s".format(content)) + } + + } + + +} diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala new file mode 100644 index 00000000..2fea79e6 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala @@ -0,0 +1,15 @@ +package com.github.mauricio.async.db.util + +/** + * User: mauricio + * Date: 4/19/13 + * Time: 1:32 PM + */ + +trait ArrayStreamingParserDelegate { + + def arrayStarted : Unit = {} + def arrayEnded : Unit {} + def elementFound( element : String ) : Unit {} + +} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 65785d47..6f36b02f 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -19,12 +19,12 @@ package com.github.mauricio.postgresql import column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import concurrent.{Future, Await} -import exceptions.{DatabaseException, UnsupportedAuthenticationMethodException} import messages.backend.InformationMessage import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import com.github.mauricio.async.db.postgresql.DatabaseTestHelper +import com.github.mauricio.async.db.postgresql.exceptions.{GenericDatabaseException, UnsupportedAuthenticationMethodException} class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { @@ -261,7 +261,7 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe failure("should not have come here") }) } catch { - case e : DatabaseException => { + case e : GenericDatabaseException => { e.errorMessage.fields(InformationMessage.Routine) === "auth_failed" } case e : Exception => { diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala new file mode 100644 index 00000000..07836bee --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala @@ -0,0 +1,29 @@ +package com.github.mauricio.async.db.postgresql.column + +import com.github.mauricio.postgresql.column.IntegerEncoderDecoder +import org.specs2.mutable.Specification + +/** + * User: mauricio + * Date: 4/19/13 + * Time: 12:38 PM + */ +class ArrayEncoderDecoderSpec extends Specification { + + "encoder/decoder" should { + + "parse an array of strings" in { + + val numbers = "{{1,2,3},{4,5,6},{7,8,9}}" + val encoder = new ArrayEncoderDecoder( IntegerEncoderDecoder ) + + val result = encoder.decode( numbers ).asInstanceOf[Array[Array[Any]]] + + result(0) === Array[Any](1,2,3) + result(1) === Array[Any](4,5,6) + result(2) === Array[Any](7,8,9) + } + + } + +} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala new file mode 100644 index 00000000..6270cd42 --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala @@ -0,0 +1,64 @@ +package com.github.mauricio.async.db.postgresql.util + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} +import scala.collection.mutable.ArrayBuffer + +/** + * User: mauricio + * Date: 4/19/13 + * Time: 1:47 PM + */ +class ArrayStreamingParserSpec extends Specification { + + val parser = new ArrayStreamingParser() + + "parser" should { + + "generate the events correctly" in { + + val content = "{{1,2,3},{4,5,6}}" + + val delegate = new LoggingDelegate() + parser.parse(content, delegate) + + delegate.starts === 3 + delegate.ends === 3 + delegate.items === ArrayBuffer("{", "{", "1", "2", "3", "}", "{", "4", "5", "6","}","}") + } + + "should parse a varchar array correctly" in { + val content = """{{"item","is here","but\"not there"},{"so","this is your last step"},{""}}""" + + val delegate = new LoggingDelegate() + parser.parse(content, delegate) + + delegate.items === ArrayBuffer( "{", "{", "item", "is here", "but\"not there", "}", "{", "so", "this is your last step", "}", "{", "", "}","}" ) + delegate.starts === 4 + delegate.ends === 4 + } + + } + +} + +class LoggingDelegate extends ArrayStreamingParserDelegate { + + val items = new ArrayBuffer[String]() + var starts = 0 + var ends = 0 + + override def arrayStarted { + items += "{" + starts += 1 + } + + override def arrayEnded { + items += "}" + ends += 1 + } + + override def elementFound(element: String) { + items += element + } +} From 9c9d8288d49e7c8f9b1c6dc4cdac7d893f651a8a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 19 Apr 2013 23:47:55 -0300 Subject: [PATCH 018/357] Implemented basic array support and tests, still needs to register them as decoders --- .../column/ArrayEncoderDecoder.scala | 59 +++++++++---------- .../async/db/util/ArrayStreamingParser.scala | 28 ++++++--- .../util/ArrayStreamingParserDelegate.scala | 5 +- .../column/ArrayEncoderDecoderSpec.scala | 20 +++++-- .../util/ArrayStreamingParserSpec.scala | 15 ++++- 5 files changed, 79 insertions(+), 48 deletions(-) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala index 2b48c424..9842ad2b 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala @@ -1,9 +1,9 @@ package com.github.mauricio.async.db.postgresql.column +import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} import com.github.mauricio.postgresql.column.ColumnEncoderDecoder -import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException -import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer +import scala.collection.IndexedSeq +import scala.collection.mutable.{ArrayBuffer,Stack} /** * User: mauricio @@ -12,44 +12,41 @@ import scala.collection.mutable.ArrayBuffer */ class ArrayEncoderDecoder( private val encoder : ColumnEncoderDecoder ) extends ColumnEncoderDecoder { - override def decode(value: String): Any = { + override def decode(value: String): IndexedSeq[Any] = { - if ( value.charAt(0) != '{' || value.charAt(value.size - 1) != '}' ) { - throw new InvalidArrayException("The array %s is not valid".format(value)) - } - - var index = 0 - var escaping = false - val stack = new mutable.Stack[ArrayBuffer[Any]]() - var currentValue : mutable.StringBuilder = null - - while ( index < value.size ) { + val stack = new Stack[ArrayBuffer[Any]]() + var current : ArrayBuffer[Any] = null + var result : IndexedSeq[Any] = null + val delegate = new ArrayStreamingParserDelegate { + override def arrayEnded { + result = stack.pop() + } - val item = value.charAt(index) + override def elementFound(element: String) { + current += encoder.decode(element) + } - if ( escaping ) { - currentValue.append(item) - escaping = false - } else { - item match { - case '{' => { - val currentArray = new ArrayBuffer[Any]() + override def nullElementFound { + current += null + } - if ( !stack.isEmpty ) { - stack.top += currentArray - } + override def arrayStarted { + current = new ArrayBuffer[Any]() - stack.push( currentArray ) + stack.headOption match { + case Some(item) => { + item += current } - case '}' => stack.pop() - case '"' => currentValue = new mutable.StringBuilder() + case None => {} } - } - - index += 1 + stack.push( current ) + } } + ArrayStreamingParser.parse(value, delegate) + + result } override def encode(value: Any): String = ??? diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala index ec55ef9f..060e73ad 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala @@ -9,13 +9,13 @@ import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException * Date: 4/19/13 * Time: 1:31 PM */ -class ArrayStreamingParser { +object ArrayStreamingParser { def parse(content: String, delegate: ArrayStreamingParserDelegate) { var index = 0 var escaping = false - var elementOpened = false + var quoted = false var currentElement: StringBuilder = null var opens = 0 var closes = 0 @@ -33,25 +33,25 @@ class ArrayStreamingParser { } case '}' => { if (currentElement != null) { - delegate.elementFound(currentElement.toString()) + sendElementEvent(currentElement, quoted, delegate) currentElement = null } delegate.arrayEnded closes += 1 } case '"' => { - if (elementOpened) { - delegate.elementFound(currentElement.toString()) + if (quoted) { + sendElementEvent(currentElement, quoted, delegate) currentElement = null - elementOpened = false + quoted = false } else { - elementOpened = true + quoted = true currentElement = new mutable.StringBuilder() } } case ',' => { if ( currentElement != null ) { - delegate.elementFound(currentElement.toString()) + sendElementEvent(currentElement, quoted, delegate) } currentElement = null } @@ -76,5 +76,17 @@ class ArrayStreamingParser { } + def sendElementEvent( builder : mutable.StringBuilder, quoted : Boolean, delegate: ArrayStreamingParserDelegate ) { + + val value = builder.toString() + + if ( !quoted && "NULL".equalsIgnoreCase(value) ) { + delegate.nullElementFound + } else { + delegate.elementFound(value) + } + + } + } diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala index 2fea79e6..b0b92d88 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala @@ -9,7 +9,8 @@ package com.github.mauricio.async.db.util trait ArrayStreamingParserDelegate { def arrayStarted : Unit = {} - def arrayEnded : Unit {} - def elementFound( element : String ) : Unit {} + def arrayEnded : Unit = {} + def elementFound( element : String ) : Unit = {} + def nullElementFound : Unit = {} } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala index 07836bee..1d5eeed5 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala @@ -12,16 +12,24 @@ class ArrayEncoderDecoderSpec extends Specification { "encoder/decoder" should { - "parse an array of strings" in { + "parse an array of numbers" in { - val numbers = "{{1,2,3},{4,5,6},{7,8,9}}" + val numbers = "{1,2,3}" val encoder = new ArrayEncoderDecoder( IntegerEncoderDecoder ) - val result = encoder.decode( numbers ).asInstanceOf[Array[Array[Any]]] + val result = encoder.decode( numbers ) - result(0) === Array[Any](1,2,3) - result(1) === Array[Any](4,5,6) - result(2) === Array[Any](7,8,9) + result === List(1,2,3) + } + + "parse an array of array of numbers" in { + + val numbers = "{{1,2,3},{4,5,6}}" + val encoder = new ArrayEncoderDecoder( IntegerEncoderDecoder ) + + val result = encoder.decode( numbers ) + + result === List(List(1,2,3), List(4,5,6)) } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala index 6270cd42..eb210417 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala @@ -11,7 +11,7 @@ import scala.collection.mutable.ArrayBuffer */ class ArrayStreamingParserSpec extends Specification { - val parser = new ArrayStreamingParser() + val parser = ArrayStreamingParser "parser" should { @@ -38,6 +38,15 @@ class ArrayStreamingParserSpec extends Specification { delegate.ends === 4 } + "should parse a varchar array with nulls correctly" in { + val content = """{NULL,"first",NULL,"second","NULL",NULL}""" + + val delegate = new LoggingDelegate() + parser.parse(content, delegate) + + delegate.items === ArrayBuffer( "{", null, "first", null, "second", "NULL", null, "}" ) + } + } } @@ -61,4 +70,8 @@ class LoggingDelegate extends ArrayStreamingParserDelegate { override def elementFound(element: String) { items += element } + + override def nullElementFound { + items += null + } } From be3d489177703cf9c05dd6425ba4a3cbe744e113 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 20 Apr 2013 11:04:53 -0300 Subject: [PATCH 019/357] Adding licence to new files that did not have it --- .../column/ArrayEncoderDecoder.scala | 21 ++++++++++++---- .../exceptions/DatabaseException.scala | 25 +++++++++++++------ .../async/db/util/ArrayStreamingParser.scala | 20 +++++++++++---- .../util/ArrayStreamingParserDelegate.scala | 22 +++++++++++----- .../column/ArrayEncoderDecoderSpec.scala | 21 ++++++++++++---- .../util/ArrayStreamingParserSpec.scala | 21 ++++++++++++---- 6 files changed, 96 insertions(+), 34 deletions(-) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala index 9842ad2b..94f0f208 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala @@ -1,3 +1,19 @@ +/* + * 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.column import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} @@ -5,11 +21,6 @@ import com.github.mauricio.postgresql.column.ColumnEncoderDecoder import scala.collection.IndexedSeq import scala.collection.mutable.{ArrayBuffer,Stack} -/** - * User: mauricio - * Date: 4/19/13 - * Time: 12:36 PM - */ class ArrayEncoderDecoder( private val encoder : ColumnEncoderDecoder ) extends ColumnEncoderDecoder { override def decode(value: String): IndexedSeq[Any] = { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala index 580fd571..342e5c2b 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala @@ -1,10 +1,19 @@ -package com.github.mauricio.async.db.postgresql.exceptions - -/** - * User: mauricio - * Date: 4/19/13 - * Time: 1:03 PM +/* + * 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. */ -class DatabaseException(message : String) extends RuntimeException(message) { -} +package com.github.mauricio.async.db.postgresql.exceptions + +class DatabaseException(message : String) extends RuntimeException(message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala index 060e73ad..46b523bc 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala @@ -1,14 +1,24 @@ +/* + * Copyright 2013 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * 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 scala.collection.mutable.StringBuilder import scala.collection.mutable import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException -/** - * User: mauricio - * Date: 4/19/13 - * Time: 1:31 PM - */ object ArrayStreamingParser { def parse(content: String, delegate: ArrayStreamingParserDelegate) { diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala index b0b92d88..6c1340cc 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala @@ -1,11 +1,21 @@ -package com.github.mauricio.async.db.util - -/** - * User: mauricio - * Date: 4/19/13 - * Time: 1:32 PM +/* + * 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.util + trait ArrayStreamingParserDelegate { def arrayStarted : Unit = {} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala index 1d5eeed5..3faa4a97 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala @@ -1,13 +1,24 @@ +/* + * 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.column import com.github.mauricio.postgresql.column.IntegerEncoderDecoder import org.specs2.mutable.Specification -/** - * User: mauricio - * Date: 4/19/13 - * Time: 12:38 PM - */ class ArrayEncoderDecoderSpec extends Specification { "encoder/decoder" should { diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala index eb210417..6a66f1b3 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala @@ -1,14 +1,25 @@ +/* + * 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 org.specs2.mutable.Specification import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} import scala.collection.mutable.ArrayBuffer -/** - * User: mauricio - * Date: 4/19/13 - * Time: 1:47 PM - */ class ArrayStreamingParserSpec extends Specification { val parser = ArrayStreamingParser From e06cc2f32d093ff91fb798e04e13c039036d088f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 23 Apr 2013 00:04:51 -0300 Subject: [PATCH 020/357] Initial implementation to Array decoder for available values --- pom.xml | 236 +++++++++--------- .../postgresql/util/PostgreSQLMD5Digest.java | 30 +-- .../mauricio/async/db/Configuration.scala | 18 +- .../github/mauricio/async/db/Connection.scala | 14 +- .../mauricio/async/db/QueryResult.scala | 28 ++- .../github/mauricio/async/db/ResultSet.scala | 6 +- .../DatabaseConnectionHandler.scala | 35 +-- .../async/db/postgresql/MessageDecoder.scala | 16 +- .../async/db/postgresql/MessageEncoder.scala | 26 +- .../async/db/postgresql/MutableQuery.scala | 26 +- .../column/ArrayEncoderDecoder.scala | 11 +- .../column/BigDecimalEncoderDecoder.scala | 4 +- .../column/BooleanEncoderDecoder.scala | 6 +- .../column/CharEncoderDecoder.scala | 23 ++ .../column/ColumnEncoderDecoder.scala | 84 +++++-- .../column/DateEncoderDecoder.scala | 10 +- .../column/DoubleEncoderDecoder.scala | 22 +- .../column/FloatEncoderDecoder.scala | 2 +- .../column/IntegerEncoderDecoder.scala | 22 +- .../column/LongEncoderDecoder.scala | 2 +- .../column/ShortEncoderDecoder.scala | 23 ++ .../column/StringEncoderDecoder.scala | 22 +- .../column/TimeEncoderDecoder.scala | 6 +- .../TimeWithTimezoneEncoderDecoder.scala | 24 +- .../column/TimestampEncoderDecoder.scala | 14 +- .../TimestampWithTimezoneEncoderDecoder.scala | 2 +- .../encoders/CloseMessageEncoder.scala | 4 +- .../encoders/CredentialEncoder.scala | 14 +- .../db/postgresql/encoders/Encoder.scala | 28 ++- .../ExecutePreparedStatementEncoder.scala | 24 +- .../PreparedStatementOpeningEncoder.scala | 32 +-- .../encoders/QueryMessageEncoder.scala | 36 ++- .../encoders/StartupMessageEncoder.scala | 22 +- .../ColumnDecoderNotFoundException.scala | 20 ++ .../exceptions/DatabaseException.scala | 2 +- .../DateEncoderNotAvailableException.scala | 4 +- .../EncoderNotAvailableException.scala | 6 +- .../exceptions/GenericDatabaseException.scala | 6 +- .../exceptions/InvalidArrayException.scala | 26 +- ...issingCredentialInformationException.scala | 34 ++- .../exceptions/NotConnectedException.scala | 2 +- ...pportedAuthenticationMethodException.scala | 4 +- ...henticationChallengeCleartextMessage.scala | 4 +- .../backend/AuthenticationChallengeMD5.scala | 6 +- .../AuthenticationChallengeMessage.scala | 8 +- .../backend/AuthenticationMessage.scala | 25 +- .../backend/AuthenticationOkMessage.scala | 2 +- .../backend/AuthenticationResponseType.scala | 2 +- .../messages/backend/BindComplete.scala | 4 +- .../messages/backend/CloseComplete.scala | 2 +- .../messages/backend/ColumnData.scala | 20 +- .../backend/CommandCompleteMessage.scala | 27 +- .../messages/backend/DataRowMessage.scala | 4 +- .../messages/backend/ErrorMessage.scala | 27 +- .../messages/backend/InformationMessage.scala | 18 +- .../postgresql/messages/backend/Message.scala | 6 +- .../messages/backend/NoticeMessage.scala | 6 +- .../backend/ParameterStatusMessage.scala | 27 +- .../messages/backend/ParseComplete.scala | 2 +- .../messages/backend/ProcessData.scala | 6 +- .../backend/ReadyForQueryMessage.scala | 25 +- .../backend/RowDescriptionMessage.scala | 6 +- .../messages/frontend/CloseMessage.scala | 6 +- .../messages/frontend/CredentialMessage.scala | 15 +- .../messages/frontend/FrontendMessage.scala | 4 +- .../PreparedStatementExecuteMessage.scala | 6 +- .../frontend/PreparedStatementMessage.scala | 10 +- .../PreparedStatementOpeningMessage.scala | 6 +- .../messages/frontend/QueryMessage.scala | 6 +- .../messages/frontend/StartupMessage.scala | 6 +- .../parsers/AuthenticationStartupParser.scala | 4 +- .../parsers/BackendKeyDataParser.scala | 6 +- .../parsers/CommandCompleteParser.scala | 34 ++- .../db/postgresql/parsers/DataRowParser.scala | 10 +- .../async/db/postgresql/parsers/Decoder.scala | 4 +- .../db/postgresql/parsers/ErrorParser.scala | 6 +- .../parsers/InformationParser.scala | 37 ++- .../db/postgresql/parsers/MessageParser.scala | 10 +- .../db/postgresql/parsers/NoticeParser.scala | 6 +- .../parsers/ParameterStatusParser.scala | 10 +- .../parsers/ReadyForQueryParser.scala | 6 +- .../parsers/ReturningMessageParser.scala | 28 ++- .../parsers/RowDescriptionParser.scala | 23 +- .../pool/ConnectionObjectFactory.scala | 8 +- .../db/postgresql/pool/ConnectionPool.scala | 7 +- .../async/db/util/ArrayStreamingParser.scala | 27 +- .../util/ArrayStreamingParserDelegate.scala | 11 +- .../{postgresql => util}/ChannelUtils.scala | 27 +- .../async/db/util/ExecutorServiceUtils.scala | 2 +- .../github/mauricio/async/db/util/Log.scala | 28 ++- src/test/resources/logback.xml | 2 +- .../async/db/postgresql/ArrayTypesSpec.scala | 22 +- .../DatabaseConnectionHandlerSpec.scala | 26 +- .../db/postgresql/DatabaseTestHelper.scala | 20 +- .../db/postgresql/MessageDecoderSpec.scala | 25 +- .../async/db/postgresql/TestUtils.scala | 2 +- .../column/ArrayEncoderDecoderSpec.scala | 13 +- .../db/postgresql/parsers/ParserESpec.scala | 7 +- .../db/postgresql/parsers/ParserKSpec.scala | 5 +- .../db/postgresql/parsers/ParserSSpec.scala | 13 +- .../util/ArrayStreamingParserSpec.scala | 8 +- 101 files changed, 1004 insertions(+), 695 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala rename src/main/scala/com/github/mauricio/async/db/{postgresql => util}/ChannelUtils.scala (65%) diff --git a/pom.xml b/pom.xml index d28dfc3d..8424940e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,136 +1,136 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - com.github.mauricio - postgresql-netty - 0.1.0-SNAPSHOT + com.github.mauricio + postgresql-netty + 0.1.0-SNAPSHOT - - 2.10.1 - utf-8 - + + 2.10.1 + utf-8 + - - - scala-tools.org - Scala-tools Maven2 Repository - https://oss.sonatype.org/content/groups/scala-tools/ - - + + + scala-tools.org + Scala-tools Maven2 Repository + https://oss.sonatype.org/content/groups/scala-tools/ + + - - - - org.scala-tools - maven-scala-plugin - 2.14.2 - - - - compile - testCompile - - - - - ${scala.version} - true - - -target:jvm-1.6 - -g:vars - -deprecation - -dependencyfile - ${project.build.directory}/.scala_dependencies - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.6 - - ${project.encoding} - - - - com.mmakowski - maven-specs2-plugin - 0.4.1 - - - verify - verify - - run-specs - - - - - - + + + + org.scala-tools + maven-scala-plugin + 2.14.2 + + + + compile + testCompile + + + + + ${scala.version} + true + + -target:jvm-1.6 + -g:vars + -deprecation + -dependencyfile + ${project.build.directory}/.scala_dependencies + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + ${project.encoding} + + + + com.mmakowski + maven-specs2-plugin + 0.4.1 + + + verify + verify + + run-specs + + + + + + - + - - commons-pool - commons-pool - 1.6 - + + commons-pool + commons-pool + 1.6 + - - ch.qos.logback - logback-classic - 1.0.9 - + + ch.qos.logback + logback-classic + 1.0.9 + - - io.netty - netty - 3.6.3.Final - + + io.netty + netty + 3.6.3.Final + - - joda-time - joda-time - 2.2 - + + joda-time + joda-time + 2.2 + - - org.joda - joda-convert - 1.3.1 - + + org.joda + joda-convert + 1.3.1 + - - org.scala-lang - scala-compiler - 2.10.1 - + + org.scala-lang + scala-compiler + 2.10.1 + - - org.scala-lang - scala-library - 2.10.1 - + + org.scala-lang + scala-library + 2.10.1 + - - org.specs2 - specs2_2.10 - 1.14 - test - + + org.specs2 + specs2_2.10 + 1.14 + test + - + \ No newline at end of file diff --git a/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java b/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java index 37f4a0e6..5063b6ff 100644 --- a/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java +++ b/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java @@ -19,13 +19,11 @@ * @author Jeremy Wohl */ -public class PostgreSQLMD5Digest -{ - private static final byte[] lookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f' }; +public class PostgreSQLMD5Digest { + private static final byte[] lookup = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'}; - private PostgreSQLMD5Digest() - { + private PostgreSQLMD5Digest() { } /* @@ -38,17 +36,15 @@ private PostgreSQLMD5Digest() * * @return A 35-byte array, comprising the string "md5" and an MD5 digest. */ - public static byte[] encode(String userStr, String passwordStr, byte[] salt, Charset charset) - { - byte[] user = userStr.getBytes( charset ); - byte[] password = passwordStr.getBytes( charset ); + public static byte[] encode(String userStr, String passwordStr, byte[] salt, Charset charset) { + byte[] user = userStr.getBytes(charset); + byte[] password = passwordStr.getBytes(charset); MessageDigest md; byte[] temp_digest, pass_digest; byte[] hex_digest = new byte[35]; - try - { + try { md = MessageDigest.getInstance("MD5"); md.update(password); @@ -64,9 +60,7 @@ public static byte[] encode(String userStr, String passwordStr, byte[] salt, Cha hex_digest[0] = 'm'; hex_digest[1] = 'd'; hex_digest[2] = '5'; - } - catch (Exception e) - { + } catch (Exception e) { throw new IllegalStateException(e); } @@ -77,13 +71,11 @@ public static byte[] encode(String userStr, String passwordStr, byte[] salt, Cha /* * Turn 16-byte stream into a human-readable 32-byte hex string */ - private static void bytesToHex(byte[] bytes, byte[] hex, int offset) - { + private static void bytesToHex(byte[] bytes, byte[] hex, int offset) { int i, c, j, pos = offset; - for (i = 0; i < 16; i++) - { + for (i = 0; i < 16; i++) { c = bytes[i] & 0xFF; j = c >> 4; hex[pos++] = lookup[j]; diff --git a/src/main/scala/com/github/mauricio/async/db/Configuration.scala b/src/main/scala/com/github/mauricio/async/db/Configuration.scala index fb760bba..cbc664f6 100644 --- a/src/main/scala/com/github/mauricio/async/db/Configuration.scala +++ b/src/main/scala/com/github/mauricio/async/db/Configuration.scala @@ -26,12 +26,12 @@ object Configuration { } -case class Configuration ( val username : String, - val host : String = "localhost", - val port : Int = 5432, - val password : Option[String] = None, - val database : Option[String] = None, - val bossPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool, - val workerPool : ExecutorService = ExecutorServiceUtils.CachedThreadPool, - val charset : Charset = CharsetUtil.UTF_8 - ) +case class Configuration(val username: String, + val host: String = "localhost", + val port: Int = 5432, + val password: Option[String] = None, + val database: Option[String] = None, + val bossPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, + val workerPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, + val charset: Charset = CharsetUtil.UTF_8 + ) diff --git a/src/main/scala/com/github/mauricio/async/db/Connection.scala b/src/main/scala/com/github/mauricio/async/db/Connection.scala index 1ba6658a..97f14ad9 100644 --- a/src/main/scala/com/github/mauricio/async/db/Connection.scala +++ b/src/main/scala/com/github/mauricio/async/db/Connection.scala @@ -20,10 +20,14 @@ import concurrent.Future trait Connection { - def disconnect : Future[Connection] - def connect : Future[Map[String,String]] - def isConnected : Boolean - def sendQuery( query : String ) : Future[QueryResult] - def sendPreparedStatement( query : String, values : Array[Any] = Array.empty[Any] ) : Future[QueryResult] + def disconnect: Future[Connection] + + def connect: Future[Map[String, String]] + + def isConnected: Boolean + + def sendQuery(query: String): Future[QueryResult] + + def sendPreparedStatement(query: String, values: Array[Any] = Array.empty[Any]): Future[QueryResult] } diff --git a/src/main/scala/com/github/mauricio/async/db/QueryResult.scala b/src/main/scala/com/github/mauricio/async/db/QueryResult.scala index efc77fbc..8de3d6f3 100644 --- a/src/main/scala/com/github/mauricio/async/db/QueryResult.scala +++ b/src/main/scala/com/github/mauricio/async/db/QueryResult.scala @@ -1,16 +1,24 @@ -package com.github.mauricio.async.db - - -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 4:01 PM +/* + * Copyright 2013 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * 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. */ -class QueryResult ( val rowsAffected : Int, val statusMessage : String, val rows : Option[ResultSet] ) { +package com.github.mauricio.async.db + +class QueryResult(val rowsAffected: Int, val statusMessage: String, val rows: Option[ResultSet]) { - override def toString : String = { - "QueryResult{rows -> %s,status -> %s}".format( this.rowsAffected, this.statusMessage ) + override def toString: String = { + "QueryResult{rows -> %s,status -> %s}".format(this.rowsAffected, this.statusMessage) } } diff --git a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala index c9ae686b..f8c2ffcc 100644 --- a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala +++ b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala @@ -18,8 +18,8 @@ package com.github.mauricio.async.db trait ResultSet extends IndexedSeq[Array[Any]] { - def apply( name : String, row : Int ) : Any + def apply(name: String, row: Int): Any - def apply( column : Int, row : Int ) : Any + def apply(column: Int, row: Int): Any -} +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index e00b7f8f..af966e14 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -14,10 +14,12 @@ * under the License. */ -package com.github.mauricio.postgresql +package com.github.mauricio.async.db.postgresql +import com.github.mauricio.async.db.postgresql.exceptions.{MissingCredentialInformationException, NotConnectedException, GenericDatabaseException} import com.github.mauricio.async.db.util.{Log, ExecutorServiceUtils} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} +import com.github.mauricio.postgresql.MessageEncoder import concurrent.{Future, Promise} import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap @@ -29,7 +31,6 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ -import com.github.mauricio.async.db.postgresql.exceptions.{MissingCredentialInformationException, NotConnectedException, GenericDatabaseException} object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -39,7 +40,7 @@ object DatabaseConnectionHandler { class DatabaseConnectionHandler ( - val configuration : Configuration = Configuration.Default ) extends SimpleChannelHandler with Connection { + val configuration: Configuration = Configuration.Default) extends SimpleChannelHandler with Connection { import DatabaseConnectionHandler._ @@ -47,7 +48,7 @@ class DatabaseConnectionHandler "user" -> configuration.username, "database" -> configuration.database, "application_name" -> DatabaseConnectionHandler.Name, - "client_encoding" -> configuration.charset.name() , + "client_encoding" -> configuration.charset.name(), "DateStyle" -> "ISO", "extra_float_digits" -> "2") @@ -79,7 +80,7 @@ class DatabaseConnectionHandler override def getPipeline(): ChannelPipeline = { Channels.pipeline( new MessageDecoder(configuration.charset), - new MessageEncoder( configuration.charset ), + new MessageEncoder(configuration.charset), DatabaseConnectionHandler.this) } @@ -108,16 +109,16 @@ class DatabaseConnectionHandler val closingPromise = Promise[Connection]() - if ( this.currentChannel.isConnected ) { + if (this.currentChannel.isConnected) { this.currentChannel.write(CloseMessage.Instance).addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { - if ( future.getCause != null ) { + if (future.getCause != null) { closingPromise.failure(future.getCause) } else { future.getChannel.close().addListener(new ChannelFutureListener { def operationComplete(internalFuture: ChannelFuture) { - if ( internalFuture.isSuccess ) { + if (internalFuture.isSuccess) { closingPromise.success(DatabaseConnectionHandler.this) } else { closingPromise.failure(internalFuture.getCause) @@ -258,7 +259,7 @@ class DatabaseConnectionHandler log.error("Error on connection", e) - if ( !this.connectionFuture.isCompleted ) { + if (!this.connectionFuture.isCompleted) { this.connectionFuture.failure(e) this.disconnect } else { @@ -312,7 +313,7 @@ class DatabaseConnectionHandler } private def onParameterStatus(m: ParameterStatusMessage) { - this.parameterStatus.put( m.key, m.value ) + this.parameterStatus.put(m.key, m.value) } private def onDataRow(m: DataRowMessage) { @@ -334,17 +335,17 @@ class DatabaseConnectionHandler this.parsedStatements.containsKey(query) } - private def onAuthenticationResponse( channel : Channel, message : AuthenticationMessage ) { + private def onAuthenticationResponse(channel: Channel, message: AuthenticationMessage) { message match { - case m : AuthenticationOkMessage => { + case m: AuthenticationOkMessage => { log.debug("Successfully logged in to database") this.authenticated = true } - case m : AuthenticationChallengeCleartextMessage => { + case m: AuthenticationChallengeCleartextMessage => { channel.write(this.credential(m)) } - case m : AuthenticationChallengeMD5 => { + case m: AuthenticationChallengeMD5 => { channel.write(this.credential(m)) } } @@ -359,8 +360,8 @@ class DatabaseConnectionHandler } } - private def credential( authenticationMessage : AuthenticationChallengeMessage ) : CredentialMessage = { - if ( configuration.username != null && configuration.password.isDefined ) { + private def credential(authenticationMessage: AuthenticationChallengeMessage): CredentialMessage = { + if (configuration.username != null && configuration.password.isDefined) { new CredentialMessage( configuration.username, configuration.password.get, @@ -371,7 +372,7 @@ class DatabaseConnectionHandler throw new MissingCredentialInformationException( this.configuration.username, this.configuration.password, - authenticationMessage.challengeType ) + authenticationMessage.challengeType) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala index 51d872f7..a34075e9 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala @@ -14,10 +14,10 @@ * under the License. */ -package com.github.mauricio.postgresql +package com.github.mauricio.async.db.postgresql +import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParser} import com.github.mauricio.async.db.util.Log -import com.github.mauricio.postgresql.parsers.{MessageParser, AuthenticationStartupParser} import java.nio.charset.Charset import messages.backend.Message import org.jboss.netty.buffer.ChannelBuffer @@ -28,13 +28,13 @@ object MessageDecoder { val log = Log.get[MessageDecoder] } -class MessageDecoder ( charset : Charset ) extends FrameDecoder { +class MessageDecoder(charset: Charset) extends FrameDecoder { private val parser = new MessageParser(charset) - override def decode(ctx: ChannelHandlerContext, c: Channel, b: ChannelBuffer) : Object = { + override def decode(ctx: ChannelHandlerContext, c: Channel, b: ChannelBuffer): Object = { - if ( b.readableBytes() >= 5 ) { + if (b.readableBytes() >= 5) { b.markReaderIndex() @@ -42,13 +42,13 @@ class MessageDecoder ( charset : Charset ) extends FrameDecoder { val lengthWithSelf = b.readInt() val length = lengthWithSelf - 4 - if ( b.readableBytes() >= length ) { + if (b.readableBytes() >= length) { code match { case Message.Authentication => { - AuthenticationStartupParser.parseMessage( b ) + AuthenticationStartupParser.parseMessage(b) } case _ => { - parser.parse( code, b.readSlice( length ) ) + parser.parse(code, b.readSlice(length)) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index 6bc6531c..6f0a7eda 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -16,41 +16,41 @@ package com.github.mauricio.postgresql +import com.github.mauricio.async.db.postgresql.encoders._ +import com.github.mauricio.async.db.postgresql.exceptions.EncoderNotAvailableException +import com.github.mauricio.async.db.postgresql.messages.frontend._ import com.github.mauricio.async.db.util.Log -import encoders._ import java.nio.charset.Charset -import messages.frontend._ import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder -import com.github.mauricio.async.db.postgresql.exceptions.EncoderNotAvailableException -class MessageEncoder( charset : Charset ) extends OneToOneEncoder { +class MessageEncoder(charset: Charset) extends OneToOneEncoder { val log = Log.getByName("MessageEncoder") - val encoders : Map[Class[_],Encoder] = Map( + val encoders: Map[Class[_], Encoder] = Map( classOf[CloseMessage] -> CloseMessageEncoder, - classOf[PreparedStatementExecuteMessage] -> new ExecutePreparedStatementEncoder( charset ), - classOf[PreparedStatementOpeningMessage] -> new PreparedStatementOpeningEncoder( charset ), + classOf[PreparedStatementExecuteMessage] -> new ExecutePreparedStatementEncoder(charset), + classOf[PreparedStatementOpeningMessage] -> new PreparedStatementOpeningEncoder(charset), classOf[StartupMessage] -> new StartupMessageEncoder(charset), classOf[QueryMessage] -> new QueryMessageEncoder(charset), - classOf[CredentialMessage] -> new CredentialEncoder( charset ) + classOf[CredentialMessage] -> new CredentialEncoder(charset) ) override def encode(ctx: ChannelHandlerContext, channel: Channel, msg: AnyRef): ChannelBuffer = { val buffer = msg match { - case message : FrontendMessage => { - val option = this.encoders.get( message.getClass ) - if ( option.isDefined ) { + case message: FrontendMessage => { + val option = this.encoders.get(message.getClass) + if (option.isDefined) { option.get.encode(message) } else { - throw new EncoderNotAvailableException( message ) + throw new EncoderNotAvailableException(message) } } case _ => { - throw new IllegalArgumentException( "Can not encode message %s".format( msg ) ) + throw new IllegalArgumentException("Can not encode message %s".format(msg)) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala index 33f49cfb..54764859 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala @@ -14,23 +14,23 @@ * under the License. */ -package com.github.mauricio.postgresql +package com.github.mauricio.async.db.postgresql import collection.mutable.ArrayBuffer import com.github.mauricio.async.db.ResultSet +import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import messages.backend.ColumnData import org.jboss.netty.buffer.ChannelBuffer object MutableQuery { val log = Log.get[MutableQuery] } -class MutableQuery ( val columnTypes : Array[ColumnData], charset : Charset ) extends ResultSet { +class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset) extends ResultSet { private val rows = new ArrayBuffer[Array[Any]]() - private val columnMapping : Map[String, Int] = this.columnTypes.map { + private val columnMapping: Map[String, Int] = this.columnTypes.map { columnData => (columnData.name, columnData.columnNumber - 1) }.toMap @@ -41,17 +41,17 @@ class MutableQuery ( val columnTypes : Array[ColumnData], charset : Charset ) ex def update(idx: Int, elem: Array[Any]) = this.rows(idx) = elem - def addRawRow( row : Array[ChannelBuffer] ) { + def addRawRow(row: Array[ChannelBuffer]) { val realRow = new Array[Any](row.length) 0.until(row.length).foreach { index => - realRow(index) = if ( row(index) == null ) { + realRow(index) = if (row(index) == null) { null } else { - this.columnTypes(index).decoder.decode( row(index).toString( charset ) ) + this.columnTypes(index).decoder.decode(row(index).toString(charset)) } } @@ -59,16 +59,16 @@ class MutableQuery ( val columnTypes : Array[ColumnData], charset : Charset ) ex this.rows += realRow } - def getValue( columnNumber : Int, rowNumber : Int ) : Any = { - this.rows( rowNumber )(columnNumber) + def getValue(columnNumber: Int, rowNumber: Int): Any = { + this.rows(rowNumber)(columnNumber) } - def getValue( columnName : String, rowNumber : Int ) : Any = { - this.rows( rowNumber )( this.columnMapping( columnName ) ) + def getValue(columnName: String, rowNumber: Int): Any = { + this.rows(rowNumber)(this.columnMapping(columnName)) } - def apply( name : String, row : Int ) : Any = this.getValue( name, row) + def apply(name: String, row: Int): Any = this.getValue(name, row) - def apply( column : Int, row : Int ) : Any = this.getValue(column, row) + def apply(column: Int, row: Int): Any = this.getValue(column, row) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala index 94f0f208..d165c723 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala @@ -17,17 +17,16 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} -import com.github.mauricio.postgresql.column.ColumnEncoderDecoder import scala.collection.IndexedSeq -import scala.collection.mutable.{ArrayBuffer,Stack} +import scala.collection.mutable.{ArrayBuffer, Stack} -class ArrayEncoderDecoder( private val encoder : ColumnEncoderDecoder ) extends ColumnEncoderDecoder { +class ArrayEncoderDecoder(private val encoder: ColumnEncoderDecoder) extends ColumnEncoderDecoder { override def decode(value: String): IndexedSeq[Any] = { val stack = new Stack[ArrayBuffer[Any]]() - var current : ArrayBuffer[Any] = null - var result : IndexedSeq[Any] = null + var current: ArrayBuffer[Any] = null + var result: IndexedSeq[Any] = null val delegate = new ArrayStreamingParserDelegate { override def arrayEnded { result = stack.pop() @@ -51,7 +50,7 @@ class ArrayEncoderDecoder( private val encoder : ColumnEncoderDecoder ) extends case None => {} } - stack.push( current ) + stack.push(current) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala index f255a161..beca86a1 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala @@ -14,10 +14,10 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column object BigDecimalEncoderDecoder extends ColumnEncoderDecoder { - override def decode( value : String ) : Any = BigDecimal(value) + override def decode(value: String): Any = BigDecimal(value) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala index 162999d4..2d1a0733 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala @@ -14,19 +14,19 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column object BooleanEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = { - if ( "t" == value ) { + if ("t" == value) { true } else { false } } - override def encode( value : Any ) : String = { + override def encode(value: Any): String = { val result = value.asInstanceOf[Boolean] if (result) { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala new file mode 100644 index 00000000..ca2356e3 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.column + +object CharEncoderDecoder extends ColumnEncoderDecoder { + + override def decode(value: String): Any = value.charAt(0) + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala index 613e068a..140cd392 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala @@ -14,33 +14,53 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column import org.joda.time._ import scala.Some -class ColumnDecoderNotFoundException( kind : Int ) - extends IllegalArgumentException( "There is no decoder available for kind %s".format(kind) ) - object ColumnEncoderDecoder { val Bigserial = 20 val Char = 18 + val CharArray = 1002 val Smallint = 21 + val SmallintArray = 1005 val Integer = 23 - val Numeric = 1700 // Decimal is the same as Numeric on PostgreSQL + val IntegerArray = 1007 + val Numeric = 1700 + // Decimal is the same as Numeric on PostgreSQL + val NumericArray = 1231 val Real = 700 + val RealArray = 1021 val Double = 701 + val DoubleArray = 1022 val Serial = 23 val Bpchar = 1042 - val Varchar = 1043 // Char is the same as Varchar on PostgreSQL + val BpcharArray = 1014 + val Varchar = 1043 + // Char is the same as Varchar on PostgreSQL + val VarcharArray = 1015 val Text = 25 + val TextArray = 1009 val Timestamp = 1114 + val TimestampArray = 1115 val TimestampWithTimezone = 1184 + val TimestampWithTimezoneArray = 1185 val Date = 1082 + val DateArray = 1182 val Time = 1083 + val TimeArray = 1183 val TimeWithTimezone = 1266 + val TimeWithTimezoneArray = 1270 val Boolean = 16 + val BooleanArray = 1000 + + val OIDArray = 1028 + val MoneyArray = 791 + val NameArray = 1003 + val UUIDArray = 2951 + val XMLArray = 143 private val classes = Map[Class[_], Int]( classOf[Int] -> Integer, @@ -81,40 +101,70 @@ object ColumnEncoderDecoder { def decoderFor(kind: Int): ColumnEncoderDecoder = { kind match { case Boolean => BooleanEncoderDecoder - case Char => StringEncoderDecoder + case BooleanArray => new ArrayEncoderDecoder(BooleanEncoderDecoder) + case Char => CharEncoderDecoder + case CharArray => new ArrayEncoderDecoder(CharEncoderDecoder) case Bigserial => LongEncoderDecoder - case Smallint => IntegerEncoderDecoder + case Smallint => ShortEncoderDecoder + case SmallintArray => new ArrayEncoderDecoder(ShortEncoderDecoder) case Integer => IntegerEncoderDecoder - case Text => StringEncoderDecoder + case IntegerArray => new ArrayEncoderDecoder(IntegerEncoderDecoder) + case Numeric => BigDecimalEncoderDecoder + case NumericArray => new ArrayEncoderDecoder(BigDecimalEncoderDecoder) case Real => FloatEncoderDecoder + case RealArray => new ArrayEncoderDecoder(FloatEncoderDecoder) case Double => DoubleEncoderDecoder + case DoubleArray => new ArrayEncoderDecoder(DoubleEncoderDecoder) + case Text => StringEncoderDecoder + case TextArray => new ArrayEncoderDecoder(StringEncoderDecoder) case Varchar => StringEncoderDecoder + case VarcharArray => new ArrayEncoderDecoder(StringEncoderDecoder) case Bpchar => StringEncoderDecoder - case Numeric => BigDecimalEncoderDecoder + case BpcharArray => new ArrayEncoderDecoder(StringEncoderDecoder) case Timestamp => TimestampEncoderDecoder.Instance + case TimestampArray => new ArrayEncoderDecoder(TimestampEncoderDecoder.Instance) case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder + case TimestampWithTimezoneArray => new ArrayEncoderDecoder(TimestampWithTimezoneEncoderDecoder) case Date => DateEncoderDecoder + case DateArray => new ArrayEncoderDecoder(DateEncoderDecoder) case Time => TimeEncoderDecoder.Instance + case TimeArray => new ArrayEncoderDecoder(TimeEncoderDecoder.Instance) case TimeWithTimezone => TimeWithTimezoneEncoderDecoder + case TimeWithTimezoneArray => new ArrayEncoderDecoder(TimeWithTimezoneEncoderDecoder) + + case OIDArray => new ArrayEncoderDecoder(StringEncoderDecoder) + case MoneyArray => new ArrayEncoderDecoder(StringEncoderDecoder) + case NameArray => new ArrayEncoderDecoder(StringEncoderDecoder) + case UUIDArray => new ArrayEncoderDecoder(StringEncoderDecoder) + case XMLArray => new ArrayEncoderDecoder(StringEncoderDecoder) + case _ => StringEncoderDecoder } } - def kindFor( clazz : Class[_] ) : Int = { + def kindFor(clazz: Class[_]): Int = { this.classes.get(clazz).getOrElse { - this.classes.find( entry => entry._1.isAssignableFrom(clazz) ) match { + this.classes.find(entry => entry._1.isAssignableFrom(clazz)) match { case Some(parent) => parent._2 case None => 0 } } } - def decode( kind : Int, value : String ) : Any = { - decoderFor(kind).decode(value) + def decode(kind: Int, value: String): Any = { + if (value == null || "NULL" == value) { + null + } else { + decoderFor(kind).decode(value) + } } - def encode( value : Any ) : String = { - decoderFor( kindFor( value.getClass ) ).encode(value) + def encode(value: Any): String = { + if (value == null) { + "NULL" + } else { + decoderFor(kindFor(value.getClass)).encode(value) + } } } @@ -123,7 +173,7 @@ trait ColumnEncoderDecoder { def decode(value: String): Any = value - def encode(value: Any) : String = value.toString + def encode(value: Any): String = value.toString } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala index f33230b6..041b4ec8 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala @@ -14,11 +14,11 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column +import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException import org.joda.time.format.DateTimeFormat import org.joda.time.{ReadableInstant, LocalDate} -import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException object DateEncoderDecoder extends ColumnEncoderDecoder { @@ -28,10 +28,10 @@ object DateEncoderDecoder extends ColumnEncoderDecoder { this.formatter.parseLocalDate(value) } - override def encode( value : Any ) : String = { + override def encode(value: Any): String = { value match { - case d : java.sql.Date => this.formatter.print( new LocalDate(d) ) - case d : ReadableInstant => this.formatter.print(d) + case d: java.sql.Date => this.formatter.print(new LocalDate(d)) + case d: ReadableInstant => this.formatter.print(d) case _ => throw new DateEncoderNotAvailableException(value) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala index 2864e6b1..6d08995e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala @@ -1,11 +1,21 @@ -package com.github.mauricio.postgresql.column - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:47 AM +/* + * 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.column + object DoubleEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Double = value.toDouble } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala index 4688de78..a3eb7172 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column object FloatEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Float = value.toFloat diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala index 67cf25bf..07fc1653 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala @@ -1,11 +1,21 @@ -package com.github.mauricio.postgresql.column - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:39 AM +/* + * 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.column + object IntegerEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Int = value.toInt diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala index 80fe9733..8acc9397 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column object LongEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Long = value.toLong diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala new file mode 100644 index 00000000..9362bdad --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.column + +object ShortEncoderDecoder extends ColumnEncoderDecoder { + + override def decode(value: String): Any = value.toShort + +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala index e5964274..7c1115b8 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala @@ -1,11 +1,21 @@ -package com.github.mauricio.postgresql.column - -/** - * User: Maurício Linhares - * Date: 3/5/12 - * Time: 9:45 AM +/* + * 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.column + object StringEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): String = value } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala index 3eadf15d..fd239c99 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column import org.joda.time.LocalTime import org.joda.time.format.DateTimeFormat @@ -33,8 +33,8 @@ class TimeEncoderDecoder extends ColumnEncoderDecoder { parser.parseLocalTime(value) } - override def encode( value : Any ) : String = { - this.parser.print( value.asInstanceOf[LocalTime] ) + override def encode(value: Any): String = { + this.parser.print(value.asInstanceOf[LocalTime]) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala index 39498c1e..60a81eb2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala @@ -1,12 +1,22 @@ -package com.github.mauricio.postgresql.column +/* + * 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. + */ -import org.joda.time.format.DateTimeFormat +package com.github.mauricio.async.db.postgresql.column -/** - * User: Maurício Linhares - * Date: 3/11/12 - * Time: 5:35 PM - */ +import org.joda.time.format.DateTimeFormat object TimeWithTimezoneEncoderDecoder extends TimeEncoderDecoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala index 6e88cc74..57e25971 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala @@ -14,13 +14,13 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column +import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException import java.sql.Timestamp import java.util.{Calendar, Date} import org.joda.time.format.DateTimeFormat import org.joda.time.{ReadableDateTime, DateTime} -import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException object TimestampEncoderDecoder { val Instance = new TimestampEncoderDecoder() @@ -36,12 +36,12 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { formatter.parseDateTime(value) } - override def encode( value : Any ) : String = { + override def encode(value: Any): String = { value match { - case t : Timestamp => this.formatter.print( new DateTime(t) ) - case t : Date => this.formatter.print( new DateTime(t) ) - case t : Calendar => this.formatter.print( new DateTime(t) ) - case t : ReadableDateTime => this.formatter.print(t) + case t: Timestamp => this.formatter.print(new DateTime(t)) + case t: Date => this.formatter.print(new DateTime(t)) + case t: Calendar => this.formatter.print(new DateTime(t)) + case t: ReadableDateTime => this.formatter.print(t) case _ => throw new DateEncoderNotAvailableException(value) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala index 5c01f129..b24214ac 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.column +package com.github.mauricio.async.db.postgresql.column import org.joda.time.format.DateTimeFormat diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala index b70f8c37..c261f792 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.encoders +package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.postgresql.messages.frontend.FrontendMessage +import com.github.mauricio.async.db.postgresql.messages.frontend.FrontendMessage import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} object CloseMessageEncoder extends Encoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index 77a09e5d..e1f134db 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -14,16 +14,16 @@ * under the License. */ -package com.github.mauricio.postgresql.encoders +package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} -import com.github.mauricio.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.{Message, AuthenticationResponseType} +import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} +import com.github.mauricio.async.db.postgresql.util.PostgreSQLMD5Digest +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.postgresql.util.PostgreSQLMD5Digest -class CredentialEncoder( charset : Charset ) extends Encoder { +class CredentialEncoder(charset: Charset) extends Encoder { def encode(message: FrontendMessage): ChannelBuffer = { @@ -38,7 +38,7 @@ class CredentialEncoder( charset : Charset ) extends Encoder { credentialMessage.username, credentialMessage.password, credentialMessage.salt.get, - charset ) + charset) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala index ab0e6f57..a35d0dcc 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala @@ -1,16 +1,26 @@ -package com.github.mauricio.postgresql.encoders +/* + * 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. + */ -import com.github.mauricio.postgresql.messages.frontend.FrontendMessage -import org.jboss.netty.buffer.ChannelBuffer +package com.github.mauricio.async.db.postgresql.encoders -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 7:16 PM - */ +import com.github.mauricio.async.db.postgresql.messages.frontend.FrontendMessage +import org.jboss.netty.buffer.ChannelBuffer trait Encoder { - def encode( message : FrontendMessage ) : ChannelBuffer + def encode(message: FrontendMessage): ChannelBuffer } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 2e1f2eb0..4cddae40 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -14,16 +14,16 @@ * under the License. */ -package com.github.mauricio.postgresql.encoders +package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.column.ColumnEncoderDecoder -import com.github.mauricio.postgresql.messages.backend.Message -import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class ExecutePreparedStatementEncoder ( charset : Charset ) extends Encoder { +class ExecutePreparedStatementEncoder(charset: Charset) extends Encoder { def encode(message: FrontendMessage): ChannelBuffer = { @@ -45,13 +45,13 @@ class ExecutePreparedStatementEncoder ( charset : Charset ) extends Encoder { bindBuffer.writeShort(m.values.length) - for ( value <- m.values ) { - if ( value == null ) { + for (value <- m.values) { + if (value == null) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes( charset ) + val encoded = ColumnEncoderDecoder.encode(value).getBytes(charset) bindBuffer.writeInt(encoded.length) - bindBuffer.writeBytes( encoded ) + bindBuffer.writeBytes(encoded) } } @@ -60,7 +60,7 @@ class ExecutePreparedStatementEncoder ( charset : Charset ) extends Encoder { ChannelUtils.writeLength(bindBuffer) val executeLength = 1 + 4 + queryBytes.length + 1 + 4 - val executeBuffer = ChannelBuffers.buffer( executeLength ) + val executeBuffer = ChannelBuffers.buffer(executeLength) executeBuffer.writeByte(Message.Execute) executeBuffer.writeInt(executeLength - 1) @@ -72,7 +72,7 @@ class ExecutePreparedStatementEncoder ( charset : Charset ) extends Encoder { val closeLength = 1 + 4 + 1 + queryBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) closeBuffer.writeByte(Message.CloseStatementOrPortal) - closeBuffer.writeInt( closeLength - 1 ) + closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') closeBuffer.writeBytes(queryBytes) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index f0609dbc..ae67f189 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -14,16 +14,16 @@ * under the License. */ -package com.github.mauricio.postgresql.encoders +package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.column.ColumnEncoderDecoder -import com.github.mauricio.postgresql.messages.backend.Message -import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { +class PreparedStatementOpeningEncoder(charset: Charset) extends Encoder { override def encode(message: FrontendMessage): ChannelBuffer = { @@ -32,7 +32,7 @@ class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { val queryBytes = m.query.getBytes(charset) val columnCount = m.valueTypes.size - val parseBuffer = ChannelBuffers.dynamicBuffer( 1024 ) + val parseBuffer = ChannelBuffers.dynamicBuffer(1024) parseBuffer.writeByte(Message.Parse) parseBuffer.writeInt(0) @@ -44,8 +44,8 @@ class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { parseBuffer.writeShort(columnCount) - for ( kind <- m.valueTypes ) { - parseBuffer.writeInt(kind) + for (kind <- m.valueTypes) { + parseBuffer.writeInt(kind) } ChannelUtils.writeLength(parseBuffer) @@ -64,13 +64,13 @@ class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { bindBuffer.writeShort(m.values.length) - for ( value <- m.values ) { - if ( value == null ) { + for (value <- m.values) { + if (value == null) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes( charset ) + val encoded = ColumnEncoderDecoder.encode(value).getBytes(charset) bindBuffer.writeInt(encoded.length) - bindBuffer.writeBytes( encoded ) + bindBuffer.writeBytes(encoded) } } @@ -79,7 +79,7 @@ class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { ChannelUtils.writeLength(bindBuffer) val describeLength = 1 + 4 + 1 + queryBytes.length + 1 - val describeBuffer = ChannelBuffers.buffer( describeLength ) + val describeBuffer = ChannelBuffers.buffer(describeLength) describeBuffer.writeByte(Message.Describe) describeBuffer.writeInt(describeLength - 1) @@ -89,7 +89,7 @@ class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { describeBuffer.writeByte(0) val executeLength = 1 + 4 + queryBytes.length + 1 + 4 - val executeBuffer = ChannelBuffers.buffer( executeLength ) + val executeBuffer = ChannelBuffers.buffer(executeLength) executeBuffer.writeByte(Message.Execute) executeBuffer.writeInt(executeLength - 1) @@ -101,7 +101,7 @@ class PreparedStatementOpeningEncoder ( charset : Charset ) extends Encoder { val closeLength = 1 + 4 + 1 + queryBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) closeBuffer.writeByte(Message.CloseStatementOrPortal) - closeBuffer.writeInt( closeLength - 1 ) + closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') closeBuffer.writeBytes(queryBytes) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index 19c185af..d40a476a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -1,29 +1,39 @@ -package com.github.mauricio.postgresql.encoders +/* + * 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.encoders -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.Message -import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, QueryMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, FrontendMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -/** - * User: Maurício Linhares - * Date: 3/3/12 - * Time: 8:32 PM - */ - -class QueryMessageEncoder ( charset : Charset ) extends Encoder { +class QueryMessageEncoder(charset: Charset) extends Encoder { override def encode(message: FrontendMessage): ChannelBuffer = { val m = message.asInstanceOf[QueryMessage] val buffer = ChannelBuffers.dynamicBuffer() - buffer.writeByte( Message.Query ) + buffer.writeByte(Message.Query) buffer.writeInt(0) ChannelUtils.writeCString(m.query, buffer, charset) - ChannelUtils.writeLength( buffer ) + ChannelUtils.writeLength(buffer) buffer } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index 29d62349..71f3b625 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -14,14 +14,14 @@ * under the License. */ -package com.github.mauricio.postgresql.encoders +package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.frontend.{FrontendMessage, StartupMessage} +import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, StartupMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class StartupMessageEncoder (charset : Charset) extends Encoder { +class StartupMessageEncoder(charset: Charset) extends Encoder { //private val log = Log.getByName("StartupMessageEncoder") @@ -31,19 +31,19 @@ class StartupMessageEncoder (charset : Charset) extends Encoder { val buffer = ChannelBuffers.dynamicBuffer() buffer.writeInt(0) - buffer.writeShort( 3 ) - buffer.writeShort( 0 ) + buffer.writeShort(3) + buffer.writeShort(0) startup.parameters.foreach { pair => pair._2 match { - case value : String => { - ChannelUtils.writeCString( pair._1, buffer, charset ) - ChannelUtils.writeCString( value, buffer, charset ) + case value: String => { + ChannelUtils.writeCString(pair._1, buffer, charset) + ChannelUtils.writeCString(value, buffer, charset) } case Some(value) => { - ChannelUtils.writeCString( pair._1, buffer, charset ) - ChannelUtils.writeCString( value.toString, buffer, charset ) + ChannelUtils.writeCString(pair._1, buffer, charset) + ChannelUtils.writeCString(value.toString, buffer, charset) } case _ => {} } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala new file mode 100644 index 00000000..043ea118 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala @@ -0,0 +1,20 @@ +/* + * 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.exceptions + +class ColumnDecoderNotFoundException(kind: Int) + extends IllegalArgumentException("There is no decoder available for kind %s".format(kind)) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala index 342e5c2b..afb48486 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.exceptions -class DatabaseException(message : String) extends RuntimeException(message) \ No newline at end of file +class DatabaseException(message: String) extends RuntimeException(message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala index fc458177..4534caad 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala @@ -5,5 +5,5 @@ package com.github.mauricio.async.db.postgresql.exceptions * Date: 4/4/13 * Time: 12:36 AM */ -class DateEncoderNotAvailableException( value : Any ) - extends DatabaseException( "There is no encoder for value [%s] of type %s".format(value, value.getClass.getCanonicalName) ) +class DateEncoderNotAvailableException(value: Any) + extends DatabaseException("There is no encoder for value [%s] of type %s".format(value, value.getClass.getCanonicalName)) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala index 68d18179..a0584cdd 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.exceptions -import com.github.mauricio.postgresql.messages.frontend.FrontendMessage +import com.github.mauricio.async.db.postgresql.messages.frontend.FrontendMessage -class EncoderNotAvailableException( message : FrontendMessage ) - extends DatabaseException( "Encoder not available for name %s".format( message.kind ) ) +class EncoderNotAvailableException(message: FrontendMessage) + extends DatabaseException("Encoder not available for name %s".format(message.kind)) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala index 58ce88b9..33339b24 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.exceptions -import com.github.mauricio.postgresql.messages.backend.ErrorMessage +import com.github.mauricio.async.db.postgresql.messages.backend.ErrorMessage -class GenericDatabaseException( val errorMessage : ErrorMessage ) - extends DatabaseException( errorMessage.toString ) \ No newline at end of file +class GenericDatabaseException(val errorMessage: ErrorMessage) + extends DatabaseException(errorMessage.toString) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala index 8f4d5494..120d1f64 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala @@ -1,10 +1,20 @@ -package com.github.mauricio.async.db.postgresql.exceptions - -/** - * User: mauricio - * Date: 4/19/13 - * Time: 12:54 PM +/* + * 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. */ -class InvalidArrayException( message : String ) extends DatabaseException(message) { -} + +package com.github.mauricio.async.db.postgresql.exceptions + +class InvalidArrayException(message: String) extends DatabaseException(message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala index 7a1e03a4..3bdf8ed9 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala @@ -1,17 +1,29 @@ +/* + * 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.exceptions -import com.github.mauricio.postgresql.messages.backend.AuthenticationResponseType +import com.github.mauricio.async.db.postgresql.messages.backend.AuthenticationResponseType -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:39 AM - */ -class MissingCredentialInformationException ( - val username : String, - val password : Option[String], - val authenticationResponseType : AuthenticationResponseType.AuthenticationResponseType ) - extends DatabaseException ( +class MissingCredentialInformationException( + val username: String, + val password: Option[String], + val authenticationResponseType: AuthenticationResponseType.AuthenticationResponseType) + extends DatabaseException( "Username and password were required by auth type %s but are not available (username=[%s] password=[%s]".format( authenticationResponseType, username, diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala index 2c4b0a74..7bd48c4a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.exceptions -class NotConnectedException ( message : String ) extends DatabaseException (message) \ No newline at end of file +class NotConnectedException(message: String) extends DatabaseException(message) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala index 15e7ca5e..fa1d9418 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala @@ -16,5 +16,5 @@ package com.github.mauricio.async.db.postgresql.exceptions -class UnsupportedAuthenticationMethodException ( val authenticationType : Int ) - extends DatabaseException ( "Unknown authentication method -> '%s'".format(authenticationType) ) \ No newline at end of file +class UnsupportedAuthenticationMethodException(val authenticationType: Int) + extends DatabaseException("Unknown authentication method -> '%s'".format(authenticationType)) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala index 3fd2c4eb..67ad7d31 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala @@ -14,11 +14,11 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object AuthenticationChallengeCleartextMessage { val Instance = new AuthenticationChallengeCleartextMessage() } class AuthenticationChallengeCleartextMessage - extends AuthenticationChallengeMessage( AuthenticationResponseType.Cleartext, None ) \ No newline at end of file + extends AuthenticationChallengeMessage(AuthenticationResponseType.Cleartext, None) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala index b8ef1b2d..8183851f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend -class AuthenticationChallengeMD5( salt : Array[Byte] ) - extends AuthenticationChallengeMessage( AuthenticationResponseType.MD5, Some(salt) ) \ No newline at end of file +class AuthenticationChallengeMD5(salt: Array[Byte]) + extends AuthenticationChallengeMessage(AuthenticationResponseType.MD5, Some(salt)) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala index b234eb4d..438b0daf 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend -class AuthenticationChallengeMessage ( - val challengeType : AuthenticationResponseType.AuthenticationResponseType, - val salt : Option[Array[Byte]] ) +class AuthenticationChallengeMessage( + val challengeType: AuthenticationResponseType.AuthenticationResponseType, + val salt: Option[Array[Byte]]) extends AuthenticationMessage \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala index c20e2c5c..860caf5b 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala @@ -1,8 +1,19 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:30 AM +/* + * 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. */ -abstract class AuthenticationMessage extends Message( Message.Authentication ) \ No newline at end of file + +package com.github.mauricio.async.db.postgresql.messages.backend + +abstract class AuthenticationMessage extends Message(Message.Authentication) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala index 3ee8d7bd..85a6905c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object AuthenticationOkMessage { val Instance = new AuthenticationOkMessage() diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala index 718a89f2..ca18ec07 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object AuthenticationResponseType extends Enumeration { type AuthenticationResponseType = Value diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala index 316e9d05..0eece8dd 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala @@ -14,10 +14,10 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object BindComplete { val Instance = new BindComplete() } -class BindComplete extends Message( Message.BindComplete ) +class BindComplete extends Message(Message.BindComplete) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala index 139e4af2..0c5af081 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object CloseComplete { val Instance = new CloseComplete() diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala index f9c7fefd..7e991d56 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala @@ -14,19 +14,19 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend -import com.github.mauricio.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder class ColumnData( - val name: String, - val tableObjectId: Int, - val columnNumber: Int, - val dataType: Int, - val dataTypeSize: Int, - val dataTypeModifier: Int, - val fieldFormat: Int ) { + val name: String, + val tableObjectId: Int, + val columnNumber: Int, + val dataType: Int, + val dataTypeSize: Int, + val dataTypeModifier: Int, + val fieldFormat: Int) { - val decoder = ColumnEncoderDecoder.decoderFor( this.dataType ) + val decoder = ColumnEncoderDecoder.decoderFor(this.dataType) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala index ec346394..d1504a13 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala @@ -1,9 +1,20 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:04 AM +/* + * 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. */ -case class CommandCompleteMessage ( val rowsAffected : Int, val statusMessage : String ) - extends Message( Message.CommandComplete ) \ No newline at end of file + +package com.github.mauricio.async.db.postgresql.messages.backend + +case class CommandCompleteMessage(val rowsAffected: Int, val statusMessage: String) + extends Message(Message.CommandComplete) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala index 073b1d09..32a592ec 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala @@ -14,8 +14,8 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend import org.jboss.netty.buffer.ChannelBuffer -case class DataRowMessage( val values : Array[ChannelBuffer] ) extends Message( Message.DataRow ) \ No newline at end of file +case class DataRowMessage(val values: Array[ChannelBuffer]) extends Message(Message.DataRow) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala index 31f67ea6..6389dea2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala @@ -1,9 +1,20 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 12:57 AM +/* + * 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. */ -class ErrorMessage ( fields : Map[Char,String] ) - extends InformationMessage( Message.Error, fields ) + +package com.github.mauricio.async.db.postgresql.messages.backend + +class ErrorMessage(fields: Map[Char, String]) + extends InformationMessage(Message.Error, fields) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala index 269432d7..aec73521 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object InformationMessage { @@ -44,17 +44,21 @@ object InformationMessage { Routine -> "Routine" ) - def fieldName( name : Char ) : String = Fields.getOrElse(name, { name.toString } ) + def fieldName(name: Char): String = Fields.getOrElse(name, { + name.toString + }) } -abstract class InformationMessage ( statusCode : Char, val fields : Map[Char,String] ) - extends Message( statusCode ) { +abstract class InformationMessage(statusCode: Char, val fields: Map[Char, String]) + extends Message(statusCode) { - def message : String = this.fields( 'M' ) + def message: String = this.fields('M') - override def toString : String = { - "%s(fields=%s)".format( this.getClass.getSimpleName, fields.map { pair => InformationMessage.fieldName(pair._1) -> pair._2 } ) + override def toString: String = { + "%s(fields=%s)".format(this.getClass.getSimpleName, fields.map { + pair => InformationMessage.fieldName(pair._1) -> pair._2 + }) } } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala index f3c0d137..150f911a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object Message { val Authentication = 'R' @@ -41,8 +41,8 @@ object Message { val Query = 'Q' val RowDescription = 'T' val ReadyForQuery = 'Z' - val Startup : Char = 0 + val Startup: Char = 0 val Sync = 'S' } -class Message ( val name : Char ) \ No newline at end of file +class Message(val name: Char) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala index 55a2dcc7..b2c78e4a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend -class NoticeMessage ( fields : Map[Char,String] ) - extends InformationMessage( Message.Notice, fields ) +class NoticeMessage(fields: Map[Char, String]) + extends InformationMessage(Message.Notice, fields) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala index b31c2bb6..18633c01 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala @@ -1,9 +1,20 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:13 AM +/* + * 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. */ -case class ParameterStatusMessage ( val key : String, val value : String ) - extends Message( Message.ParameterStatus ) \ No newline at end of file + +package com.github.mauricio.async.db.postgresql.messages.backend + +case class ParameterStatusMessage(val key: String, val value: String) + extends Message(Message.ParameterStatus) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala index 0a0e02bd..3e8441c2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend object ParseComplete { val Instance = new ParseComplete() diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala index 5dbf7105..98b51a4f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend -case class ProcessData ( val processId : Int, val secretKey : Int ) - extends Message( Message.BackendKeyData ) +case class ProcessData(val processId: Int, val secretKey: Int) + extends Message(Message.BackendKeyData) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala index 257a1d48..41594d7d 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala @@ -1,8 +1,19 @@ -package com.github.mauricio.postgresql.messages.backend - -/** - * User: mauricio - * Date: 3/31/13 - * Time: 1:26 AM +/* + * 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. */ -class ReadyForQueryMessage ( transactionStatus : Char ) extends Message ( Message.ReadyForQuery ) + +package com.github.mauricio.async.db.postgresql.messages.backend + +class ReadyForQueryMessage(transactionStatus: Char) extends Message(Message.ReadyForQuery) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala index a22493ab..06594ba6 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.backend +package com.github.mauricio.async.db.postgresql.messages.backend -case class RowDescriptionMessage ( val columnDatas : Array[ColumnData] ) - extends Message( Message.RowDescription ) \ No newline at end of file +case class RowDescriptionMessage(val columnDatas: Array[ColumnData]) + extends Message(Message.RowDescription) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala index 86c64c62..e48ad72b 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala @@ -14,12 +14,12 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.Message object CloseMessage { val Instance = new CloseMessage() } -class CloseMessage extends FrontendMessage( Message.Close ) +class CloseMessage extends FrontendMessage(Message.Close) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala index bf4e9455..1e96ab74 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala @@ -14,14 +14,15 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend + +import com.github.mauricio.async.db.postgresql.messages.backend.{Message, AuthenticationResponseType} -import com.github.mauricio.postgresql.messages.backend.{Message, AuthenticationResponseType} class CredentialMessage( - val username : String, - val password : String, - val authenticationType : AuthenticationResponseType.AuthenticationResponseType, - val salt : Option[Array[Byte]] + val username: String, + val password: String, + val authenticationType: AuthenticationResponseType.AuthenticationResponseType, + val salt: Option[Array[Byte]] ) - extends FrontendMessage( Message.PasswordMessage ) \ No newline at end of file + extends FrontendMessage(Message.PasswordMessage) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala index 91974ca0..5230ddd8 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala @@ -14,6 +14,6 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend -class FrontendMessage ( val kind : Char ) +class FrontendMessage(val kind: Char) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index 7d2e28de..ca563154 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.Message -class PreparedStatementExecuteMessage( query : String, values : Seq[Any] ) +class PreparedStatementExecuteMessage(query: String, values: Seq[Any]) extends PreparedStatementMessage(Message.Execute, query, values) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index 9c135f91..0bb3db1a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -14,15 +14,15 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder -class PreparedStatementMessage( kind : Char, val query : String, val values : Seq[Any] ) extends FrontendMessage(kind) { +class PreparedStatementMessage(kind: Char, val query: String, val values: Seq[Any]) extends FrontendMessage(kind) { - val valueTypes : Seq[Int] = values.map { + val valueTypes: Seq[Int] = values.map { value => - if ( value == null ) { + if (value == null) { 0 } else { ColumnEncoderDecoder.kindFor(value.asInstanceOf[AnyRef].getClass) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index 919f6966..97294cb6 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.Message -class PreparedStatementOpeningMessage( query : String, values : Seq[Any] ) +class PreparedStatementOpeningMessage(query: String, values: Seq[Any]) extends PreparedStatementMessage(Message.Parse, query, values) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala index 55e46bb3..797b520c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala @@ -14,8 +14,8 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.Message -class QueryMessage( val query : String ) extends FrontendMessage( Message.Query ) \ No newline at end of file +class QueryMessage(val query: String) extends FrontendMessage(Message.Query) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala index ac108e60..d62d25e9 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala @@ -14,8 +14,8 @@ * under the License. */ -package com.github.mauricio.postgresql.messages.frontend +package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.Message -class StartupMessage ( val parameters : List[(String, Any)] ) extends FrontendMessage(Message.Startup) \ No newline at end of file +class StartupMessage(val parameters: List[(String, Any)]) extends FrontendMessage(Message.Startup) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index ed24cff9..6ffa47b2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -14,10 +14,10 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.exceptions.UnsupportedAuthenticationMethodException -import com.github.mauricio.postgresql.messages.backend._ +import com.github.mauricio.async.db.postgresql.messages.backend.{AuthenticationChallengeMD5, AuthenticationChallengeCleartextMessage, AuthenticationOkMessage, Message} import org.jboss.netty.buffer.ChannelBuffer object AuthenticationStartupParser extends Decoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala index f4a87e65..eaa501fa 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala @@ -14,15 +14,15 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ProcessData, Message} import org.jboss.netty.buffer.ChannelBuffer object BackendKeyDataParser extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { - new ProcessData( b.readInt(), b.readInt() ) + new ProcessData(b.readInt(), b.readInt()) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 1986a4d7..9b01f32f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -1,17 +1,27 @@ -package com.github.mauricio.postgresql.parsers +/* + * 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.parsers -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.{CommandCompleteMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, Message} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -/** - * User: Maurício Linhares - * Date: 3/1/12 - * Time: 10:33 PM - */ - -class CommandCompleteParser (charset : Charset) extends Decoder { +class CommandCompleteParser(charset: Charset) extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { @@ -19,13 +29,13 @@ class CommandCompleteParser (charset : Charset) extends Decoder { val indexOfRowCount = result.lastIndexOf(" ") - val rowCount = if ( indexOfRowCount == -1 ) { + val rowCount = if (indexOfRowCount == -1) { 0 } else { try { result.substring(indexOfRowCount).trim.toInt } catch { - case e : NumberFormatException => { + case e: NumberFormatException => { 0 } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala index 49c639b6..ba2ac279 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{DataRowMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{DataRowMessage, Message} import org.jboss.netty.buffer.ChannelBuffer object DataRowParser extends Decoder { @@ -25,15 +25,15 @@ object DataRowParser extends Decoder { val row = new Array[ChannelBuffer](buffer.readShort()) - 0.until( row.length ).foreach { + 0.until(row.length).foreach { column => val length = buffer.readInt() row(column) = if (length == -1) { null } else { - val slice = buffer.slice( buffer.readerIndex(), length ) - buffer.readerIndex( buffer.readerIndex() + length ) + val slice = buffer.slice(buffer.readerIndex(), length) + buffer.readerIndex(buffer.readerIndex() + length) slice } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala index 2e98d123..8f002003 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.Message import org.jboss.netty.buffer.ChannelBuffer trait Decoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala index c245fa95..ca080bd5 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala @@ -14,12 +14,12 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ErrorMessage, Message} import java.nio.charset.Charset -class ErrorParser( charset : Charset ) extends InformationParser(charset) { +class ErrorParser(charset: Charset) extends InformationParser(charset) { def createMessage(fields: Map[Char, String]): Message = new ErrorMessage(fields) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 376d5166..60fd73de 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -1,25 +1,36 @@ -package com.github.mauricio.postgresql.parsers +/* + * 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.parsers -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -/** - * User: mauricio - * Date: 3/31/13 - * Time: 12:54 AM - */ -abstract class InformationParser( charset : Charset ) extends Decoder { +abstract class InformationParser(charset: Charset) extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { - val fields = scala.collection.mutable.Map[Char,String]() + val fields = scala.collection.mutable.Map[Char, String]() - while ( b.readable() ) { + while (b.readable()) { val kind = b.readByte() - if ( kind != 0 ) { + if (kind != 0) { fields.put( kind.toChar, ChannelUtils.readCString(b, charset) @@ -31,6 +42,6 @@ abstract class InformationParser( charset : Charset ) extends Decoder { createMessage(fields.toMap) } - def createMessage( fields : Map[Char,String] ) : Message + def createMessage(fields: Map[Char, String]): Message } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala index 84d1468c..bd9ed3e7 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala @@ -14,14 +14,14 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{CloseComplete, BindComplete, ParseComplete, Message} +import com.github.mauricio.async.db.postgresql.exceptions.ParserNotAvailableException +import com.github.mauricio.async.db.postgresql.messages.backend.{ParseComplete, CloseComplete, BindComplete, Message} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.postgresql.exceptions.ParserNotAvailableException -class MessageParser(charset : Charset) { +class MessageParser(charset: Charset) { private val parsers = Map( Message.Authentication -> AuthenticationStartupParser, @@ -41,7 +41,7 @@ class MessageParser(charset : Charset) { def parserFor(t: Char): Decoder = { val option = this.parsers.get(t) - if ( option.isDefined ) { + if (option.isDefined) { option.get } else { throw new ParserNotAvailableException(t) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala index 07f6b91f..a8aeb11a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala @@ -14,12 +14,12 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{NoticeMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{Message, NoticeMessage} import java.nio.charset.Charset -class NoticeParser( charset : Charset ) extends InformationParser(charset) { +class NoticeParser(charset: Charset) extends InformationParser(charset) { def createMessage(fields: Map[Char, String]): Message = new NoticeMessage(fields) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index c98ddc34..057d7d8f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -14,19 +14,19 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, Message} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -class ParameterStatusParser( charset : Charset ) extends Decoder { +class ParameterStatusParser(charset: Charset) extends Decoder { import ChannelUtils._ override def parseMessage(b: ChannelBuffer): Message = { - new ParameterStatusMessage( readCString(b, charset), readCString(b, charset) ) + new ParameterStatusMessage(readCString(b, charset), readCString(b, charset)) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala index 85df2aa8..13b2d062 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala @@ -14,15 +14,15 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{ReadyForQueryMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ReadyForQueryMessage, Message} import org.jboss.netty.buffer.ChannelBuffer object ReadyForQueryParser extends Decoder { override def parseMessage(b: ChannelBuffer): Message = { - new ReadyForQueryMessage( b.readByte().asInstanceOf[Char] ) + new ReadyForQueryMessage(b.readByte().asInstanceOf[Char]) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala index 4d69061a..f796e20c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala @@ -1,15 +1,25 @@ -package com.github.mauricio.postgresql.parsers +/* + * 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. + */ -import com.github.mauricio.postgresql.messages.backend.Message -import org.jboss.netty.buffer.ChannelBuffer +package com.github.mauricio.async.db.postgresql.parsers -/** - * User: Maurício Linhares - * Date: 3/12/12 - * Time: 11:36 PM - */ +import com.github.mauricio.async.db.postgresql.messages.backend.Message +import org.jboss.netty.buffer.ChannelBuffer -class ReturningMessageParser( val message : Message ) extends Decoder { +class ReturningMessageParser(val message: Message) extends Decoder { def parseMessage(buffer: ChannelBuffer): Message = { this.message } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 8e1408d3..c8c42ef5 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -14,16 +14,16 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.postgresql.ChannelUtils -import com.github.mauricio.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer /** - RowDescription (B) +RowDescription (B) Byte1('T') Identifies the message as a row description. @@ -55,24 +55,23 @@ The type modifier (see pg_attribute.atttypmod). The meaning of the modifier is t Int16 The format code being used for the field. Currently will be zero (text) or one (binary). In a RowDescription returned from the statement variant of Describe, the format code is not yet known and will always be zero. - * - */ + * + */ -class RowDescriptionParser (charset : Charset) extends Decoder { +class RowDescriptionParser(charset: Charset) extends Decoder { - import ChannelUtils._ override def parseMessage(b: ChannelBuffer): Message = { val columnsCount = b.readShort() val columns = new Array[ColumnData](columnsCount) - 0.until( columnsCount ).foreach { + 0.until(columnsCount).foreach { index => columns(index) = new ColumnData( - name = readCString( b, charset ), - tableObjectId = b.readInt(), + name = ChannelUtils.readCString(b, charset), + tableObjectId = b.readInt(), columnNumber = b.readShort(), dataType = b.readInt(), dataTypeSize = b.readShort(), @@ -81,7 +80,7 @@ class RowDescriptionParser (charset : Charset) extends Decoder { ) } - new RowDescriptionMessage( columns ) + new RowDescriptionMessage(columns) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index 3437f117..6b00bce6 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -14,21 +14,21 @@ * under the License. */ -package com.github.mauricio.postgresql.pool +package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.Configuration -import com.github.mauricio.postgresql.DatabaseConnectionHandler +import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler import concurrent.Await import concurrent.duration._ import org.apache.commons.pool.PoolableObjectFactory class ConnectionObjectFactory( - configuration : Configuration) + configuration: Configuration) extends PoolableObjectFactory[DatabaseConnectionHandler] { def makeObject(): DatabaseConnectionHandler = { val connection = new DatabaseConnectionHandler(configuration) - Await.result( connection.connect, 5 seconds ) + Await.result(connection.connect, 5 seconds) connection } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala index 72eee163..b7237551 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala @@ -16,15 +16,16 @@ package com.github.mauricio.postgresql.pool +import com.github.mauricio.async.db.postgresql.pool.ConnectionObjectFactory import com.github.mauricio.async.db.{Configuration, Connection} import org.apache.commons.pool.impl.StackObjectPool -class ConnectionPool( val configuration : Configuration ) { +class ConnectionPool(val configuration: Configuration) { private val factory = new ConnectionObjectFactory(configuration) - private val pool = new StackObjectPool( this.factory, 1 ) + private val pool = new StackObjectPool(this.factory, 1) - def doWithConnection[T]( fn : Connection => T ) : T = { + def doWithConnection[T](fn: Connection => T): T = { val borrowed = this.pool.borrowObject() try { fn(borrowed) diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala index 46b523bc..ba8360a7 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala @@ -15,14 +15,18 @@ package com.github.mauricio.async.db.util -import scala.collection.mutable.StringBuilder -import scala.collection.mutable import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException +import scala.collection.mutable +import scala.collection.mutable.StringBuilder object ArrayStreamingParser { + val log = Log.getByName(ArrayStreamingParser.getClass.getName) + def parse(content: String, delegate: ArrayStreamingParserDelegate) { + log.debug("Processing array [{}]", content) + var index = 0 var escaping = false var quoted = false @@ -32,16 +36,17 @@ object ArrayStreamingParser { while (index < content.size) { val char = content.charAt(index) + if (escaping) { currentElement.append(char) - escaping = false + escaping = false } else { char match { - case '{' => { + case '{' if !quoted => { delegate.arrayStarted opens += 1 } - case '}' => { + case '}' if !quoted => { if (currentElement != null) { sendElementEvent(currentElement, quoted, delegate) currentElement = null @@ -59,8 +64,8 @@ object ArrayStreamingParser { currentElement = new mutable.StringBuilder() } } - case ',' => { - if ( currentElement != null ) { + case ',' if !quoted => { + if (currentElement != null) { sendElementEvent(currentElement, quoted, delegate) } currentElement = null @@ -69,7 +74,7 @@ object ArrayStreamingParser { escaping = true } case _ => { - if ( currentElement == null ) { + if (currentElement == null) { currentElement = new mutable.StringBuilder() } currentElement.append(char) @@ -80,17 +85,17 @@ object ArrayStreamingParser { index += 1 } - if ( opens != closes ) { + if (opens != closes) { throw new InvalidArrayException("This array is unbalanced %s".format(content)) } } - def sendElementEvent( builder : mutable.StringBuilder, quoted : Boolean, delegate: ArrayStreamingParserDelegate ) { + def sendElementEvent(builder: mutable.StringBuilder, quoted: Boolean, delegate: ArrayStreamingParserDelegate) { val value = builder.toString() - if ( !quoted && "NULL".equalsIgnoreCase(value) ) { + if (!quoted && "NULL".equalsIgnoreCase(value)) { delegate.nullElementFound } else { delegate.elementFound(value) diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala index 6c1340cc..d185f335 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala @@ -18,9 +18,12 @@ package com.github.mauricio.async.db.util trait ArrayStreamingParserDelegate { - def arrayStarted : Unit = {} - def arrayEnded : Unit = {} - def elementFound( element : String ) : Unit = {} - def nullElementFound : Unit = {} + def arrayStarted: Unit = {} + + def arrayEnded: Unit = {} + + def elementFound(element: String): Unit = {} + + def nullElementFound: Unit = {} } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/ChannelUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala similarity index 65% rename from src/main/scala/com/github/mauricio/async/db/postgresql/ChannelUtils.scala rename to src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala index 391c8aba..d6794ce8 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/ChannelUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -14,28 +14,25 @@ * under the License. */ -package com.github.mauricio.postgresql +package com.github.mauricio.async.db.util -import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer object ChannelUtils { - private val log = Log.getByName("ChannelUtils") - - def writeLength( buffer : ChannelBuffer ) { + def writeLength(buffer: ChannelBuffer) { val length = buffer.writerIndex() - 1 buffer.markWriterIndex() buffer.writerIndex(1) - buffer.writeInt( length ) + buffer.writeInt(length) buffer.resetWriterIndex() } - def printBuffer( b : ChannelBuffer ) : Unit = { + def printBuffer(b: ChannelBuffer): Unit = { val bytes = new Array[Byte](b.readableBytes()) b.markReaderIndex() @@ -46,28 +43,28 @@ object ChannelUtils { } - def writeCString( content : String, b : ChannelBuffer, charset : Charset ) : Unit = { - b.writeBytes( content.getBytes( charset ) ) + def writeCString(content: String, b: ChannelBuffer, charset: Charset): Unit = { + b.writeBytes(content.getBytes(charset)) b.writeByte(0) } - def readCString( b : ChannelBuffer, charset : Charset ) : String = { + def readCString(b: ChannelBuffer, charset: Charset): String = { b.markReaderIndex() - var byte : Byte = 0 + var byte: Byte = 0 var count = 0 do { byte = b.readByte() - count+= 1 - } while ( byte != 0 ) + count += 1 + } while (byte != 0) b.resetReaderIndex() - val result = b.toString( b.readerIndex(), count - 1, charset ) + val result = b.toString(b.readerIndex(), count - 1, charset) - b.readerIndex( b.readerIndex() + count) + b.readerIndex(b.readerIndex() + count) return result } diff --git a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala index 84d14ab2..f74d8f26 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.util import java.util.concurrent.Executors object ExecutorServiceUtils { - val CachedThreadPool = Executors.newCachedThreadPool( DaemonThreadsFactory ) + val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) } diff --git a/src/main/scala/com/github/mauricio/async/db/util/Log.scala b/src/main/scala/com/github/mauricio/async/db/util/Log.scala index f172356b..df7df4cd 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/Log.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/Log.scala @@ -1,21 +1,29 @@ +/* + * Copyright 2013 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * 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 org.slf4j.LoggerFactory - -/** - * User: Maurício Linhares - * Date: 2/25/12 - * Time: 8:01 PM - */ - object Log { - def get[T](implicit tag : reflect.ClassTag[T]) = { - LoggerFactory.getLogger( tag.runtimeClass.getName ) + def get[T](implicit tag: reflect.ClassTag[T]) = { + LoggerFactory.getLogger(tag.runtimeClass.getName) } - def getByName( name : String ) = { + def getByName(name: String) = { LoggerFactory.getLogger(name) } diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index 060e49d8..a2804fc6 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala index 92f111b5..9a2df734 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification +import com.github.mauricio.async.db.postgresql.column.TimestampWithTimezoneEncoderDecoder class ArrayTypesSpec extends Specification with DatabaseTestHelper { @@ -37,9 +38,9 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { constraint bigserial_column_pkey primary key (bigserial_column) )""" - val simpleCreate = """create temp table type_test_table ( + val simpleCreate = """create temp table type_test_table ( bigserial_column bigserial not null, - smallint_column smallint[] 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) @@ -51,7 +52,7 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { values ( '{1,2,3,4}', '{"some,\"comma,separated,text","another line of text"}', - '{ 2013-04-06 01:15:10.528-03, 2013-04-06 01:15:08.528-03 }' + '{"2013-04-06 01:15:10.528-03","2013-04-06 01:15:08.528-03"}' )""" "connection" should { @@ -62,14 +63,13 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { handler => executeDdl(handler, simpleCreate) executeDdl(handler, insert, 1) - val result = executeQuery(handler, "select * from type_test_table") - printf("id %s%n", result.rows.get(0,0)) - printf("array %s%n", result.rows.get.apply(1,0)) - printf("strings %s%n", result.rows.get.apply(2,0)) - printf("timestamps %s%n", result.rows.get.apply(3,0)) - - - true === false + val result = executeQuery(handler, "select * from type_test_table").rows.get + result("smallint_column", 0) === List(1,2,3,4) + result("text_column", 0) === List("some,\"comma,separated,text", "another line of text" ) + result("timestamp_column", 0) === List( + TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:10.528-03"), + TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:08.528-03") + ) } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 6f36b02f..e4c6ead0 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -16,15 +16,15 @@ package com.github.mauricio.postgresql -import column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} +import com.github.mauricio.async.db.postgresql.column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} +import com.github.mauricio.async.db.postgresql.exceptions.{GenericDatabaseException, UnsupportedAuthenticationMethodException} +import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage +import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import concurrent.{Future, Await} -import messages.backend.InformationMessage import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ -import com.github.mauricio.async.db.postgresql.DatabaseTestHelper -import com.github.mauricio.async.db.postgresql.exceptions.{GenericDatabaseException, UnsupportedAuthenticationMethodException} class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { @@ -178,10 +178,10 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe val select = "select * from prepared_statement_test where name like ?" - val queryResult = executePreparedStatement(handler, select, Array("Peter Parker") ) + val queryResult = executePreparedStatement(handler, select, Array("Peter Parker")) val rows = queryResult.rows.get - val queryResult2 = executePreparedStatement(handler, select, Array("Mary Jane") ) + val queryResult2 = executePreparedStatement(handler, select, Array("Mary Jane")) val rows2 = queryResult2.rows.get List( @@ -261,10 +261,10 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe failure("should not have come here") }) } catch { - case e : GenericDatabaseException => { + case e: GenericDatabaseException => { e.errorMessage.fields(InformationMessage.Routine) === "auth_failed" } - case e : Exception => { + case e: Exception => { failure("should not have come here") } } @@ -273,12 +273,12 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe "transaction and flatmap example" in { - val handler : Connection = new DatabaseConnectionHandler( defaultConfiguration ) + val handler: Connection = new DatabaseConnectionHandler(defaultConfiguration) val result: Future[QueryResult] = handler.connect - .map( parameters => handler ) - .flatMap( connection => connection.sendQuery("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ") ) - .flatMap( query => handler.sendQuery("SELECT 0") ) - .flatMap( query => handler.sendQuery("COMMIT").map( value => query ) ) + .map(parameters => handler) + .flatMap(connection => connection.sendQuery("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ")) + .flatMap(query => handler.sendQuery("SELECT 0")) + .flatMap(query => handler.sendQuery("COMMIT").map(value => query)) val queryResult: QueryResult = Await.result(result, Duration(5, SECONDS)) diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index ae8836d7..07f75368 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -16,7 +16,6 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.postgresql.DatabaseConnectionHandler import com.github.mauricio.async.db.Configuration import concurrent.Await import concurrent.duration._ @@ -24,6 +23,7 @@ import concurrent.duration._ trait DatabaseTestHelper { def databaseName = Some("netty_driver_test") + def databasePort = 5433 def defaultConfiguration = new Configuration( @@ -32,10 +32,10 @@ trait DatabaseTestHelper { database = databaseName) def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { - withHandler( this.defaultConfiguration, fn ) + withHandler(this.defaultConfiguration, fn) } - def withHandler[T]( configuration : Configuration, fn: (DatabaseConnectionHandler) => T): T = { + def withHandler[T](configuration: Configuration, fn: (DatabaseConnectionHandler) => T): T = { val handler = new DatabaseConnectionHandler(configuration) @@ -48,24 +48,24 @@ trait DatabaseTestHelper { } - def executeDdl( handler : DatabaseConnectionHandler, data : String, count : Int = 0 ) = { + def executeDdl(handler: DatabaseConnectionHandler, data: String, count: Int = 0) = { val rows = Await.result(handler.sendQuery(data), Duration(5, SECONDS)).rowsAffected - if ( rows != count ) { + if (rows != count) { throw new IllegalStateException("We expected %s rows but there were %s".format(count, rows)) } } - def executeQuery( handler : DatabaseConnectionHandler, data : String ) = { + def executeQuery(handler: DatabaseConnectionHandler, data: String) = { Await.result(handler.sendQuery(data), Duration(5, SECONDS)) } def executePreparedStatement( - handler : DatabaseConnectionHandler, - statement : String, - values : Array[Any] = Array.empty[Any] ) = { - Await.result( handler.sendPreparedStatement(statement, values), Duration(5, SECONDS) ) + handler: DatabaseConnectionHandler, + statement: String, + values: Array[Any] = Array.empty[Any]) = { + Await.result(handler.sendPreparedStatement(statement, values), Duration(5, SECONDS)) } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index b0f5d687..5c02cd1c 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -16,14 +16,15 @@ package com.github.mauricio.postgresql -import messages.backend.ErrorMessage +import com.github.mauricio.async.db.postgresql.MessageDecoder +import com.github.mauricio.async.db.postgresql.messages.backend.ErrorMessage import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification class MessageDecoderSpec extends Specification { - val decoder = new MessageDecoder( CharsetUtil.UTF_8 ) + val decoder = new MessageDecoder(CharsetUtil.UTF_8) "message decoder" should { @@ -35,7 +36,7 @@ class MessageDecoderSpec extends Specification { buffer.writeByte(1) buffer.writeByte(2) - this.decoder.decode( null, null, buffer ) must beNull + this.decoder.decode(null, null, buffer) must beNull } "should not try to decode if there is a type and lenght but it's not long enough" in { @@ -43,31 +44,31 @@ class MessageDecoderSpec extends Specification { val buffer = ChannelBuffers.dynamicBuffer() buffer.writeByte('R') - buffer.writeInt( 30 ) - buffer.writeBytes( "my-name".getBytes( CharsetUtil.UTF_8 ) ) + buffer.writeInt(30) + buffer.writeBytes("my-name".getBytes(CharsetUtil.UTF_8)) List( - this.decoder.decode( null, null, buffer ) must beNull, + this.decoder.decode(null, null, buffer) must beNull, buffer.readerIndex() === 0 ) } "should correctly decode a message" in { - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = ChannelBuffers.dynamicBuffer() val text = "This is an error message" - val textBytes = text.getBytes( CharsetUtil.UTF_8 ) + val textBytes = text.getBytes(CharsetUtil.UTF_8) buffer.writeByte('E') - buffer.writeInt( textBytes.length + 4 + 1 ) + buffer.writeInt(textBytes.length + 4 + 1) buffer.writeByte('M') - buffer.writeBytes( textBytes ) + buffer.writeBytes(textBytes) - val result = this.decoder.decode( null, null, buffer ).asInstanceOf[ErrorMessage] + val result = this.decoder.decode(null, null, buffer).asInstanceOf[ErrorMessage] List( result.message === text, - buffer.readerIndex() === (textBytes.length + 5 ) + buffer.readerIndex() === (textBytes.length + 5) ) } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala index 7d3bd17c..0184b2c4 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala @@ -22,7 +22,7 @@ object TestUtils { private val count = new AtomicInteger() - def nextInt : Int = { + def nextInt: Int = { count.incrementAndGet() } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala index 3faa4a97..1bf8fc32 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala @@ -16,7 +16,6 @@ package com.github.mauricio.async.db.postgresql.column -import com.github.mauricio.postgresql.column.IntegerEncoderDecoder import org.specs2.mutable.Specification class ArrayEncoderDecoderSpec extends Specification { @@ -26,21 +25,21 @@ class ArrayEncoderDecoderSpec extends Specification { "parse an array of numbers" in { val numbers = "{1,2,3}" - val encoder = new ArrayEncoderDecoder( IntegerEncoderDecoder ) + val encoder = new ArrayEncoderDecoder(IntegerEncoderDecoder) - val result = encoder.decode( numbers ) + val result = encoder.decode(numbers) - result === List(1,2,3) + result === List(1, 2, 3) } "parse an array of array of numbers" in { val numbers = "{{1,2,3},{4,5,6}}" - val encoder = new ArrayEncoderDecoder( IntegerEncoderDecoder ) + val encoder = new ArrayEncoderDecoder(IntegerEncoderDecoder) - val result = encoder.decode( numbers ) + val result = encoder.decode(numbers) - result === List(List(1,2,3), List(4,5,6)) + result === List(List(1, 2, 3), List(4, 5, 6)) } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala index fe763f27..06eac711 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala @@ -16,7 +16,8 @@ package com.github.mauricio.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{ErrorMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ErrorMessage} +import com.github.mauricio.async.db.postgresql.parsers.ErrorParser import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification @@ -30,9 +31,9 @@ class ParserESpec extends Specification { val error = "this is my error message" val buffer = ChannelBuffers.dynamicBuffer() buffer.writeByte('M') - buffer.writeBytes( error.getBytes ) + buffer.writeBytes(error.getBytes) - val message = new ErrorParser(CharsetUtil.UTF_8).parseMessage( buffer ).asInstanceOf[ErrorMessage] + val message = new ErrorParser(CharsetUtil.UTF_8).parseMessage(buffer).asInstanceOf[ErrorMessage] List(message.message === error, message.name === Message.Error) } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala index b4e6918b..93251981 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala @@ -16,7 +16,8 @@ package com.github.mauricio.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{ProcessData, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ProcessData} +import com.github.mauricio.async.db.postgresql.parsers.BackendKeyDataParser import org.jboss.netty.buffer.ChannelBuffers import org.specs2.mutable.Specification @@ -32,7 +33,7 @@ class ParserKSpec extends Specification { buffer.writeInt(10) buffer.writeInt(20) - val data = parser.parseMessage( buffer ).asInstanceOf[ProcessData] + val data = parser.parseMessage(buffer).asInstanceOf[ProcessData] List( data.name === Message.BackendKeyData, diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala index 5928cc7b..db2aeda3 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala @@ -16,7 +16,8 @@ package com.github.mauricio.postgresql.parsers -import com.github.mauricio.postgresql.messages.backend.{ParameterStatusMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ParameterStatusMessage} +import com.github.mauricio.async.db.postgresql.parsers.ParameterStatusParser import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil @@ -30,23 +31,23 @@ class ParserSSpec extends Specification { "correctly parse a config pair" in { - val key = "application-name" + val key = "application-name" val value = "my-cool-application" val buffer = ChannelBuffers.dynamicBuffer() - buffer.writeBytes( key.getBytes( Charset.forName("UTF-8") ) ) + buffer.writeBytes(key.getBytes(Charset.forName("UTF-8"))) buffer.writeByte(0) - buffer.writeBytes( value.getBytes( Charset.forName("UTF-8") ) ) + buffer.writeBytes(value.getBytes(Charset.forName("UTF-8"))) buffer.writeByte(0) - val content = this.parser.parseMessage( buffer ).asInstanceOf[ParameterStatusMessage] + val content = this.parser.parseMessage(buffer).asInstanceOf[ParameterStatusMessage] List( content.key === key, content.value === value, content.name === Message.ParameterStatus, - buffer.readerIndex() === buffer.writerIndex() ) + buffer.readerIndex() === buffer.writerIndex()) } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala index 6a66f1b3..15074cd6 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.util -import org.specs2.mutable.Specification import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} +import org.specs2.mutable.Specification import scala.collection.mutable.ArrayBuffer class ArrayStreamingParserSpec extends Specification { @@ -35,7 +35,7 @@ class ArrayStreamingParserSpec extends Specification { delegate.starts === 3 delegate.ends === 3 - delegate.items === ArrayBuffer("{", "{", "1", "2", "3", "}", "{", "4", "5", "6","}","}") + delegate.items === ArrayBuffer("{", "{", "1", "2", "3", "}", "{", "4", "5", "6", "}", "}") } "should parse a varchar array correctly" in { @@ -44,7 +44,7 @@ class ArrayStreamingParserSpec extends Specification { val delegate = new LoggingDelegate() parser.parse(content, delegate) - delegate.items === ArrayBuffer( "{", "{", "item", "is here", "but\"not there", "}", "{", "so", "this is your last step", "}", "{", "", "}","}" ) + delegate.items === ArrayBuffer("{", "{", "item", "is here", "but\"not there", "}", "{", "so", "this is your last step", "}", "{", "", "}", "}") delegate.starts === 4 delegate.ends === 4 } @@ -55,7 +55,7 @@ class ArrayStreamingParserSpec extends Specification { val delegate = new LoggingDelegate() parser.parse(content, delegate) - delegate.items === ArrayBuffer( "{", null, "first", null, "second", "NULL", null, "}" ) + delegate.items === ArrayBuffer("{", null, "first", null, "second", "NULL", null, "}") } } From f5a0df8b522fcbfe29004396c91c754093b683ae Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 23 Apr 2013 17:45:46 -0300 Subject: [PATCH 021/357] Finished basic array types support --- .gitignore | 4 +- README.markdown | 3 +- build.sbt | 18 ++ pom.xml | 136 ----------- .../DatabaseConnectionHandler.scala | 37 +-- .../async/db/postgresql/MessageEncoder.scala | 13 +- .../async/db/postgresql/MutableQuery.scala | 5 +- ...ncoderDecoder.scala => ArrayDecoder.scala} | 4 +- .../column/BigDecimalEncoderDecoder.scala | 2 + .../column/BooleanEncoderDecoder.scala | 2 + .../column/CharEncoderDecoder.scala | 2 + .../db/postgresql/column/ColumnDecoder.scala | 23 ++ .../column/ColumnDecoderRegistry.scala | 23 ++ .../db/postgresql/column/ColumnEncoder.scala | 25 ++ .../column/ColumnEncoderDecoder.scala | 218 +----------------- .../column/ColumnEncoderRegistry.scala | 25 ++ .../db/postgresql/column/ColumnTypes.scala | 119 ++++++++++ .../column/DateEncoderDecoder.scala | 2 + .../column/DefaultColumnDecoderRegistry.scala | 88 +++++++ .../column/DefaultColumnEncoderRegistry.scala | 150 ++++++++++++ .../column/DoubleEncoderDecoder.scala | 1 + .../column/FloatEncoderDecoder.scala | 1 + .../column/IntegerEncoderDecoder.scala | 2 + .../column/LongEncoderDecoder.scala | 1 + .../column/ShortEncoderDecoder.scala | 1 + .../column/StringEncoderDecoder.scala | 1 + .../column/TimeEncoderDecoder.scala | 2 + .../TimeWithTimezoneEncoderDecoder.scala | 2 + .../column/TimestampEncoderDecoder.scala | 2 + .../TimestampWithTimezoneEncoderDecoder.scala | 2 + .../ExecutePreparedStatementEncoder.scala | 6 +- .../PreparedStatementOpeningEncoder.scala | 6 +- .../messages/backend/ColumnData.scala | 8 +- .../PreparedStatementExecuteMessage.scala | 5 +- .../frontend/PreparedStatementMessage.scala | 17 +- .../PreparedStatementOpeningMessage.scala | 5 +- .../pool/ConnectionObjectFactory.scala | 1 + .../async/db/util/ArrayStreamingParser.scala | 2 - .../mauricio/async/db/util/ChannelUtils.scala | 3 +- .../async/db/postgresql/ArrayTypesSpec.scala | 4 +- .../db/postgresql/MessageDecoderSpec.scala | 10 +- .../column/ArrayEncoderDecoderSpec.scala | 4 +- .../DefaultColumnEncoderRegistrySpec.scala | 39 ++++ .../db/postgresql/parsers/ParserESpec.scala | 12 +- .../db/postgresql/parsers/ParserKSpec.scala | 3 +- .../db/postgresql/parsers/ParserSSpec.scala | 12 +- 46 files changed, 620 insertions(+), 431 deletions(-) create mode 100644 build.sbt delete mode 100644 pom.xml rename src/main/scala/com/github/mauricio/async/db/postgresql/column/{ArrayEncoderDecoder.scala => ArrayDecoder.scala} (91%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala diff --git a/.gitignore b/.gitignore index 2157db0f..98ee5565 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,10 @@ target/* bin/* .idea* .classpath -.project +.project/* .settings/* project/target/* project/project/* .rvmrc +.ruby-version +.ruby-gemset diff --git a/README.markdown b/README.markdown index 61dd6857..e8714aa1 100644 --- a/README.markdown +++ b/README.markdown @@ -13,7 +13,7 @@ to PostgreSQL. - receive database notices - execute direct queries (without portals/prepared statements) - portals/prepared statements -- parses all basic (non-array) PostgreSQL types, other types are parsed as string +- parses all basic PostgreSQL types, other types are parsed as string - date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects - all work is done using the new `scala.concurrent.Future` and `scala.concurrent.Promise` objects @@ -23,7 +23,6 @@ to PostgreSQL. - benchmarks - more tests - timeout handler for initial handshare and queries -- array types support ## What are the design goals? diff --git a/build.sbt b/build.sbt new file mode 100644 index 00000000..dac08647 --- /dev/null +++ b/build.sbt @@ -0,0 +1,18 @@ +name := "postgresql-async" + +version := "0.0.1" + +organization := "com.github.mauricio" + +scalaVersion := "2.10.1" + +libraryDependencies ++= Seq( + "commons-pool" % "commons-pool" % "1.6", + "ch.qos.logback" % "logback-classic" % "1.0.9", + "io.netty" % "netty" % "3.6.5.Final", + "joda-time" % "joda-time" % "2.2", + "org.joda" % "joda-convert" % "1.3.1", + "org.specs2" % "specs2_2.10" % "1.14" % "test" +) + +scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature") \ No newline at end of file diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 8424940e..00000000 --- a/pom.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - 4.0.0 - - com.github.mauricio - postgresql-netty - 0.1.0-SNAPSHOT - - - 2.10.1 - utf-8 - - - - - scala-tools.org - Scala-tools Maven2 Repository - https://oss.sonatype.org/content/groups/scala-tools/ - - - - - - - org.scala-tools - maven-scala-plugin - 2.14.2 - - - - compile - testCompile - - - - - ${scala.version} - true - - -target:jvm-1.6 - -g:vars - -deprecation - -dependencyfile - ${project.build.directory}/.scala_dependencies - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.6 - - ${project.encoding} - - - - com.mmakowski - maven-specs2-plugin - 0.4.1 - - - verify - verify - - run-specs - - - - - - - - - - - commons-pool - commons-pool - 1.6 - - - - ch.qos.logback - logback-classic - 1.0.9 - - - - io.netty - netty - 3.6.3.Final - - - - joda-time - joda-time - 2.2 - - - - org.joda - joda-convert - 1.3.1 - - - - org.scala-lang - scala-compiler - 2.10.1 - - - - org.scala-lang - scala-library - 2.10.1 - - - - org.specs2 - specs2_2.10 - 1.14 - test - - - - - \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index af966e14..e324075f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -31,6 +31,7 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ +import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -40,7 +41,10 @@ object DatabaseConnectionHandler { class DatabaseConnectionHandler ( - val configuration: Configuration = Configuration.Default) extends SimpleChannelHandler with Connection { + configuration: Configuration = Configuration.Default, + encoderRegistry : ColumnEncoderRegistry = DefaultColumnEncoderRegistry.Instance, + decoderRegistry : ColumnDecoderRegistry = DefaultColumnDecoderRegistry.Instance + ) extends SimpleChannelHandler with Connection { import DatabaseConnectionHandler._ @@ -80,7 +84,7 @@ class DatabaseConnectionHandler override def getPipeline(): ChannelPipeline = { Channels.pipeline( new MessageDecoder(configuration.charset), - new MessageEncoder(configuration.charset), + new MessageEncoder(configuration.charset, encoderRegistry), DatabaseConnectionHandler.this) } @@ -116,17 +120,20 @@ class DatabaseConnectionHandler if (future.getCause != null) { closingPromise.failure(future.getCause) } else { - future.getChannel.close().addListener(new ChannelFutureListener { - def operationComplete(internalFuture: ChannelFuture) { - if (internalFuture.isSuccess) { - closingPromise.success(DatabaseConnectionHandler.this) - } else { - closingPromise.failure(internalFuture.getCause) + if ( future.getChannel.isOpen ) { + future.getChannel.close().addListener(new ChannelFutureListener { + def operationComplete(internalFuture: ChannelFuture) { + if (internalFuture.isSuccess) { + closingPromise.success(DatabaseConnectionHandler.this) + } else { + closingPromise.failure(internalFuture.getCause) + } } - } - }) + }) + } else { + closingPromise.success(DatabaseConnectionHandler.this) + } } - } }) @@ -242,10 +249,10 @@ class DatabaseConnectionHandler if (!this.isParsed(realQuery)) { log.debug("MutableQuery is not parsed yet -> {}", realQuery) - this.currentChannel.write(new PreparedStatementOpeningMessage(realQuery, values)) + this.currentChannel.write(new PreparedStatementOpeningMessage(realQuery, values, this.encoderRegistry)) } else { - this.currentQuery = Some(new MutableQuery(this.parsedStatements.get(realQuery), configuration.charset)) - this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values)) + this.currentQuery = Some(new MutableQuery(this.parsedStatements.get(realQuery), configuration.charset, this.decoderRegistry)) + this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values, this.encoderRegistry)) } this.queryPromise.get.future @@ -322,7 +329,7 @@ class DatabaseConnectionHandler private def onRowDescription(m: RowDescriptionMessage) { log.debug("received query description {}", m) - this.currentQuery = Option(new MutableQuery(m.columnDatas, configuration.charset)) + this.currentQuery = Option(new MutableQuery(m.columnDatas, configuration.charset, this.decoderRegistry)) log.debug("Current prepared statement is {}", this.currentPreparedStatement) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index 6f0a7eda..92e4ff35 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -24,15 +24,20 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry -class MessageEncoder(charset: Charset) extends OneToOneEncoder { +object MessageEncoder { + val log = Log.get[MessageEncoder] +} + +class MessageEncoder(charset: Charset, encoderRegistry : ColumnEncoderRegistry) extends OneToOneEncoder { - val log = Log.getByName("MessageEncoder") + import MessageEncoder.log val encoders: Map[Class[_], Encoder] = Map( classOf[CloseMessage] -> CloseMessageEncoder, - classOf[PreparedStatementExecuteMessage] -> new ExecutePreparedStatementEncoder(charset), - classOf[PreparedStatementOpeningMessage] -> new PreparedStatementOpeningEncoder(charset), + classOf[PreparedStatementExecuteMessage] -> new ExecutePreparedStatementEncoder(charset, encoderRegistry), + classOf[PreparedStatementOpeningMessage] -> new PreparedStatementOpeningEncoder(charset, encoderRegistry), classOf[StartupMessage] -> new StartupMessageEncoder(charset), classOf[QueryMessage] -> new QueryMessageEncoder(charset), classOf[CredentialMessage] -> new CredentialEncoder(charset) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala index 54764859..03554351 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala @@ -22,12 +22,13 @@ import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.postgresql.column.ColumnDecoderRegistry object MutableQuery { val log = Log.get[MutableQuery] } -class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset) extends ResultSet { +class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder : ColumnDecoderRegistry) extends ResultSet { private val rows = new ArrayBuffer[Array[Any]]() private val columnMapping: Map[String, Int] = this.columnTypes.map { @@ -51,7 +52,7 @@ class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset) extends realRow(index) = if (row(index) == null) { null } else { - this.columnTypes(index).decoder.decode(row(index).toString(charset)) + this.decoder.decode( this.columnTypes(index).dataType, row(index).toString(charset) ) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala similarity index 91% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala index d165c723..ef221b24 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala @@ -20,7 +20,7 @@ import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingPa import scala.collection.IndexedSeq import scala.collection.mutable.{ArrayBuffer, Stack} -class ArrayEncoderDecoder(private val encoder: ColumnEncoderDecoder) extends ColumnEncoderDecoder { +class ArrayDecoder(private val encoder: ColumnDecoder) extends ColumnDecoder { override def decode(value: String): IndexedSeq[Any] = { @@ -59,6 +59,4 @@ class ArrayEncoderDecoder(private val encoder: ColumnEncoderDecoder) extends Col result } - override def encode(value: Any): String = ??? - } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala index beca86a1..9af99424 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala @@ -20,4 +20,6 @@ object BigDecimalEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = BigDecimal(value) + def kind = ColumnTypes.Numeric + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala index 2d1a0733..0a8a10f2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala @@ -36,4 +36,6 @@ object BooleanEncoderDecoder extends ColumnEncoderDecoder { } } + def kind = ColumnTypes.Boolean + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala index ca2356e3..cbf2c4e7 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala @@ -20,4 +20,6 @@ object CharEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = value.charAt(0) + def kind = ColumnTypes.Char + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala new file mode 100644 index 00000000..58bddaf5 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.column + +trait ColumnDecoder { + + def decode(value: String): Any = value + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala new file mode 100644 index 00000000..801502b6 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala @@ -0,0 +1,23 @@ +/* + * 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.column + +trait ColumnDecoderRegistry { + + def decode(kind: Int, value: String) : Any + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala new file mode 100644 index 00000000..ed782b9f --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.column + +trait ColumnEncoder { + + def encode(value: Any): String = value.toString + + def kind : Int + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala index 140cd392..d6616d95 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala @@ -16,220 +16,4 @@ package com.github.mauricio.async.db.postgresql.column -import org.joda.time._ -import scala.Some - -object ColumnEncoderDecoder { - - val Bigserial = 20 - val Char = 18 - val CharArray = 1002 - val Smallint = 21 - val SmallintArray = 1005 - val Integer = 23 - val IntegerArray = 1007 - val Numeric = 1700 - // Decimal is the same as Numeric on PostgreSQL - val NumericArray = 1231 - val Real = 700 - val RealArray = 1021 - val Double = 701 - val DoubleArray = 1022 - val Serial = 23 - val Bpchar = 1042 - val BpcharArray = 1014 - val Varchar = 1043 - // Char is the same as Varchar on PostgreSQL - val VarcharArray = 1015 - val Text = 25 - val TextArray = 1009 - val Timestamp = 1114 - val TimestampArray = 1115 - val TimestampWithTimezone = 1184 - val TimestampWithTimezoneArray = 1185 - val Date = 1082 - val DateArray = 1182 - val Time = 1083 - val TimeArray = 1183 - val TimeWithTimezone = 1266 - val TimeWithTimezoneArray = 1270 - val Boolean = 16 - val BooleanArray = 1000 - - val OIDArray = 1028 - val MoneyArray = 791 - val NameArray = 1003 - val UUIDArray = 2951 - val XMLArray = 143 - - private val classes = Map[Class[_], Int]( - classOf[Int] -> Integer, - classOf[Short] -> Integer, - classOf[java.lang.Integer] -> Integer, - classOf[java.lang.Short] -> Integer, - - classOf[Long] -> Bigserial, - classOf[java.lang.Long] -> Bigserial, - - classOf[String] -> Varchar, - classOf[java.lang.String] -> Varchar, - - classOf[Float] -> Real, - classOf[java.lang.Float] -> Real, - - classOf[Double] -> Double, - classOf[java.lang.Double] -> Double, - - classOf[BigDecimal] -> Numeric, - classOf[java.math.BigDecimal] -> Numeric, - - classOf[LocalDate] -> Date, - classOf[LocalTime] -> Time, - classOf[ReadablePartial] -> Time, - classOf[ReadableDateTime] -> Timestamp, - classOf[ReadableInstant] -> Date, - classOf[DateTime] -> Timestamp, - - classOf[java.util.Date] -> Timestamp, - classOf[java.sql.Date] -> Date, - classOf[java.sql.Time] -> Time, - classOf[java.sql.Timestamp] -> Timestamp, - classOf[java.util.Calendar] -> Timestamp, - classOf[java.util.GregorianCalendar] -> Timestamp - ) - - def decoderFor(kind: Int): ColumnEncoderDecoder = { - kind match { - case Boolean => BooleanEncoderDecoder - case BooleanArray => new ArrayEncoderDecoder(BooleanEncoderDecoder) - case Char => CharEncoderDecoder - case CharArray => new ArrayEncoderDecoder(CharEncoderDecoder) - case Bigserial => LongEncoderDecoder - case Smallint => ShortEncoderDecoder - case SmallintArray => new ArrayEncoderDecoder(ShortEncoderDecoder) - case Integer => IntegerEncoderDecoder - case IntegerArray => new ArrayEncoderDecoder(IntegerEncoderDecoder) - case Numeric => BigDecimalEncoderDecoder - case NumericArray => new ArrayEncoderDecoder(BigDecimalEncoderDecoder) - case Real => FloatEncoderDecoder - case RealArray => new ArrayEncoderDecoder(FloatEncoderDecoder) - case Double => DoubleEncoderDecoder - case DoubleArray => new ArrayEncoderDecoder(DoubleEncoderDecoder) - case Text => StringEncoderDecoder - case TextArray => new ArrayEncoderDecoder(StringEncoderDecoder) - case Varchar => StringEncoderDecoder - case VarcharArray => new ArrayEncoderDecoder(StringEncoderDecoder) - case Bpchar => StringEncoderDecoder - case BpcharArray => new ArrayEncoderDecoder(StringEncoderDecoder) - case Timestamp => TimestampEncoderDecoder.Instance - case TimestampArray => new ArrayEncoderDecoder(TimestampEncoderDecoder.Instance) - case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder - case TimestampWithTimezoneArray => new ArrayEncoderDecoder(TimestampWithTimezoneEncoderDecoder) - case Date => DateEncoderDecoder - case DateArray => new ArrayEncoderDecoder(DateEncoderDecoder) - case Time => TimeEncoderDecoder.Instance - case TimeArray => new ArrayEncoderDecoder(TimeEncoderDecoder.Instance) - case TimeWithTimezone => TimeWithTimezoneEncoderDecoder - case TimeWithTimezoneArray => new ArrayEncoderDecoder(TimeWithTimezoneEncoderDecoder) - - case OIDArray => new ArrayEncoderDecoder(StringEncoderDecoder) - case MoneyArray => new ArrayEncoderDecoder(StringEncoderDecoder) - case NameArray => new ArrayEncoderDecoder(StringEncoderDecoder) - case UUIDArray => new ArrayEncoderDecoder(StringEncoderDecoder) - case XMLArray => new ArrayEncoderDecoder(StringEncoderDecoder) - - case _ => StringEncoderDecoder - } - } - - def kindFor(clazz: Class[_]): Int = { - this.classes.get(clazz).getOrElse { - this.classes.find(entry => entry._1.isAssignableFrom(clazz)) match { - case Some(parent) => parent._2 - case None => 0 - } - } - } - - def decode(kind: Int, value: String): Any = { - if (value == null || "NULL" == value) { - null - } else { - decoderFor(kind).decode(value) - } - } - - def encode(value: Any): String = { - if (value == null) { - "NULL" - } else { - decoderFor(kindFor(value.getClass)).encode(value) - } - } - -} - -trait ColumnEncoderDecoder { - - def decode(value: String): Any = value - - def encode(value: Any): String = value.toString - -} - -/* - - public static final int UNSPECIFIED = 0; - public static final int INT2 = 21; - public static final int INT2_ARRAY = 1005; - public static final int INT4 = 23; - public static final int INT4_ARRAY = 1007; - public static final int INT8 = 20; - public static final int INT8_ARRAY = 1016; - public static final int TEXT = 25; - public static final int TEXT_ARRAY = 1009; - public static final int NUMERIC = 1700; - public static final int NUMERIC_ARRAY = 1231; - public static final int FLOAT4 = 700; - public static final int FLOAT4_ARRAY = 1021; - public static final int FLOAT8 = 701; - public static final int FLOAT8_ARRAY = 1022; - public static final int BOOL = 16; - public static final int BOOL_ARRAY = 1000; - public static final int DATE = 1082; - public static final int DATE_ARRAY = 1182; - public static final int TIME = 1083; - public static final int TIME_ARRAY = 1183; - public static final int TIMETZ = 1266; - public static final int TIMETZ_ARRAY = 1270; - public static final int TIMESTAMP = 1114; - public static final int TIMESTAMP_ARRAY = 1115; - public static final int TIMESTAMPTZ = 1184; - public static final int TIMESTAMPTZ_ARRAY = 1185; - public static final int BYTEA = 17; - public static final int BYTEA_ARRAY = 1001; - public static final int VARCHAR = 1043; - public static final int VARCHAR_ARRAY = 1015; - public static final int OID = 26; - public static final int OID_ARRAY = 1028; - public static final int BPCHAR = 1042; - public static final int BPCHAR_ARRAY = 1014; - public static final int MONEY = 790; - public static final int MONEY_ARRAY = 791; - public static final int NAME = 19; - public static final int NAME_ARRAY = 1003; - public static final int BIT = 1560; - public static final int BIT_ARRAY = 1561; - public static final int VOID = 2278; - public static final int INTERVAL = 1186; - public static final int INTERVAL_ARRAY = 1187; - public static final int CHAR = 18; // This is not char(N), this is "char" a single byte type. - public static final int CHAR_ARRAY = 1002; - public static final int VARBIT = 1562; - public static final int VARBIT_ARRAY = 1563; - public static final int UUID = 2950; - public static final int UUID_ARRAY = 2951; - public static final int XML = 142; - public static final int XML_ARRAY = 143; - -*/ \ No newline at end of file +trait ColumnEncoderDecoder extends ColumnEncoder with ColumnDecoder \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala new file mode 100644 index 00000000..54f8dd6a --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala @@ -0,0 +1,25 @@ +/* + * 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.column + +trait ColumnEncoderRegistry { + + def encode( value : Any ) : String + + def kindOf( value : Any ) : Int + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala new file mode 100644 index 00000000..ebba8332 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala @@ -0,0 +1,119 @@ +/* + * 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.column + +object ColumnTypes { + + val Bigserial = 20 + val Char = 18 + val CharArray = 1002 + val Smallint = 21 + val SmallintArray = 1005 + val Integer = 23 + val IntegerArray = 1007 + val Numeric = 1700 + // Decimal is the same as Numeric on PostgreSQL + val NumericArray = 1231 + val Real = 700 + val RealArray = 1021 + val Double = 701 + val DoubleArray = 1022 + val Serial = 23 + val Bpchar = 1042 + val BpcharArray = 1014 + val Varchar = 1043 + // Char is the same as Varchar on PostgreSQL + val VarcharArray = 1015 + val Text = 25 + val TextArray = 1009 + val Timestamp = 1114 + val TimestampArray = 1115 + val TimestampWithTimezone = 1184 + val TimestampWithTimezoneArray = 1185 + val Date = 1082 + val DateArray = 1182 + val Time = 1083 + val TimeArray = 1183 + val TimeWithTimezone = 1266 + val TimeWithTimezoneArray = 1270 + val Boolean = 16 + val BooleanArray = 1000 + + val OIDArray = 1028 + val MoneyArray = 791 + val NameArray = 1003 + val UUIDArray = 2951 + val XMLArray = 143 + +} + +/* + + public static final int UNSPECIFIED = 0; + public static final int INT2 = 21; + public static final int INT2_ARRAY = 1005; + public static final int INT4 = 23; + public static final int INT4_ARRAY = 1007; + public static final int INT8 = 20; + public static final int INT8_ARRAY = 1016; + public static final int TEXT = 25; + public static final int TEXT_ARRAY = 1009; + public static final int NUMERIC = 1700; + public static final int NUMERIC_ARRAY = 1231; + public static final int FLOAT4 = 700; + public static final int FLOAT4_ARRAY = 1021; + public static final int FLOAT8 = 701; + public static final int FLOAT8_ARRAY = 1022; + public static final int BOOL = 16; + public static final int BOOL_ARRAY = 1000; + public static final int DATE = 1082; + public static final int DATE_ARRAY = 1182; + public static final int TIME = 1083; + public static final int TIME_ARRAY = 1183; + public static final int TIMETZ = 1266; + public static final int TIMETZ_ARRAY = 1270; + public static final int TIMESTAMP = 1114; + public static final int TIMESTAMP_ARRAY = 1115; + public static final int TIMESTAMPTZ = 1184; + public static final int TIMESTAMPTZ_ARRAY = 1185; + public static final int BYTEA = 17; + public static final int BYTEA_ARRAY = 1001; + public static final int VARCHAR = 1043; + public static final int VARCHAR_ARRAY = 1015; + public static final int OID = 26; + public static final int OID_ARRAY = 1028; + public static final int BPCHAR = 1042; + public static final int BPCHAR_ARRAY = 1014; + public static final int MONEY = 790; + public static final int MONEY_ARRAY = 791; + public static final int NAME = 19; + public static final int NAME_ARRAY = 1003; + public static final int BIT = 1560; + public static final int BIT_ARRAY = 1561; + public static final int VOID = 2278; + public static final int INTERVAL = 1186; + public static final int INTERVAL_ARRAY = 1187; + public static final int CHAR = 18; // This is not char(N), this is "char" a single byte type. + public static final int CHAR_ARRAY = 1002; + public static final int VARBIT = 1562; + public static final int VARBIT_ARRAY = 1563; + public static final int UUID = 2950; + public static final int UUID_ARRAY = 2951; + public static final int XML = 142; + public static final int XML_ARRAY = 143; + +*/ \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala index 041b4ec8..13a07440 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala @@ -36,4 +36,6 @@ object DateEncoderDecoder extends ColumnEncoderDecoder { } } + def kind = ColumnTypes.Date + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala new file mode 100644 index 00000000..f1d70abd --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala @@ -0,0 +1,88 @@ +/* + * 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.column + +import com.github.mauricio.async.db.postgresql.column.ColumnTypes._ + +object DefaultColumnDecoderRegistry { + val Instance = new DefaultColumnDecoderRegistry() +} + +class DefaultColumnDecoderRegistry extends ColumnDecoderRegistry { + + def decode(kind: Int, value: String) : Any = decoderFor(kind).decode(value) + + def decoderFor(kind: Int): ColumnDecoder = { + kind match { + case Boolean => BooleanEncoderDecoder + case BooleanArray => new ArrayDecoder(BooleanEncoderDecoder) + + case ColumnTypes.Char => CharEncoderDecoder + case CharArray => new ArrayDecoder(CharEncoderDecoder) + + case Bigserial => LongEncoderDecoder + + case Smallint => ShortEncoderDecoder + case SmallintArray => new ArrayDecoder(ShortEncoderDecoder) + + case ColumnTypes.Integer => IntegerEncoderDecoder + case IntegerArray => new ArrayDecoder(IntegerEncoderDecoder) + + case ColumnTypes.Numeric => BigDecimalEncoderDecoder + case NumericArray => new ArrayDecoder(BigDecimalEncoderDecoder) + + case Real => FloatEncoderDecoder + case RealArray => new ArrayDecoder(FloatEncoderDecoder) + + case ColumnTypes.Double => DoubleEncoderDecoder + case DoubleArray => new ArrayDecoder(DoubleEncoderDecoder) + + case Text => StringEncoderDecoder + case TextArray => new ArrayDecoder(StringEncoderDecoder) + + case Varchar => StringEncoderDecoder + case VarcharArray => new ArrayDecoder(StringEncoderDecoder) + + case Bpchar => StringEncoderDecoder + case BpcharArray => new ArrayDecoder(StringEncoderDecoder) + + case Timestamp => TimestampEncoderDecoder.Instance + case TimestampArray => new ArrayDecoder(TimestampEncoderDecoder.Instance) + + case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder + case TimestampWithTimezoneArray => new ArrayDecoder(TimestampWithTimezoneEncoderDecoder) + + case Date => DateEncoderDecoder + case DateArray => new ArrayDecoder(DateEncoderDecoder) + + case Time => TimeEncoderDecoder.Instance + case TimeArray => new ArrayDecoder(TimeEncoderDecoder.Instance) + + case TimeWithTimezone => TimeWithTimezoneEncoderDecoder + case TimeWithTimezoneArray => new ArrayDecoder(TimeWithTimezoneEncoderDecoder) + + case OIDArray => new ArrayDecoder(StringEncoderDecoder) + case MoneyArray => new ArrayDecoder(StringEncoderDecoder) + case NameArray => new ArrayDecoder(StringEncoderDecoder) + case UUIDArray => new ArrayDecoder(StringEncoderDecoder) + case XMLArray => new ArrayDecoder(StringEncoderDecoder) + + case _ => StringEncoderDecoder + } + } + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala new file mode 100644 index 00000000..b2d89b62 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala @@ -0,0 +1,150 @@ +/* + * 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.column + +import org.joda.time._ +import scala.collection.JavaConversions._ + +object DefaultColumnEncoderRegistry { + val Instance = new DefaultColumnEncoderRegistry() +} + +class DefaultColumnEncoderRegistry extends ColumnEncoderRegistry { + + private val classesSequence = List( + classOf[Int] -> IntegerEncoderDecoder, + classOf[java.lang.Integer] -> IntegerEncoderDecoder, + + classOf[java.lang.Short] -> IntegerEncoderDecoder, + classOf[Short] -> ShortEncoderDecoder, + + classOf[Long] -> LongEncoderDecoder, + classOf[java.lang.Long] -> LongEncoderDecoder, + + classOf[String] -> StringEncoderDecoder, + classOf[java.lang.String] -> StringEncoderDecoder, + + classOf[Float] -> FloatEncoderDecoder, + classOf[java.lang.Float] -> FloatEncoderDecoder, + + classOf[Double] -> DoubleEncoderDecoder, + classOf[java.lang.Double] -> DoubleEncoderDecoder, + + classOf[BigDecimal] -> BigDecimalEncoderDecoder, + classOf[java.math.BigDecimal] -> BigDecimalEncoderDecoder, + + classOf[LocalDate] -> DateEncoderDecoder, + classOf[LocalTime] -> TimeEncoderDecoder.Instance, + classOf[DateTime] -> TimestampWithTimezoneEncoderDecoder, + classOf[ReadablePartial] -> TimeEncoderDecoder.Instance, + classOf[ReadableDateTime] -> TimestampWithTimezoneEncoderDecoder, + classOf[ReadableInstant] -> DateEncoderDecoder, + + classOf[java.util.Date] -> TimestampWithTimezoneEncoderDecoder, + classOf[java.sql.Date] -> DateEncoderDecoder, + classOf[java.sql.Time] -> TimeEncoderDecoder.Instance, + classOf[java.sql.Timestamp] -> TimestampWithTimezoneEncoderDecoder, + classOf[java.util.Calendar] -> TimestampWithTimezoneEncoderDecoder, + classOf[java.util.GregorianCalendar] -> TimestampWithTimezoneEncoderDecoder) + + private val classes: Map[Class[_], ColumnEncoder] = classesSequence.toMap + + def encode(value: Any): String = { + + if (value == null) { + return null + } + + val encoder = this.classes.get(value.getClass) + + if (encoder.isDefined) { + encoder.get.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 => { + this.classesSequence.find(entry => entry._1.isAssignableFrom(value.getClass)) match { + case Some(parent) => parent._2.encode(value) + case None => value.toString + } + } + } + + } + + } + + def encodeArray(collection: Traversable[_]): String = { + val builder = new StringBuilder() + + builder.append('{') + + val result = collection.map { + item => + + if (item == null) { + "NULL" + } else { + if (this.shouldQuote(item)) { + "\"" + this.encode(item).replaceAllLiterally("\"", """\"""") + "\"" + } else { + this.encode(item) + } + } + + }.mkString(",") + + builder.append(result) + builder.append('}') + + builder.toString() + } + + def shouldQuote(value: Any): Boolean = { + value match { + case n: java.lang.Number => false + case n: Int => false + case n: Short => false + case n: Long => false + case n: Float => false + case n: Double => false + case n: java.lang.Iterable[_] => false + case n: Traversable[_] => false + case n: Array[_] => false + case _ => true + } + } + + def kindOf(value: Any): Int = { + if ( value == null ) { + 0 + } else { + this.classes.get(value.getClass) match { + case Some( encoder ) => encoder.kind + case None => 0 + } + } + } +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala index 6d08995e..b4df457f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala @@ -18,4 +18,5 @@ package com.github.mauricio.async.db.postgresql.column object DoubleEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Double = value.toDouble + def kind = ColumnTypes.Double } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala index a3eb7172..fa2c245d 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala @@ -18,4 +18,5 @@ package com.github.mauricio.async.db.postgresql.column object FloatEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Float = value.toFloat + def kind = ColumnTypes.Real } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala index 07fc1653..14d7d3f1 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala @@ -20,4 +20,6 @@ object IntegerEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Int = value.toInt + def kind = ColumnTypes.Integer + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala index 8acc9397..e81f48a3 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala @@ -18,4 +18,5 @@ package com.github.mauricio.async.db.postgresql.column object LongEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Long = value.toLong + def kind = ColumnTypes.Bigserial } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala index 9362bdad..cca4bacb 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala @@ -19,5 +19,6 @@ package com.github.mauricio.async.db.postgresql.column object ShortEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = value.toShort + def kind = ColumnTypes.Smallint } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala index 7c1115b8..4d18f98e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala @@ -18,4 +18,5 @@ package com.github.mauricio.async.db.postgresql.column object StringEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): String = value + def kind = ColumnTypes.Varchar } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala index fd239c99..f80e85f1 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala @@ -37,4 +37,6 @@ class TimeEncoderDecoder extends ColumnEncoderDecoder { this.parser.print(value.asInstanceOf[LocalTime]) } + def kind = ColumnTypes.Time + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala index 60a81eb2..387b2a51 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala @@ -24,4 +24,6 @@ object TimeWithTimezoneEncoderDecoder extends TimeEncoderDecoder { override def formatter = format + override def kind = ColumnTypes.TimeWithTimezone + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala index 57e25971..90236ca1 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala @@ -46,4 +46,6 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { } } + def kind = ColumnTypes.Timestamp + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala index b24214ac..a36deba8 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala @@ -24,4 +24,6 @@ object TimestampWithTimezoneEncoderDecoder extends TimestampEncoderDecoder { override def formatter = format + override def kind = ColumnTypes.TimestampWithTimezone + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 4cddae40..cad61016 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.column.{ColumnEncoderRegistry, ColumnEncoderDecoder} import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class ExecutePreparedStatementEncoder(charset: Charset) extends Encoder { +class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { def encode(message: FrontendMessage): ChannelBuffer = { @@ -49,7 +49,7 @@ class ExecutePreparedStatementEncoder(charset: Charset) extends Encoder { if (value == null) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes(charset) + val encoded = encoder.encode(value).getBytes(charset) bindBuffer.writeInt(encoded.length) bindBuffer.writeBytes(encoded) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index ae67f189..8eac943b 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.column.{ColumnEncoderRegistry, ColumnEncoderDecoder} import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class PreparedStatementOpeningEncoder(charset: Charset) extends Encoder { +class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { override def encode(message: FrontendMessage): ChannelBuffer = { @@ -68,7 +68,7 @@ class PreparedStatementOpeningEncoder(charset: Charset) extends Encoder { if (value == null) { bindBuffer.writeInt(-1) } else { - val encoded = ColumnEncoderDecoder.encode(value).getBytes(charset) + val encoded = encoder.encode(value).getBytes(charset) bindBuffer.writeInt(encoded.length) bindBuffer.writeBytes(encoded) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala index 7e991d56..ffff5651 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala @@ -16,8 +16,6 @@ package com.github.mauricio.async.db.postgresql.messages.backend -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder - class ColumnData( val name: String, val tableObjectId: Int, @@ -25,8 +23,4 @@ class ColumnData( val dataType: Int, val dataTypeSize: Int, val dataTypeModifier: Int, - val fieldFormat: Int) { - - val decoder = ColumnEncoderDecoder.decoderFor(this.dataType) - -} + val fieldFormat: Int) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index ca563154..76c413e6 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry -class PreparedStatementExecuteMessage(query: String, values: Seq[Any]) - extends PreparedStatementMessage(Message.Execute, query, values) +class PreparedStatementExecuteMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(Message.Execute, query, values, encoderRegistry) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index 0bb3db1a..f8c4b5df 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -16,18 +16,19 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry -class PreparedStatementMessage(kind: Char, val query: String, val values: Seq[Any]) extends FrontendMessage(kind) { +class PreparedStatementMessage( + kind: Char, + val query: String, + val values: Seq[Any], + encoderRegistry : ColumnEncoderRegistry + ) + extends FrontendMessage(kind) { val valueTypes: Seq[Int] = values.map { value => - if (value == null) { - 0 - } else { - ColumnEncoderDecoder.kindFor(value.asInstanceOf[AnyRef].getClass) - } - + encoderRegistry.kindOf(value) } } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index 97294cb6..9d38fa35 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry -class PreparedStatementOpeningMessage(query: String, values: Seq[Any]) - extends PreparedStatementMessage(Message.Parse, query, values) \ No newline at end of file +class PreparedStatementOpeningMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(Message.Parse, query, values, encoderRegistry) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index 6b00bce6..18d3f18e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -21,6 +21,7 @@ import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler import concurrent.Await import concurrent.duration._ import org.apache.commons.pool.PoolableObjectFactory +import scala.language.postfixOps class ConnectionObjectFactory( configuration: Configuration) diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala index ba8360a7..6cc99042 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala @@ -25,8 +25,6 @@ object ArrayStreamingParser { def parse(content: String, delegate: ArrayStreamingParserDelegate) { - log.debug("Processing array [{}]", content) - var index = 0 var escaping = false var quoted = false diff --git a/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala index d6794ce8..bbc5c049 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -32,6 +32,7 @@ object ChannelUtils { } + /* def printBuffer(b: ChannelBuffer): Unit = { val bytes = new Array[Byte](b.readableBytes()) @@ -41,7 +42,7 @@ object ChannelUtils { println(bytes.mkString("-")) - } + }*/ def writeCString(content: String, b: ChannelBuffer, charset: Charset): Unit = { b.writeBytes(content.getBytes(charset)) diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala index 9a2df734..34820368 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala @@ -51,7 +51,7 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { (smallint_column, text_column, timestamp_column) values ( '{1,2,3,4}', - '{"some,\"comma,separated,text","another line of text"}', + '{"some,\"comma,separated,text","another line of text",NULL}', '{"2013-04-06 01:15:10.528-03","2013-04-06 01:15:08.528-03"}' )""" @@ -65,7 +65,7 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { executeDdl(handler, insert, 1) val result = executeQuery(handler, "select * from type_test_table").rows.get result("smallint_column", 0) === List(1,2,3,4) - result("text_column", 0) === List("some,\"comma,separated,text", "another line of text" ) + result("text_column", 0) === List("some,\"comma,separated,text", "another line of text", null ) result("timestamp_column", 0) === List( TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:10.528-03"), TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:08.528-03") diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index 5c02cd1c..26b57193 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -60,16 +60,16 @@ class MessageDecoderSpec extends Specification { val textBytes = text.getBytes(CharsetUtil.UTF_8) buffer.writeByte('E') - buffer.writeInt(textBytes.length + 4 + 1) + buffer.writeInt(textBytes.length + 4 + 1 + 1) buffer.writeByte('M') buffer.writeBytes(textBytes) + buffer.writeByte(0) val result = this.decoder.decode(null, null, buffer).asInstanceOf[ErrorMessage] - List( - result.message === text, - buffer.readerIndex() === (textBytes.length + 5) - ) + result.message === text + buffer.readerIndex() === (textBytes.length + 4 + 1 + 1 + 1) + } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala index 1bf8fc32..428e903e 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala @@ -25,7 +25,7 @@ class ArrayEncoderDecoderSpec extends Specification { "parse an array of numbers" in { val numbers = "{1,2,3}" - val encoder = new ArrayEncoderDecoder(IntegerEncoderDecoder) + val encoder = new ArrayDecoder(IntegerEncoderDecoder) val result = encoder.decode(numbers) @@ -35,7 +35,7 @@ class ArrayEncoderDecoderSpec extends Specification { "parse an array of array of numbers" in { val numbers = "{{1,2,3},{4,5,6}}" - val encoder = new ArrayEncoderDecoder(IntegerEncoderDecoder) + val encoder = new ArrayDecoder(IntegerEncoderDecoder) val result = encoder.decode(numbers) diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala new file mode 100644 index 00000000..d1daa410 --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala @@ -0,0 +1,39 @@ +/* + * 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.column + +import org.specs2.mutable.Specification + +class DefaultColumnEncoderRegistrySpec extends Specification { + + val registry = new DefaultColumnEncoderRegistry() + + "registry" should { + + "correctly render an array of strings with nulls" in { + val items = Array( "some", """text \ hoes " here to be seen""", null, "all, right" ) + registry.encode( items ) === """{"some","text \ hoes \" here to be seen",NULL,"all, right"}""" + } + + "correctly render an array of numbers" in { + val items = Array(Array(1,2,3),Array(4,5,6),Array(7,null,8)) + registry.encode( items ) === "{{1,2,3},{4,5,6},{7,NULL,8}}" + } + + } + +} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala index 06eac711..e8cb95c1 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala @@ -14,10 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ErrorMessage} -import com.github.mauricio.async.db.postgresql.parsers.ErrorParser import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification @@ -28,14 +27,17 @@ class ParserESpec extends Specification { "correctly parse an error message" in { - val error = "this is my error message" + val content = "this is my error message" + val error = content.getBytes(CharsetUtil.UTF_8) val buffer = ChannelBuffers.dynamicBuffer() buffer.writeByte('M') - buffer.writeBytes(error.getBytes) + buffer.writeBytes(error) + buffer.writeByte(0) val message = new ErrorParser(CharsetUtil.UTF_8).parseMessage(buffer).asInstanceOf[ErrorMessage] - List(message.message === error, message.name === Message.Error) + message.message === content + message.name === Message.Error } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala index 93251981..46d410f8 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala @@ -14,10 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ProcessData} -import com.github.mauricio.async.db.postgresql.parsers.BackendKeyDataParser import org.jboss.netty.buffer.ChannelBuffers import org.specs2.mutable.Specification diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala index db2aeda3..bb3896cc 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala @@ -14,10 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql.parsers +package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ParameterStatusMessage} -import com.github.mauricio.async.db.postgresql.parsers.ParameterStatusParser import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil @@ -43,11 +42,10 @@ class ParserSSpec extends Specification { val content = this.parser.parseMessage(buffer).asInstanceOf[ParameterStatusMessage] - List( - content.key === key, - content.value === value, - content.name === Message.ParameterStatus, - buffer.readerIndex() === buffer.writerIndex()) + content.key === key + content.value === value + content.name === Message.ParameterStatus + buffer.readerIndex() === buffer.writerIndex() } } From 057cb4f4e94456ff6d7e4ef3d8618e00271cd342 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 23 Apr 2013 22:26:05 -0300 Subject: [PATCH 022/357] Implementing parser for a NoData message when it comes back from prepared statements --- .../DatabaseConnectionHandler.scala | 3 + .../async/db/postgresql/MessageDecoder.scala | 4 +- .../backend/NoData.scala} | 11 +--- .../parsers/AuthenticationStartupParser.scala | 2 +- .../parsers/BackendKeyDataParser.scala | 2 +- .../parsers/CommandCompleteParser.scala | 2 +- .../db/postgresql/parsers/DataRowParser.scala | 2 +- .../parsers/InformationParser.scala | 2 +- .../db/postgresql/parsers/MessageParser.scala | 38 ++----------- .../parsers/MessageParsersRegistry.scala | 57 +++++++++++++++++++ .../parsers/ParameterStatusParser.scala | 2 +- .../parsers/ReadyForQueryParser.scala | 2 +- .../parsers/ReturningMessageParser.scala | 2 +- .../parsers/RowDescriptionParser.scala | 2 +- .../async/db/postgresql/ArrayTypesSpec.scala | 47 +++++++++------ ...coderSpec.scala => ArrayDecoderSpec.scala} | 2 +- 16 files changed, 108 insertions(+), 72 deletions(-) rename src/main/scala/com/github/mauricio/async/db/postgresql/{parsers/Decoder.scala => messages/backend/NoData.scala} (72%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala rename src/test/scala/com/github/mauricio/async/db/postgresql/column/{ArrayEncoderDecoderSpec.scala => ArrayDecoderSpec.scala} (95%) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index e324075f..91ee762a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -187,6 +187,9 @@ class DatabaseConnectionHandler case Message.Error => { this.onError(m.asInstanceOf[ErrorMessage]) } + case Message.NoData => { + log.debug("Statement response does not contain any data") + } case Message.Notice => { log.info("notice -> {}", m.asInstanceOf[NoticeMessage]) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala index a34075e9..0aaa2837 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParser} +import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParsersRegistry} import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import messages.backend.Message @@ -30,7 +30,7 @@ object MessageDecoder { class MessageDecoder(charset: Charset) extends FrameDecoder { - private val parser = new MessageParser(charset) + private val parser = new MessageParsersRegistry(charset) override def decode(ctx: ChannelHandlerContext, c: Channel, b: ChannelBuffer): Object = { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala similarity index 72% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala rename to src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala index 8f002003..13eb8b9d 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/Decoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala @@ -14,13 +14,6 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.parsers +package com.github.mauricio.async.db.postgresql.messages.backend -import com.github.mauricio.async.db.postgresql.messages.backend.Message -import org.jboss.netty.buffer.ChannelBuffer - -trait Decoder { - - def parseMessage(buffer: ChannelBuffer): Message - -} +object NoData extends Message( Message.NoData ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index 6ffa47b2..220c1206 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -20,7 +20,7 @@ import com.github.mauricio.async.db.postgresql.exceptions.UnsupportedAuthenticat import com.github.mauricio.async.db.postgresql.messages.backend.{AuthenticationChallengeMD5, AuthenticationChallengeCleartextMessage, AuthenticationOkMessage, Message} import org.jboss.netty.buffer.ChannelBuffer -object AuthenticationStartupParser extends Decoder { +object AuthenticationStartupParser extends MessageParser { val AuthenticationOk = 0 val AuthenticationKerberosV5 = 2 diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala index eaa501fa..c8a8bcbc 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ProcessData, Message} import org.jboss.netty.buffer.ChannelBuffer -object BackendKeyDataParser extends Decoder { +object BackendKeyDataParser extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { new ProcessData(b.readInt(), b.readInt()) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 9b01f32f..5152c5d1 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -class CommandCompleteParser(charset: Charset) extends Decoder { +class CommandCompleteParser(charset: Charset) extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala index ba2ac279..e9d9e79e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{DataRowMessage, Message} import org.jboss.netty.buffer.ChannelBuffer -object DataRowParser extends Decoder { +object DataRowParser extends MessageParser { def parseMessage(buffer: ChannelBuffer): Message = { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 60fd73de..062095c5 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -abstract class InformationParser(charset: Charset) extends Decoder { +abstract class InformationParser(charset: Charset) extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala index bd9ed3e7..44a60e76 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala @@ -16,41 +16,11 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.exceptions.ParserNotAvailableException -import com.github.mauricio.async.db.postgresql.messages.backend.{ParseComplete, CloseComplete, BindComplete, Message} -import java.nio.charset.Charset +import com.github.mauricio.async.db.postgresql.messages.backend.Message import org.jboss.netty.buffer.ChannelBuffer -class MessageParser(charset: Charset) { +trait MessageParser { - private val parsers = Map( - Message.Authentication -> AuthenticationStartupParser, - Message.BackendKeyData -> BackendKeyDataParser, - Message.BindComplete -> new ReturningMessageParser(BindComplete.Instance), - Message.CloseComplete -> new ReturningMessageParser(CloseComplete.Instance), - Message.CommandComplete -> new CommandCompleteParser(charset), - Message.DataRow -> DataRowParser, - Message.Error -> new ErrorParser(charset), - Message.Notice -> new NoticeParser(charset), - Message.ParameterStatus -> new ParameterStatusParser(charset), - Message.ParseComplete -> new ReturningMessageParser(ParseComplete.Instance), - Message.RowDescription -> new RowDescriptionParser(charset), - Message.ReadyForQuery -> ReadyForQueryParser - ) + def parseMessage(buffer: ChannelBuffer): Message - def parserFor(t: Char): Decoder = { - val option = this.parsers.get(t) - - if (option.isDefined) { - option.get - } else { - throw new ParserNotAvailableException(t) - } - - } - - def parse(t: Char, b: ChannelBuffer): Message = { - this.parserFor(t).parseMessage(b) - } - -} \ No newline at end of file +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala new file mode 100644 index 00000000..2ee5cb95 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -0,0 +1,57 @@ +/* + * 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.parsers + +import com.github.mauricio.async.db.postgresql.exceptions.ParserNotAvailableException +import com.github.mauricio.async.db.postgresql.messages.backend._ +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer + +class MessageParsersRegistry(charset: Charset) { + + private val parsers = Map( + Message.Authentication -> AuthenticationStartupParser, + Message.BackendKeyData -> BackendKeyDataParser, + Message.BindComplete -> new ReturningMessageParser(BindComplete.Instance), + Message.CloseComplete -> new ReturningMessageParser(CloseComplete.Instance), + Message.CommandComplete -> new CommandCompleteParser(charset), + Message.DataRow -> DataRowParser, + Message.Error -> new ErrorParser(charset), + Message.NoData -> new ReturningMessageParser(NoData), + Message.Notice -> new NoticeParser(charset), + Message.ParameterStatus -> new ParameterStatusParser(charset), + Message.ParseComplete -> new ReturningMessageParser(ParseComplete.Instance), + Message.RowDescription -> new RowDescriptionParser(charset), + Message.ReadyForQuery -> ReadyForQueryParser + ) + + private def parserFor(t: Char): MessageParser = { + val option = this.parsers.get(t) + + if (option.isDefined) { + option.get + } else { + throw new ParserNotAvailableException(t) + } + + } + + def parse(t: Char, b: ChannelBuffer): Message = { + this.parserFor(t).parseMessage(b) + } + +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index 057d7d8f..a4b0cd33 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -class ParameterStatusParser(charset: Charset) extends Decoder { +class ParameterStatusParser(charset: Charset) extends MessageParser { import ChannelUtils._ diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala index 13b2d062..bc7201f2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ReadyForQueryMessage, Message} import org.jboss.netty.buffer.ChannelBuffer -object ReadyForQueryParser extends Decoder { +object ReadyForQueryParser extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { new ReadyForQueryMessage(b.readByte().asInstanceOf[Char]) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala index f796e20c..8f4fec18 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.Message import org.jboss.netty.buffer.ChannelBuffer -class ReturningMessageParser(val message: Message) extends Decoder { +class ReturningMessageParser(val message: Message) extends MessageParser { def parseMessage(buffer: ChannelBuffer): Message = { this.message } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index c8c42ef5..4f4eb951 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -59,7 +59,7 @@ The format code being used for the field. Currently will be zero (text) or one ( */ -class RowDescriptionParser(charset: Charset) extends Decoder { +class RowDescriptionParser(charset: Charset) extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala index 34820368..0b0bc122 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala @@ -21,23 +21,6 @@ import com.github.mauricio.async.db.postgresql.column.TimestampWithTimezoneEncod class ArrayTypesSpec extends Specification with DatabaseTestHelper { - val create = """create temp table type_test_table ( - bigserial_column bigserial not null, - smallint_column smallint[] not null, - integer_column integer[] not null, - decimal_column decimal(10,4)[], - real_column real[], - double_column double[] precision, - serial_column serial[] not null, - varchar_column varchar(255)[], - text_column text[], - timestamp_column timestamp[], - date_column date[], - time_column time[], - boolean_column boolean[], - constraint bigserial_column_pkey primary key (bigserial_column) - )""" - val simpleCreate = """create temp table type_test_table ( bigserial_column bigserial not null, smallint_column integer[] not null, @@ -55,6 +38,10 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { '{"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 (?,?,?)""" + "connection" should { "correctly parse the array type" in { @@ -74,6 +61,32 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { } + "correctly send arrays using prepared statements" in { + + val timestamps = List( + TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:10.528-03"), + TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:08.528-03") + ) + val numbers = List(1,2,3,4) + val texts = List("some,\"comma,separated,text", "another line of text", 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("smallint_column", 0) === numbers + result("text_column", 0) === texts + result("timestamp_column", 0) === timestamps + } + + } + } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala similarity index 95% rename from src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala rename to src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala index 428e903e..f248d6b9 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayEncoderDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.postgresql.column import org.specs2.mutable.Specification -class ArrayEncoderDecoderSpec extends Specification { +class ArrayDecoderSpec extends Specification { "encoder/decoder" should { From 8a1c4f9b6b310d22327ef7c03c8717f6c02ea9a4 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 24 Apr 2013 14:16:03 -0300 Subject: [PATCH 023/357] Initial implementation for object pool --- .../async/db/postgresql/MutableQuery.scala | 2 +- .../db/postgresql/pool/AsyncObjectPool.scala | 27 ++ .../pool/ConnectionObjectFactory.scala | 54 ++-- .../db/postgresql/pool/ObjectFactory.scala | 27 ++ .../pool/PoolAlreadyTerminatedException.scala | 20 ++ .../postgresql/pool/PoolConfiguration.scala | 24 ++ .../pool/PoolExhaustedException.scala | 19 ++ .../pool/SingleThreadedAsyncObjectPool.scala | 244 ++++++++++++++++++ .../async/db/util/ExecutorServiceUtils.scala | 7 +- .../Worker.scala} | 38 ++- .../SingleThreadedAsyncObjectPoolSpec.scala | 161 ++++++++++++ 11 files changed, 589 insertions(+), 34 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala rename src/main/scala/com/github/mauricio/async/db/{postgresql/pool/ConnectionPool.scala => util/Worker.scala} (51%) create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala index 03554351..9264195c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala @@ -18,11 +18,11 @@ package com.github.mauricio.async.db.postgresql import collection.mutable.ArrayBuffer import com.github.mauricio.async.db.ResultSet +import com.github.mauricio.async.db.postgresql.column.ColumnDecoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.postgresql.column.ColumnDecoderRegistry object MutableQuery { val log = Log.get[MutableQuery] diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala new file mode 100644 index 00000000..0d8af46c --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala @@ -0,0 +1,27 @@ +/* + * 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.pool + +import scala.concurrent.Future + +trait AsyncObjectPool[T] { + + def take: Future[T] + def giveBack( item : T ) : Future[AsyncObjectPool[T]] + def close : Future[AsyncObjectPool[T]] + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index 18d3f18e..62893228 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -18,34 +18,52 @@ package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.Configuration import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler -import concurrent.Await -import concurrent.duration._ -import org.apache.commons.pool.PoolableObjectFactory +import scala.concurrent.duration._ import scala.language.postfixOps +import scala.concurrent.Await +import com.github.mauricio.async.db.util.Log +import scala.util.{Success, Failure, Try} -class ConnectionObjectFactory( - configuration: Configuration) - extends PoolableObjectFactory[DatabaseConnectionHandler] { +object ConnectionObjectFactory { + val log = Log.get[ConnectionObjectFactory] +} + +class ConnectionObjectFactory( val configuration : Configuration ) extends ObjectFactory[DatabaseConnectionHandler] { + + import ConnectionObjectFactory.log - def makeObject(): DatabaseConnectionHandler = { + def create: DatabaseConnectionHandler = { val connection = new DatabaseConnectionHandler(configuration) - Await.result(connection.connect, 5 seconds) + Await.result(connection.connect, 5.seconds) + connection } - def destroyObject(obj: DatabaseConnectionHandler) { - obj.disconnect + def destroy(item: DatabaseConnectionHandler) { + item.disconnect } - def validateObject(obj: DatabaseConnectionHandler): Boolean = { - obj.isConnected - } + def validate(item: DatabaseConnectionHandler): Try[DatabaseConnectionHandler] = { + val result : Try[DatabaseConnectionHandler] = Try({ + Await.result( item.sendQuery("SELECT 0"), 5.seconds ) + item + }) - def activateObject(obj: DatabaseConnectionHandler) { - //no op + result match { + case Failure(e) => { + try { + if ( item.isConnected ) { + item.disconnect + } + } catch { + case e : Exception => log.error("Failed disconnecting object", e) + } + result + } + case Success(i) => { + result + } + } } - def passivateObject(obj: DatabaseConnectionHandler) { - //no op - } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala new file mode 100644 index 00000000..3beb8d1b --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala @@ -0,0 +1,27 @@ +/* + * 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.pool + +import scala.util.Try + +trait ObjectFactory[T] { + + def create : T + def destroy( item : T ) + def validate( item : T ) : Try[T] + +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala new file mode 100644 index 00000000..dbe78e43 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala @@ -0,0 +1,20 @@ +/* + * 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.pool + +class PoolAlreadyTerminatedException extends IllegalStateException( "This pool has already been terminated" ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala new file mode 100644 index 00000000..3b1b396f --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala @@ -0,0 +1,24 @@ +/* + * 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.pool + +case class PoolConfiguration( + maxObjects: Int, + maxIdle: Long, + maxQueueSize: Int, + validationInterval: Long = 5000 + ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala new file mode 100644 index 00000000..9fb82c58 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala @@ -0,0 +1,19 @@ +/* + * 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.pool + +class PoolExhaustedException( message : String ) extends IllegalStateException( message ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala new file mode 100644 index 00000000..5df4f66f --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala @@ -0,0 +1,244 @@ +/* + * 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.pool + +import com.github.mauricio.async.db.util.{Log, Worker} +import java.util.concurrent.atomic.AtomicLong +import java.util.{TimerTask, Timer} +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.{Promise, Future} +import scala.util.{Failure, Success} + +object SingleThreadedAsyncObjectPool { + val Counter = new AtomicLong() + val log = Log.get[SingleThreadedAsyncObjectPool[Nothing]] +} + +/** + * + * Implements an {@link AsyncObjectPool} using a single thread from a fixed executor service with + * a single thread as an event loop to cause all calls to be sequential. + * + * @param factory + * @param configuration + * @tparam T + */ + +class SingleThreadedAsyncObjectPool[T]( + factory: ObjectFactory[T], + configuration: PoolConfiguration) extends AsyncObjectPool[T] { + + import SingleThreadedAsyncObjectPool.{Counter, log} + + private val mainPool = new Worker() + private val poolables = new ArrayBuffer[PoolableHolder[T]](configuration.maxObjects) + private val checkouts = new ArrayBuffer[T](configuration.maxObjects) + private val waitQueue = new ArrayBuffer[Promise[T]](configuration.maxQueueSize) + private val timer = new Timer("async-object-pool-timer-" + Counter.incrementAndGet(), true) + timer.scheduleAtFixedRate(new TimerTask { + def run() { + mainPool.action { + validateObjects + } + } + }, configuration.validationInterval, configuration.validationInterval) + + private var closed = false + + /** + * + * Asks for an object from the pool, this object should be returned to the pool when not in use anymore. + * + * @return + */ + + def take: Future[T] = { + + if (this.closed) { + return Promise.failed(new PoolAlreadyTerminatedException).future + } + + val promise = Promise[T]() + this.checkout(promise) + promise.future + } + + /** + * + * Returns an object to the pool. The object is validated before being added to the collection + * of available objects to make sure we have a usable object. If the object isn't valid it's discarded. + * + * @param item + * @return + */ + + def giveBack(item: T): Future[AsyncObjectPool[T]] = { + val promise = Promise[AsyncObjectPool[T]]() + this.mainPool.action { + this.factory.validate(item) match { + case Success(item) => { + this.addBack(item, promise) + } + case Failure(e) => { + promise.failure(e) + } + } + } + promise.future + } + + def isFull: Boolean = this.poolables.isEmpty && this.checkouts.size == configuration.maxObjects + + def close: Future[AsyncObjectPool[T]] = { + val promise = Promise[AsyncObjectPool[T]]() + + this.mainPool.action { + if (!this.closed) { + try { + this.timer.cancel() + this.mainPool.shutdown + this.closed = true + (this.poolables.map(i => i.item) ++ this.checkouts).foreach(item => factory.destroy(item)) + promise.success(this) + } catch { + case e: Exception => promise.failure(e) + } + } else { + promise.success(this) + } + } + + promise.future + } + + def availables: Traversable[T] = this.poolables.map(item => item.item) + + def inUse: Traversable[T] = this.checkouts + + def queued: Traversable[Promise[T]] = this.waitQueue + + /** + * + * Adds back an object that was in use to the list of poolable objects. + * + * @param item + * @param promise + */ + + private def addBack(item: T, promise: Promise[AsyncObjectPool[T]]) { + this.checkouts -= item + this.poolables += new PoolableHolder[T](item) + + if (!this.waitQueue.isEmpty) { + this.checkout(this.waitQueue.remove(0)) + } + + promise.success(this) + } + + /** + * + * Enqueues a promise to be fulfilled in the future when objects are sent back to the pool. If + * we have already reached the limit of enqueued objects, fail the promise. + * + * @param promise + */ + + private def enqueuePromise(promise: Promise[T]) { + if (this.waitQueue.size >= configuration.maxQueueSize) { + val exception = new PoolExhaustedException("There are no objects available and the waitQueue is full") + exception.fillInStackTrace() + promise.failure(exception) + } else { + this.waitQueue += promise + } + } + + private def checkout(promise: Promise[T]) { + this.mainPool.action { + if (this.isFull) { + this.enqueuePromise(promise) + } else { + this.createOrReturnItem(promise) + } + } + } + + /** + * + * Checks if there is a poolable object available and returns it to the promise. + * If there are no objects available, create a new one using the factory and return it. + * + * @param promise + */ + + private def createOrReturnItem(promise: Promise[T]) { + if (this.poolables.isEmpty) { + try { + val item = this.factory.create + this.checkouts += item + promise.success(item) + } catch { + case e: Exception => promise.failure(e) + } + } else { + val item = this.poolables.remove(0).item + this.checkouts += item + promise.success(item) + } + } + + override def finalize() { + this.close + } + + /** + * + * Validates pooled objects not in use to make sure they are all usable, great if + * you're holding onto network connections since you can "ping" the destination + * to keep the connection alive. + * + */ + + private def validateObjects { + val removals = new ArrayBuffer[PoolableHolder[T]]() + this.poolables.foreach { + poolable => + this.factory.validate(poolable.item) match { + case Success(item) => { + if (poolable.timeElapsed > configuration.maxIdle) { + log.debug("Connection was idle for {}, maxIdle is {}, removing it", poolable.timeElapsed, configuration.maxIdle) + removals += poolable + factory.destroy(poolable.item) + } + } + case Failure(e) => { + log.error("Failed to validate object", e) + removals += poolable + } + } + } + this.poolables --= removals + } + + private class PoolableHolder[T](val item: T) { + val time = System.currentTimeMillis() + + def timeElapsed = System.currentTimeMillis() - time + } + +} diff --git a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala index f74d8f26..6411fd5e 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -16,8 +16,13 @@ package com.github.mauricio.async.db.util -import java.util.concurrent.Executors +import java.util.concurrent.{ExecutorService, Executors} object ExecutorServiceUtils { val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) + + def newFixedPool( count : Int ) : ExecutorService = { + Executors.newFixedThreadPool( count, DaemonThreadsFactory ) + } + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/util/Worker.scala similarity index 51% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala rename to src/main/scala/com/github/mauricio/async/db/util/Worker.scala index b7237551..724579d4 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/Worker.scala @@ -14,24 +14,34 @@ * under the License. */ -package com.github.mauricio.postgresql.pool +package com.github.mauricio.async.db.util -import com.github.mauricio.async.db.postgresql.pool.ConnectionObjectFactory -import com.github.mauricio.async.db.{Configuration, Connection} -import org.apache.commons.pool.impl.StackObjectPool +object Worker { + val log = Log.get[Worker] +} + +class Worker { -class ConnectionPool(val configuration: Configuration) { + import Worker.log - private val factory = new ConnectionObjectFactory(configuration) - private val pool = new StackObjectPool(this.factory, 1) + private val mainPool = ExecutorServiceUtils.newFixedPool(1) + + def action(f: => Unit) { + this.mainPool.submit(new Runnable { + def run() { + try { + f + } catch { + case e : Exception => { + log.error("Failed to execute task %s".format(f), e) + } + } + } + }) + } - def doWithConnection[T](fn: Connection => T): T = { - val borrowed = this.pool.borrowObject() - try { - fn(borrowed) - } finally { - this.pool.returnObject(borrowed) - } + def shutdown { + this.mainPool.shutdown() } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala new file mode 100644 index 00000000..0afa0b9b --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala @@ -0,0 +1,161 @@ +/* + * 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.pool + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.postgresql.{DatabaseTestHelper, DatabaseConnectionHandler} +import scala.concurrent.duration._ +import scala.language.postfixOps +import scala.concurrent.{Future, Await} +import java.util.concurrent.TimeUnit +import java.nio.channels.ClosedChannelException + +class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { + + "pool" should { + + "give me a valid object when I ask for one" in { + + withPool { + pool => + val connection = get(pool) + val result = executeTest(connection) + pool.giveBack(connection) + result + } + } + + "enqueue an action if the pool is full" in { + + withPool({ + pool => + + val connection = get(pool) + val promises = List(pool.take, pool.take, pool.take) + + pool.availables.size === 0 + pool.inUse.size === 1 + pool.queued.size === 3 + + executeTest(connection) + + pool.giveBack(connection) + + promises.foreach { + promise => + val connection = Await.result(promise, Duration(5, TimeUnit.SECONDS)) + executeTest(connection) + pool.giveBack(connection) + } + + pool.availables.size === 1 + pool.inUse.size === 0 + pool.queued.size === 0 + + }, 1, 3) + + } + + "exhaust the pool" in { + + withPool({ + pool => + 1 to 2 foreach { + _ => pool.take + } + await(pool.take) must throwA[PoolExhaustedException] + }, 1, 1) + + } + + "it shoudl remove idle connections once the time limit has been reached" in { + + withPool( { + pool => + val connections = 1 to 5 map { + _ => + val connection = get(pool) + executeTest(connection) + connection + } + + connections.foreach( connection => await(pool.giveBack(connection)) ) + + pool.availables.size === 5 + + Thread.sleep(2000) + + pool.availables.isEmpty must beTrue + + }, validationInterval = 1000) + + } + + "it should validate returned connections before sending them back to the pool" in { + + withPool { + pool => + val connection = get(pool) + await(connection.disconnect) + + pool.inUse.size === 1 + + await(pool.giveBack(connection)) must throwA[ClosedChannelException] + + pool.availables.size === 0 + } + + } + + } + + def withPool[T]( + fn: (SingleThreadedAsyncObjectPool[DatabaseConnectionHandler]) => T, + maxObjects: Int = 5, + maxQueueSize: Int = 5, + validationInterval: Long = 3000 + ): T = { + + val poolConfiguration = new PoolConfiguration( + maxIdle = 1000, + maxObjects = maxObjects, + maxQueueSize = maxQueueSize, + validationInterval = validationInterval + ) + val factory = new ConnectionObjectFactory(this.defaultConfiguration) + val pool = new SingleThreadedAsyncObjectPool[DatabaseConnectionHandler](factory, poolConfiguration) + + try { + fn(pool) + } finally { + pool.close + } + + } + + def executeTest(connection: DatabaseConnectionHandler) = executeQuery(connection, "SELECT 0").rows.get(0, 0) === 0 + + def get(pool: SingleThreadedAsyncObjectPool[DatabaseConnectionHandler]): DatabaseConnectionHandler = { + val future = pool.take + Await.result(future, Duration(5, TimeUnit.SECONDS)) + } + + def await[T](future: Future[T]): T = { + Await.result(future, Duration(5, TimeUnit.SECONDS)) + } + +} From ffa8a8441420f9ab04336e3118549eabd96beec3 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 25 Apr 2013 00:10:50 -0300 Subject: [PATCH 024/357] Preparing documentation for first release --- .../mauricio/async/db/Configuration.scala | 13 +++ .../github/mauricio/async/db/Connection.scala | 87 ++++++++++++++- .../mauricio/async/db/QueryResult.scala | 10 ++ .../github/mauricio/async/db/ResultSet.scala | 35 ++++++ .../DatabaseConnectionHandler.scala | 2 +- .../async/db/postgresql/MutableQuery.scala | 2 + .../db/postgresql/pool/AsyncObjectPool.scala | 38 +++++++ .../db/postgresql/pool/ConnectionPool.scala | 104 ++++++++++++++++++ .../db/postgresql/pool/ObjectFactory.scala | 40 +++++++ .../pool/SingleThreadedAsyncObjectPool.scala | 2 + .../SingleThreadedAsyncObjectPoolSpec.scala | 12 +- 11 files changed, 337 insertions(+), 8 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala diff --git a/src/main/scala/com/github/mauricio/async/db/Configuration.scala b/src/main/scala/com/github/mauricio/async/db/Configuration.scala index cbc664f6..55826783 100644 --- a/src/main/scala/com/github/mauricio/async/db/Configuration.scala +++ b/src/main/scala/com/github/mauricio/async/db/Configuration.scala @@ -25,6 +25,19 @@ object Configuration { val Default = new Configuration("postgres") } +/** + * + * Contains the configuration to be able to connect to a database. + * + * @param username + * @param host + * @param port + * @param password + * @param database + * @param bossPool + * @param workerPool + * @param charset + */ case class Configuration(val username: String, val host: String = "localhost", diff --git a/src/main/scala/com/github/mauricio/async/db/Connection.scala b/src/main/scala/com/github/mauricio/async/db/Connection.scala index 97f14ad9..cab63b93 100644 --- a/src/main/scala/com/github/mauricio/async/db/Connection.scala +++ b/src/main/scala/com/github/mauricio/async/db/Connection.scala @@ -18,16 +18,101 @@ package com.github.mauricio.async.db import concurrent.Future +/** + * + * Base interface for all objects that behave like a connection. This trait will usually be implemented by the + * objects that connect to a database, either over the filesystem or sockets. {@link Connection} are not supposed + * to be thread-safe and clients should assume implementations **are not** thread safe and shouldn't try to perform + * more than one statement (either common or prepared) at the same time. They should wait for the previous statement + * to be executed to then be able to pick the next one. + * + * You can, for instance, compose on top of the futures returned by this class to execute many statements + * at the same time: + * + * {{{ + * val handler: Connection = ... + * val result: Future[QueryResult] = handler.connect + * .map(parameters => handler) + * .flatMap(connection => connection.sendQuery("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ")) + * .flatMap(query => handler.sendQuery("SELECT 0")) + * .flatMap(query => handler.sendQuery("COMMIT").map(value => query)) + * + * val queryResult: QueryResult = Await.result(result, Duration(5, SECONDS)) + * }}} + * + */ + trait Connection { + /** + * + * Disconnects this object. You should discard this object after calling this method. No more queries + * will be accepted. + * + * @return + */ + def disconnect: Future[Connection] + /** + * + * Connects this object to the database. Connection objects are not necessarily created with a connection to the + * database so you might have to call this method to be able to run queries against it. + * + * @return + */ + def connect: Future[Map[String, String]] + /** + * + * Checks whether we are still connected to the database. + * + * @return + */ + def isConnected: Boolean + /** + * + * Sends a statement to the database. The statement can be anything your database can execute. Not all statements + * will return a collection of rows, so check the returned object if there are rows available. + * + * @param query + * @return + */ + def sendQuery(query: String): Future[QueryResult] - def sendPreparedStatement(query: String, values: Array[Any] = Array.empty[Any]): Future[QueryResult] + /** + * + * Sends a prepared statement to the database. Prepared statements are special statements that are pre-compiled + * by the database to run faster, they also allow you to avoid SQL injection attacks by not having to concatenate + * strings from possibly unsafe sources (like users) and sending them directy to the database. + * + * When sending a prepared statement, you can insert ? signs in your statement and then provide values at the method + * call 'values' parameter, as in: + * + * {{{ + * connection.sendPreparedStatement( "SELECT * FROM users WHERE users.login = ?", Array( "john-doe" ) ) + * }}} + * + * As you are using the ? as the placeholder for the value, you don't have to perform any kind of manipulation + * to the value, just provide it as is and the database will clean it up. You must provide as many parameters + * as you have provided placeholders, so, if your query is as "INSERT INTO users (login,email) VALUES (?,?)" you + * have to provide an array with at least two values, as in: + * + * {{{ + * Array("john-doe", "doe@mail.com") + * }}} + * + * You can still use this method if your statement doesn't take any parameters, the default is an empty collection. + * + * @param query + * @param values + * @return + */ + + def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] } diff --git a/src/main/scala/com/github/mauricio/async/db/QueryResult.scala b/src/main/scala/com/github/mauricio/async/db/QueryResult.scala index 8de3d6f3..89c9dca9 100644 --- a/src/main/scala/com/github/mauricio/async/db/QueryResult.scala +++ b/src/main/scala/com/github/mauricio/async/db/QueryResult.scala @@ -15,6 +15,16 @@ package com.github.mauricio.async.db +/** + * + * This is the result of the execution of a statement, contains basic information as the number or rows + * affected by the statement and the rows returned if there were any. + * + * @param rowsAffected + * @param statusMessage + * @param rows + */ + class QueryResult(val rowsAffected: Int, val statusMessage: String, val rows: Option[ResultSet]) { override def toString: String = { diff --git a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala index f8c2ffcc..9e1ead4d 100644 --- a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala +++ b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala @@ -16,10 +16,45 @@ package com.github.mauricio.async.db +/** + * + * Represents the collection of rows that is returned from a statement inside a {@link QueryResult}. It's basically + * a collection of Array[Any]. Mutating fields in this array will not affect the database in any way + * + */ + trait ResultSet extends IndexedSeq[Array[Any]] { + /** + * + * The names of the columns returned by the statement. + * + * @return + */ + + def columnNames : IndexedSeq[String] + + /** + * + * Returns the value in the given column name at the given row. + * + * @param name + * @param row + * @return + */ + def apply(name: String, row: Int): Any + /** + * + * Returns the value in the given column index at the given row. The column order is usually defined by the + * "SELECT" clause of the statement executed. + * + * @param column + * @param row + * @return + */ + def apply(column: Int, row: Int): Any } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 91ee762a..07070d9f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -227,7 +227,7 @@ class DatabaseConnectionHandler this.queryPromise.get.future } - override def sendPreparedStatement(query: String, values: Array[Any] = Array.empty[Any]): Future[QueryResult] = { + override def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { this.readyForQuery = false this.queryPromise = Some(Promise[QueryResult]()) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala index 9264195c..1f843410 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala @@ -36,6 +36,8 @@ class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder (columnData.name, columnData.columnNumber - 1) }.toMap + override def columnNames : IndexedSeq[String] = this.columnTypes.map( data => data.name ) + override def length: Int = this.rows.length override def apply(idx: Int): Array[Any] = this.rows(idx) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala index 0d8af46c..1793681d 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala @@ -18,10 +18,48 @@ package com.github.mauricio.async.db.postgresql.pool import scala.concurrent.Future +/** + * + * Defines the common interface for async object pools. These are pools that do not block clients trying to acquire + * a resource from it. Different than the usual synchronous pool, you **must** return objects back to it manually + * since it's impossible for the pool to know when the object is ready to be given back. + * + * @tparam T + */ + trait AsyncObjectPool[T] { + /** + * + * Returns an object from the pool to the callee with the returned future. If the pool can not create or enqueue + * requests it will fill the returned [[scala.concurrent.Future]] with an + * [[com.github.mauricio.async.db.postgresql.pool.PoolExhaustedException]]. + * + * @return future that will eventually return a usable pool object. + */ + def take: Future[T] + + /** + * + * Returns an object taken from the pool back to it. This object will become available for another client to use. + * If the object is invalid or can not be reused for some reason the [[scala.concurrent.Future]] returned will contain + * the error that prevented this object of being added back to the pool. The object is then discarded from the pool. + * + * @param item + * @return + */ + def giveBack( item : T ) : Future[AsyncObjectPool[T]] + + /** + * + * Closes this pool and future calls to **take** will cause the [[scala.concurrent.Future]] to raise an + * [[com.github.mauricio.async.db.postgresql.pool.PoolAlreadyTerminatedException]]. + * + * @return + */ + def close : Future[AsyncObjectPool[T]] } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala new file mode 100644 index 00000000..2aa40d11 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala @@ -0,0 +1,104 @@ +/* + * 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.pool + +import com.github.mauricio.async.db.{QueryResult, Connection} +import scala.concurrent.Future + +/** + * + * Pool specialized in database connections that also simplifies connection handling by + * implementing the {@link Connection} trait and saving clients from having to implement + * the "give back" part of pool management. This lets you do your job without having to worry + * about managing and giving back connection objects to the pool. + * + * The downside of this is that you should not start transactions or any kind of long running process + * in this object as the object will be sent back to the pool right after executing a query. If you + * need to start transactions you will have to take an object from the pool, do it and then give it + * back manually. + * + * @param factory + * @param configuration + */ + +class ConnectionPool( + factory : ObjectFactory[Connection], + configuration : PoolConfiguration ) + extends SingleThreadedAsyncObjectPool[Connection]( factory, configuration ) + with Connection +{ + + /** + * + * Closes the pool, you should discard the object. + * + * @return + */ + + def disconnect: Future[Connection] = this.close.map( item => this ) + + /** + * + * Always returns an empty map. + * + * @return + */ + + def connect: Future[Map[String, String]] = Future { Map[String,String]() } + + def isConnected: Boolean = !this.isClosed + + /** + * + * Picks one connection and runs this query against it. The query should be stateless, it should not + * start transactions and should not leave anything to be cleaned up in the future. The behavior of this + * object is undefined if you start a transaction from this method. + * + * @param query + * @return + */ + + def sendQuery(query: String): Future[QueryResult] = { + this.take.flatMap { + connection => + connection.sendQuery(query).andThen { + case c => this.giveBack(connection) + } + } + } + + /** + * + * Picks one connection and runs this query against it. The query should be stateless, it should not + * start transactions and should not leave anything to be cleaned up in the future. The behavior of this + * object is undefined if you start a transaction from this method. + * + * @param query + * @param values + * @return + */ + + def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { + this.take.flatMap { + connection => + connection.sendPreparedStatement(query, values).andThen { + case c => this.giveBack(connection) + } + } + } + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala index 3beb8d1b..d7d3bb43 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala @@ -18,10 +18,50 @@ package com.github.mauricio.async.db.postgresql.pool import scala.util.Try +/** + * + * Definition for objects that can be used as a factory for [[com.github.mauricio.async.db.postgresql.pool.AsyncObjectPool]] + * objects. + * + * @tparam T + */ + trait ObjectFactory[T] { + /** + * + * Creates a valid object to be used in the pool. This method can block if necessary to make sure a correctly built + * is created. + * + * @return + */ + def create : T + + /** + * + * This method should "close" and release all resources acquired by the pooled object. This object will not be used + * anymore so any cleanup necessary to remove it from memory should be made in this method. Implementors should not + * raise an exception under any circumstances, the factory should log and clean up the exception itself. + * + * @param item + */ + def destroy( item : T ) + + /** + * + * Validates that an object can still be used for it's purpose. This method should test the object to make sure + * it's still valid for clients to use. If you have a database connection, test if you are still connected, if you're + * accessing a file system, make sure you can still see and change the file. + * + * If this object is not valid anymore, a [[scala.util.Failure]] should be returned, otherwise [[scala.util.Success]] + * should be the result of this call. + * + * @param item + * @return + */ + def validate( item : T ) : Try[T] } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala index 5df4f66f..1989b7d4 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala @@ -131,6 +131,8 @@ class SingleThreadedAsyncObjectPool[T]( def queued: Traversable[Promise[T]] = this.waitQueue + def isClosed : Boolean = this.closed + /** * * Adds back an object that was in use to the list of poolable objects. diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala index 0afa0b9b..b6a48ded 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.postgresql.pool -import org.specs2.mutable.Specification import com.github.mauricio.async.db.postgresql.{DatabaseTestHelper, DatabaseConnectionHandler} +import java.nio.channels.ClosedChannelException +import java.util.concurrent.TimeUnit +import org.specs2.mutable.Specification import scala.concurrent.duration._ -import scala.language.postfixOps import scala.concurrent.{Future, Await} -import java.util.concurrent.TimeUnit -import java.nio.channels.ClosedChannelException +import scala.language.postfixOps class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { @@ -84,7 +84,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH "it shoudl remove idle connections once the time limit has been reached" in { - withPool( { + withPool({ pool => val connections = 1 to 5 map { _ => @@ -93,7 +93,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH connection } - connections.foreach( connection => await(pool.giveBack(connection)) ) + connections.foreach(connection => await(pool.giveBack(connection))) pool.availables.size === 5 From a7e0dce802cf389e4930a9bc3c1f734090cb26ee Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 25 Apr 2013 16:35:11 -0300 Subject: [PATCH 025/357] More docs and fixing a bug where objects that failed tests were not removed from the pool --- .../DatabaseConnectionHandler.scala | 6 ++- .../pool/ConnectionObjectFactory.scala | 36 +++++++++++++++++- .../db/postgresql/pool/ConnectionPool.scala | 37 ++++++++++--------- .../db/postgresql/pool/ObjectFactory.scala | 23 +++++++++++- .../pool/PoolAlreadyTerminatedException.scala | 6 +++ .../postgresql/pool/PoolConfiguration.scala | 11 ++++++ .../pool/PoolExhaustedException.scala | 7 ++++ .../pool/SingleThreadedAsyncObjectPool.scala | 25 ++++++++----- .../async/db/util/ExecutorServiceUtils.scala | 2 + .../SingleThreadedAsyncObjectPoolSpec.scala | 4 +- 10 files changed, 125 insertions(+), 32 deletions(-) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 07070d9f..a0d8e732 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -145,7 +145,11 @@ class DatabaseConnectionHandler } override def isConnected: Boolean = { - this.connected + if ( this.currentChannel != null ) { + this.currentChannel.isConnected + } else { + this.connected + } } def parameterStatuses: scala.collection.immutable.Map[String, String] = this.parameterStatus.toMap diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index 62893228..a982afe8 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -23,11 +23,19 @@ import scala.language.postfixOps import scala.concurrent.Await import com.github.mauricio.async.db.util.Log import scala.util.{Success, Failure, Try} +import java.nio.channels.ClosedChannelException object ConnectionObjectFactory { val log = Log.get[ConnectionObjectFactory] } +/** + * + * Object responsible for creating new connection instances. + * + * @param configuration + */ + class ConnectionObjectFactory( val configuration : Configuration ) extends ObjectFactory[DatabaseConnectionHandler] { import ConnectionObjectFactory.log @@ -43,7 +51,33 @@ class ConnectionObjectFactory( val configuration : Configuration ) extends Objec item.disconnect } - def validate(item: DatabaseConnectionHandler): Try[DatabaseConnectionHandler] = { + /** + * + * Validates by checking if the connection is still connected to the database or not. + * + * @param item an object produced by this pool + * @return + */ + + def validate( item : DatabaseConnectionHandler ) : Try[DatabaseConnectionHandler] = { + Try { + if ( item.isConnected ) { + item + } else { + throw new ClosedChannelException() + } + } + } + + /** + * + * Tests whether we can still send a **SELECT 0** statement to the database. + * + * @param item an object produced by this pool + * @return + */ + + override def test(item: DatabaseConnectionHandler): Try[DatabaseConnectionHandler] = { val result : Try[DatabaseConnectionHandler] = Try({ Await.result( item.sendQuery("SELECT 0"), 5.seconds ) item diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala index 2aa40d11..c5939ac2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala @@ -17,12 +17,12 @@ package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.{QueryResult, Connection} -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} /** * * Pool specialized in database connections that also simplifies connection handling by - * implementing the {@link Connection} trait and saving clients from having to implement + * implementing the [[com.github.mauricio.async.db.Connection]] trait and saving clients from having to implement * the "give back" part of pool management. This lets you do your job without having to worry * about managing and giving back connection objects to the pool. * @@ -36,11 +36,12 @@ import scala.concurrent.Future */ class ConnectionPool( - factory : ObjectFactory[Connection], - configuration : PoolConfiguration ) - extends SingleThreadedAsyncObjectPool[Connection]( factory, configuration ) - with Connection -{ + factory: ObjectFactory[Connection], + configuration: PoolConfiguration, + executionContext: ExecutionContext + ) + extends SingleThreadedAsyncObjectPool[Connection](factory, configuration, executionContext) + with Connection { /** * @@ -49,7 +50,7 @@ class ConnectionPool( * @return */ - def disconnect: Future[Connection] = this.close.map( item => this ) + def disconnect: Future[Connection] = this.close.map(item => this)(executionContext) /** * @@ -58,7 +59,9 @@ class ConnectionPool( * @return */ - def connect: Future[Map[String, String]] = Future { Map[String,String]() } + def connect: Future[Map[String, String]] = Future( { + Map[String, String]() + })(executionContext) def isConnected: Boolean = !this.isClosed @@ -73,12 +76,12 @@ class ConnectionPool( */ def sendQuery(query: String): Future[QueryResult] = { - this.take.flatMap { + this.take.flatMap( { connection => - connection.sendQuery(query).andThen { + connection.sendQuery(query).andThen( { case c => this.giveBack(connection) - } - } + })(executionContext) + })(executionContext) } /** @@ -93,12 +96,12 @@ class ConnectionPool( */ def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { - this.take.flatMap { + this.take.flatMap( { connection => - connection.sendPreparedStatement(query, values).andThen { + connection.sendPreparedStatement(query, values).andThen( { case c => this.giveBack(connection) - } - } + })(executionContext) + })(executionContext) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala index d7d3bb43..1b96e25a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala @@ -23,7 +23,7 @@ import scala.util.Try * Definition for objects that can be used as a factory for [[com.github.mauricio.async.db.postgresql.pool.AsyncObjectPool]] * objects. * - * @tparam T + * @tparam T the kind of object this factory produces. */ trait ObjectFactory[T] { @@ -55,13 +55,32 @@ trait ObjectFactory[T] { * it's still valid for clients to use. If you have a database connection, test if you are still connected, if you're * accessing a file system, make sure you can still see and change the file. * + * You decide how fast this method should return and what it will test, you should usually do something that's fast + * enough not to slow down the pool usage, since this call will be made whenever an object returns to the pool. + * * If this object is not valid anymore, a [[scala.util.Failure]] should be returned, otherwise [[scala.util.Success]] * should be the result of this call. * - * @param item + * @param item an object produced by this pool * @return */ def validate( item : T ) : Try[T] + /** + * + * Does a full test on the given object making sure it's still valid. Different than validate, that's called whenever + * an object is given back to the pool and should usually be fast, this method will be called when objects are + * idle to make sure they don't "timeout" or become stale in anyway. + * + * For convenience, this method defaults to call **validate** but you can implement it in a different way if you + * would like to. + * + * @param item an object produced by this pool + * @return + */ + + def test( item : T ) : Try[T] = validate(item) + + } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala index dbe78e43..af247a57 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala @@ -17,4 +17,10 @@ package com.github.mauricio.async.db.postgresql.pool +/** + * + * Thrown when the pool has already been closed. + * + */ + class PoolAlreadyTerminatedException extends IllegalStateException( "This pool has already been terminated" ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala index 3b1b396f..1cfa2db2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala @@ -16,6 +16,17 @@ package com.github.mauricio.async.db.postgresql.pool +/** + * + * Defines specific pieces of a pool's behavior. + * + * @param maxObjects how many objects this pool will hold + * @param maxIdle how long are objects going to be kept as idle (not in use by clients of the pool) + * @param maxQueueSize when there are no more objects, the pool can queue up requests to serve later then there + * are objects available, this is the maximum number of enqueued requests + * @param validationInterval pools will use this value as the timer period to validate idle objects. + */ + case class PoolConfiguration( maxObjects: Int, maxIdle: Long, diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala index 9fb82c58..366058aa 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala @@ -16,4 +16,11 @@ package com.github.mauricio.async.db.postgresql.pool +/** + * + * Raised when a pool has reached it's limit of available objects. + * + * @param message + */ + class PoolExhaustedException( message : String ) extends IllegalStateException( message ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala index 1989b7d4..a149e614 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala @@ -20,7 +20,7 @@ import com.github.mauricio.async.db.util.{Log, Worker} import java.util.concurrent.atomic.AtomicLong import java.util.{TimerTask, Timer} import scala.collection.mutable.ArrayBuffer -import scala.concurrent.{Promise, Future} +import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.{Failure, Success} object SingleThreadedAsyncObjectPool { @@ -30,17 +30,22 @@ object SingleThreadedAsyncObjectPool { /** * - * Implements an {@link AsyncObjectPool} using a single thread from a fixed executor service with - * a single thread as an event loop to cause all calls to be sequential. + * Implements an [[com.github.mauricio.async.db.postgresql.pool.AsyncObjectPool]] using a single thread from a + * fixed executor service with a single thread as an event loop to cause all calls to be sequential. + * + * Once you are done with this object remember to call it's close method to clean up the thread pool and + * it's objects as this might prevent your application from ending. * * @param factory * @param configuration - * @tparam T + * @tparam T type of the object this pool holds */ class SingleThreadedAsyncObjectPool[T]( factory: ObjectFactory[T], - configuration: PoolConfiguration) extends AsyncObjectPool[T] { + configuration: PoolConfiguration, + executionContext: ExecutionContext + ) extends AsyncObjectPool[T] { import SingleThreadedAsyncObjectPool.{Counter, log} @@ -52,7 +57,7 @@ class SingleThreadedAsyncObjectPool[T]( timer.scheduleAtFixedRate(new TimerTask { def run() { mainPool.action { - validateObjects + testObjects } } }, configuration.validationInterval, configuration.validationInterval) @@ -89,6 +94,7 @@ class SingleThreadedAsyncObjectPool[T]( def giveBack(item: T): Future[AsyncObjectPool[T]] = { val promise = Promise[AsyncObjectPool[T]]() this.mainPool.action { + this.checkouts -= item this.factory.validate(item) match { case Success(item) => { this.addBack(item, promise) @@ -131,7 +137,7 @@ class SingleThreadedAsyncObjectPool[T]( def queued: Traversable[Promise[T]] = this.waitQueue - def isClosed : Boolean = this.closed + def isClosed: Boolean = this.closed /** * @@ -142,7 +148,6 @@ class SingleThreadedAsyncObjectPool[T]( */ private def addBack(item: T, promise: Promise[AsyncObjectPool[T]]) { - this.checkouts -= item this.poolables += new PoolableHolder[T](item) if (!this.waitQueue.isEmpty) { @@ -216,11 +221,11 @@ class SingleThreadedAsyncObjectPool[T]( * */ - private def validateObjects { + private def testObjects { val removals = new ArrayBuffer[PoolableHolder[T]]() this.poolables.foreach { poolable => - this.factory.validate(poolable.item) match { + this.factory.test(poolable.item) match { case Success(item) => { if (poolable.timeElapsed > configuration.maxIdle) { log.debug("Connection was idle for {}, maxIdle is {}, removing it", poolable.timeElapsed, configuration.maxIdle) diff --git a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala index 6411fd5e..7da4b1e0 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -17,9 +17,11 @@ package com.github.mauricio.async.db.util import java.util.concurrent.{ExecutorService, Executors} +import scala.concurrent.ExecutionContext object ExecutorServiceUtils { val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) + val CachedExecutionContext = ExecutionContext.fromExecutor( CachedThreadPool ) def newFixedPool( count : Int ) : ExecutorService = { Executors.newFixedThreadPool( count, DaemonThreadsFactory ) diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala index b6a48ded..48684b60 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala @@ -23,6 +23,7 @@ import org.specs2.mutable.Specification import scala.concurrent.duration._ import scala.concurrent.{Future, Await} import scala.language.postfixOps +import com.github.mauricio.async.db.util.ExecutorServiceUtils class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { @@ -117,6 +118,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH await(pool.giveBack(connection)) must throwA[ClosedChannelException] pool.availables.size === 0 + pool.inUse.size === 0 } } @@ -137,7 +139,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH validationInterval = validationInterval ) val factory = new ConnectionObjectFactory(this.defaultConfiguration) - val pool = new SingleThreadedAsyncObjectPool[DatabaseConnectionHandler](factory, poolConfiguration) + val pool = new SingleThreadedAsyncObjectPool[DatabaseConnectionHandler](factory, poolConfiguration, ExecutorServiceUtils.CachedExecutionContext) try { fn(pool) From 5f28e69d93e9f7c3af63f43a30d5001f884d721e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 26 Apr 2013 18:17:54 -0300 Subject: [PATCH 026/357] Parsing URLs --- .../async/db/postgresql/util/ParseURL.java | 154 ++++++++++++++++++ .../async/db/postgresql/MutableQuery.scala | 14 +- .../mauricio/async/db/util/URLParser.scala | 52 ++++++ .../DatabaseConnectionHandlerSpec.scala | 12 ++ .../db/postgresql/util/URLParserSpec.scala | 40 +++++ 5 files changed, 260 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java create mode 100644 src/main/scala/com/github/mauricio/async/db/util/URLParser.scala create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala diff --git a/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java b/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java new file mode 100644 index 00000000..c3f1d6dd --- /dev/null +++ b/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java @@ -0,0 +1,154 @@ +package com.github.mauricio.async.db.postgresql.util; + +import com.github.mauricio.async.db.Configuration; + +import java.sql.SQLException; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * + * Copied over from the JDBC PostgreSQL driver. + * + */ + +public class ParseURL { + + public static final String PGPORT = "port"; + public static final String PGDBNAME = "database"; + public static final String PGHOST = "host"; + public static final String PROTOCOL = "Protocol"; + static private String[] protocols = { "jdbc", "postgresql" }; + + public static Properties parseURL(String url) throws SQLException + { + int state = -1; + Properties urlProps = new Properties(); + urlProps.setProperty(PGHOST, Configuration.Default().host()); + urlProps.setProperty(PGPORT, Integer.toString(Configuration.Default().port())); + + String l_urlServer = url; + String l_urlArgs = ""; + + int l_qPos = url.indexOf('?'); + if (l_qPos != -1) + { + l_urlServer = url.substring(0, l_qPos); + l_urlArgs = url.substring(l_qPos + 1); + } + + // look for an IPv6 address that is enclosed by [] + // the upcoming parsing that uses colons as identifiers can't handle + // the colons in an IPv6 address. + int ipv6start = l_urlServer.indexOf("["); + int ipv6end = l_urlServer.indexOf("]"); + String ipv6address = null; + if (ipv6start != -1 && ipv6end > ipv6start) + { + ipv6address = l_urlServer.substring(ipv6start + 1, ipv6end); + l_urlServer = l_urlServer.substring(0, ipv6start) + "ipv6host" + l_urlServer.substring(ipv6end + 1); + } + + //parse the server part of the url + StringTokenizer st = new StringTokenizer(l_urlServer, ":/", true); + int count; + for (count = 0; (st.hasMoreTokens()); count++) + { + String token = st.nextToken(); + + // PM Aug 2 1997 - Modified to allow multiple backends + if (count <= 3) + { + if ((count % 2) == 1 && token.equals(":")) + ; + else if ((count % 2) == 0) + { + boolean found = (count == 0) ? true : false; + for (int tmp = 0;tmp < protocols.length;tmp++) + { + if (token.equals(protocols[tmp])) + { + // PM June 29 1997 Added this property to enable the driver + // to handle multiple backend protocols. + if (count == 2 && tmp > 0) + { + urlProps.setProperty(PROTOCOL, token); + found = true; + } + } + } + + if (found == false) + return null; + } + else + return null; + } + else if (count > 3) + { + if (count == 4 && token.equals("/")) + state = 0; + else if (count == 4) + { + urlProps.setProperty(PGDBNAME, token); + state = -2; + } + else if (count == 5 && state == 0 && token.equals("/")) + state = 1; + else if (count == 5 && state == 0) + return null; + else if (count == 6 && state == 1) + urlProps.setProperty(PGHOST, token); + else if (count == 7 && token.equals(":")) + state = 2; + else if (count == 8 && state == 2) + { + try + { + Integer portNumber = Integer.decode(token); + urlProps.setProperty(PGPORT, portNumber.toString()); + } + catch (Exception e) + { + return null; + } + } + else if ((count == 7 || count == 9) && + (state == 1 || state == 2) && token.equals("/")) + state = -1; + else if (state == -1) + { + urlProps.setProperty(PGDBNAME, token); + state = -2; + } + } + } + if (count <= 1) + { + return null; + } + + // if we extracted an IPv6 address out earlier put it back + if (ipv6address != null) + urlProps.setProperty(PGHOST, ipv6address); + + //parse the args part of the url + StringTokenizer qst = new StringTokenizer(l_urlArgs, "&"); + for (count = 0; (qst.hasMoreTokens()); count++) + { + String token = qst.nextToken(); + int l_pos = token.indexOf('='); + if (l_pos == -1) + { + urlProps.setProperty(token, ""); + } + else + { + urlProps.setProperty(token.substring(0, l_pos), token.substring(l_pos + 1)); + } + } + + return urlProps; + } + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala index 1f843410..926d6aaa 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala @@ -42,8 +42,6 @@ class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder override def apply(idx: Int): Array[Any] = this.rows(idx) - def update(idx: Int, elem: Array[Any]) = this.rows(idx) = elem - def addRawRow(row: Array[ChannelBuffer]) { val realRow = new Array[Any](row.length) @@ -62,16 +60,8 @@ class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder this.rows += realRow } - def getValue(columnNumber: Int, rowNumber: Int): Any = { - this.rows(rowNumber)(columnNumber) - } - - def getValue(columnName: String, rowNumber: Int): Any = { - this.rows(rowNumber)(this.columnMapping(columnName)) - } - - def apply(name: String, row: Int): Any = this.getValue(name, row) + def apply(name: String, row: Int): Any = this.rows(row)(this.columnMapping(name)) - def apply(column: Int, row: Int): Any = this.getValue(column, row) + def apply(column: Int, row: Int): Any = this.rows(row)(column) } diff --git a/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala b/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala new file mode 100644 index 00000000..c6990837 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala @@ -0,0 +1,52 @@ +/* + * 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.util + +import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.postgresql.util.ParseURL +import java.nio.charset.Charset +import java.util.concurrent.ExecutorService +import scala.collection.JavaConversions._ + +object URLParser { + + import Configuration.Default + + def parse(url: String, + bossPool: ExecutorService = Default.bossPool, + workerPool: ExecutorService = Default.workerPool, + charset: Charset = Default.charset + ): Configuration = { + + val properties = ParseURL.parseURL(url).toMap + + val port = properties(ParseURL.PGPORT).toInt + + new Configuration( + username = properties.get("username").getOrElse(Default.username), + password = properties.get("password"), + database = properties.get(ParseURL.PGDBNAME), + host = properties(ParseURL.PGHOST), + port = port, + charset = charset, + workerPool = workerPool, + bossPool = bossPool + ) + + } + +} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index e4c6ead0..979d063a 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -84,6 +84,7 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe val preparedStatementInsert = " insert into prepared_statement_test (name) values ('John Doe')" val preparedStatementInsert2 = " insert into prepared_statement_test (name) values ('Mary Jane')" val preparedStatementInsert3 = " insert into prepared_statement_test (name) values ('Peter Parker')" + val preparedStatementInsertReturning = " insert into prepared_statement_test (name) values ('John Doe') returning id" val preparedStatementSelect = "select * from prepared_statement_test" "handler" should { @@ -286,6 +287,17 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } + "use RETURNING in an insert statement" in { + + withHandler { + connection => + executeDdl( connection, this.preparedStatementCreate ) + val result = executeQuery( connection, this.preparedStatementInsertReturning ) + result.rows.get("id", 0) === 1 + } + + } + } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala new file mode 100644 index 00000000..6f6f2c87 --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala @@ -0,0 +1,40 @@ +/* + * 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 org.specs2.mutable.Specification +import com.github.mauricio.async.db.util.URLParser + +class URLParserSpec extends Specification { + + "parser" should { + + "create a connection with the available fields" in { + val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database?username=john&password=doe" + + val configuration = URLParser.parse(connectionUri) + + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "128.567.54.90" + configuration.port === 9987 + } + + } + +} From fba795ac6e53e6839d85a29cff9968ee61f097d7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 26 Apr 2013 23:26:50 -0300 Subject: [PATCH 027/357] Adding default params --- build.sbt | 44 +++++++++++++++++-- .../db/postgresql/pool/ConnectionPool.scala | 3 +- .../postgresql/pool/PoolConfiguration.scala | 4 ++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index dac08647..75ea5089 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "postgresql-async" -version := "0.0.1" +version := "0.0.1-SNAPSHOT" organization := "com.github.mauricio" @@ -12,7 +12,45 @@ libraryDependencies ++= Seq( "io.netty" % "netty" % "3.6.5.Final", "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", - "org.specs2" % "specs2_2.10" % "1.14" % "test" + "org.specs2" %% "specs2" % "1.14" % "test" ) -scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature") \ No newline at end of file +scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature") + +credentials += Credentials(Path.userHome / ".ivy2" / ".credentials") + +publishMavenStyle := true + +publishArtifact in Test := false + +pomIncludeRepository := { _ => false } + +publishTo <<= version { v: String => + val nexus = "https://oss.sonatype.org/" + if (v.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} + +pomExtra := ( + http://your.project.url + + + APACHE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + git@github.com:mauricio/postgresql-netty.git + scm:git:git@github.com:mauricio/postgresql-netty.git + + + + mauricio-linhares + Maurício Linhares + https://github.com/mauricio + + +) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala index c5939ac2..18d0dee1 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.{QueryResult, Connection} import scala.concurrent.{ExecutionContext, Future} +import com.github.mauricio.async.db.util.ExecutorServiceUtils /** * @@ -38,7 +39,7 @@ import scala.concurrent.{ExecutionContext, Future} class ConnectionPool( factory: ObjectFactory[Connection], configuration: PoolConfiguration, - executionContext: ExecutionContext + executionContext: ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends SingleThreadedAsyncObjectPool[Connection](factory, configuration, executionContext) with Connection { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala index 1cfa2db2..4bb20d4f 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala @@ -16,6 +16,10 @@ package com.github.mauricio.async.db.postgresql.pool +object PoolConfiguration { + val Default = new PoolConfiguration(10, 4, 10) +} + /** * * Defines specific pieces of a pool's behavior. From 22bbb70b7355f7a53b9c59a75d0d2fe2d109f667 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 26 Apr 2013 23:34:18 -0300 Subject: [PATCH 028/357] ConnectionPool shold be co-variant --- .../mauricio/async/db/postgresql/pool/ConnectionPool.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala index 18d0dee1..391d430a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala @@ -36,12 +36,12 @@ import com.github.mauricio.async.db.util.ExecutorServiceUtils * @param configuration */ -class ConnectionPool( - factory: ObjectFactory[Connection], +class ConnectionPool[T <: Connection]( + factory: ObjectFactory[T], configuration: PoolConfiguration, executionContext: ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) - extends SingleThreadedAsyncObjectPool[Connection](factory, configuration, executionContext) + extends SingleThreadedAsyncObjectPool[T](factory, configuration, executionContext) with Connection { /** From e0e505241d66add91904a8f8872f27f0435f0d3e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 27 Apr 2013 16:34:03 -0300 Subject: [PATCH 029/357] marking thread pools as implicits --- .../github/mauricio/async/db/util/ExecutorServiceUtils.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala index 7da4b1e0..44ba2c83 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -20,8 +20,8 @@ import java.util.concurrent.{ExecutorService, Executors} import scala.concurrent.ExecutionContext object ExecutorServiceUtils { - val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) - val CachedExecutionContext = ExecutionContext.fromExecutor( CachedThreadPool ) + implicit val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) + implicit val CachedExecutionContext = ExecutionContext.fromExecutor( CachedThreadPool ) def newFixedPool( count : Int ) : ExecutorService = { Executors.newFixedThreadPool( count, DaemonThreadsFactory ) From 84482348f454bc5340575c56e59dceb76275d860 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 28 Apr 2013 10:45:36 -0300 Subject: [PATCH 030/357] Wrapping the row array in a RowData object to simplify access --- .../github/mauricio/async/db/ResultSet.scala | 2 +- .../github/mauricio/async/db/RowData.scala | 56 +++++++++++++++++ .../async/db/general/ArrayRowData.scala | 61 +++++++++++++++++++ .../MutableQuery.scala | 10 +-- .../DatabaseConnectionHandler.scala | 1 + 5 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/async/db/RowData.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala rename src/main/scala/com/github/mauricio/async/db/{postgresql => general}/MutableQuery.scala (86%) diff --git a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala index 9e1ead4d..f7bb635d 100644 --- a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala +++ b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala @@ -23,7 +23,7 @@ package com.github.mauricio.async.db * */ -trait ResultSet extends IndexedSeq[Array[Any]] { +trait ResultSet extends IndexedSeq[RowData] { /** * diff --git a/src/main/scala/com/github/mauricio/async/db/RowData.scala b/src/main/scala/com/github/mauricio/async/db/RowData.scala new file mode 100644 index 00000000..e3a3d1f4 --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/RowData.scala @@ -0,0 +1,56 @@ +/* + * 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 + +/** + * + * Represents a row from a database, allows clients to access rows by column number or column name. + * + */ + +trait RowData { + + /** + * + * Returns a column value by it's position in the originating query. + * + * @param columnNumber + * @return + */ + + def apply( columnNumber : Int ) : Any + + /** + * + * Returns a column value by it's name in the originating query. + * + * @param columnName + * @return + */ + + def apply( columnName : String ) : Any + + /** + * + * Number of this row in the query results. Counts start at 0. + * + * @return + */ + + def rowNumber : Int + +} diff --git a/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala b/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala new file mode 100644 index 00000000..1705de8c --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala @@ -0,0 +1,61 @@ +/* + * 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.general + +import com.github.mauricio.async.db.RowData + +class ArrayRowData( columnCount : Int, row : Int, mapping : Map[String, Int] ) extends RowData { + + private val columns = new Array[Any](columnCount) + + /** + * + * Returns a column value by it's position in the originating query. + * + * @param columnNumber + * @return + */ + def apply(columnNumber: Int): Any = columns(columnNumber) + + /** + * + * Returns a column value by it's name in the originating query. + * + * @param columnName + * @return + */ + def apply(columnName: String): Any = columns( mapping(columnName) ) + + /** + * + * Number of this row in the query results. Counts start at 0. + * + * @return + */ + def rowNumber: Int = row + + /** + * + * Sets a value to a column in this collection. + * + * @param i + * @param x + */ + + def update(i: Int, x: Any) = columns(i) = x + +} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/general/MutableQuery.scala similarity index 86% rename from src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala rename to src/main/scala/com/github/mauricio/async/db/general/MutableQuery.scala index 926d6aaa..2d5a8596 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/general/MutableQuery.scala @@ -14,10 +14,10 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql +package com.github.mauricio.async.db.general import collection.mutable.ArrayBuffer -import com.github.mauricio.async.db.ResultSet +import com.github.mauricio.async.db.{RowData, ResultSet} import com.github.mauricio.async.db.postgresql.column.ColumnDecoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData import com.github.mauricio.async.db.util.Log @@ -30,7 +30,7 @@ object MutableQuery { class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder : ColumnDecoderRegistry) extends ResultSet { - private val rows = new ArrayBuffer[Array[Any]]() + private val rows = new ArrayBuffer[RowData]() private val columnMapping: Map[String, Int] = this.columnTypes.map { columnData => (columnData.name, columnData.columnNumber - 1) @@ -40,11 +40,11 @@ class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder override def length: Int = this.rows.length - override def apply(idx: Int): Array[Any] = this.rows(idx) + override def apply(idx: Int): RowData = this.rows(idx) def addRawRow(row: Array[ChannelBuffer]) { - val realRow = new Array[Any](row.length) + val realRow = new ArrayRowData(columnMapping.size, this.rows.size, this.columnMapping) 0.until(row.length).foreach { index => diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index a0d8e732..86ecb753 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -32,6 +32,7 @@ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} +import com.github.mauricio.async.db.general.MutableQuery object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] From 7af1f5bc7ee9e4e125db1b533d00b5d2fa9153f1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 28 Apr 2013 17:48:45 -0300 Subject: [PATCH 031/357] Raise a better exception if the query string is empty --- README.markdown | 5 +- build.sbt | 7 +- .../DatabaseConnectionHandler.scala | 14 +++- .../QueryMustNotBeNullOrEmptyException.scala | 26 ++++++++ .../messages/backend/EmptyQueryString.scala | 19 ++++++ .../postgresql/messages/backend/Message.scala | 2 +- .../parsers/MessageParsersRegistry.scala | 1 + .../db/postgresql/pool/ConnectionPool.scala | 2 +- .../pool/SingleThreadedAsyncObjectPool.scala | 6 +- .../mauricio/async/db/util/Worker.scala | 18 ++++-- .../DatabaseConnectionHandlerSpec.scala | 36 ++++++++++- .../db/postgresql/DatabaseTestHelper.scala | 19 ++++-- .../postgresql/pool/ConnectionPoolSpec.scala | 64 +++++++++++++++++++ .../SingleThreadedAsyncObjectPoolSpec.scala | 9 +-- 14 files changed, 200 insertions(+), 28 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala create mode 100644 src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala diff --git a/README.markdown b/README.markdown index e8714aa1..a9d82370 100644 --- a/README.markdown +++ b/README.markdown @@ -13,7 +13,7 @@ to PostgreSQL. - receive database notices - execute direct queries (without portals/prepared statements) - portals/prepared statements -- parses all basic PostgreSQL types, other types are parsed as string +- parses most of the basic PostgreSQL types, other types are parsed as string - date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects - all work is done using the new `scala.concurrent.Future` and `scala.concurrent.Promise` objects @@ -21,8 +21,9 @@ to PostgreSQL. - more authentication mechanisms - benchmarks -- more tests +- more tests (run the `jacoco:cover` sbt task and see where you can improve) - timeout handler for initial handshare and queries +- implement byte array support ## What are the design goals? diff --git a/build.sbt b/build.sbt index 75ea5089..56e8b3d0 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,6 @@ +import de.johoop.jacoco4sbt._ +import JacocoPlugin._ + name := "postgresql-async" version := "0.0.1-SNAPSHOT" @@ -53,4 +56,6 @@ pomExtra := ( https://github.com/mauricio -) \ No newline at end of file +) + +seq(jacoco.settings : _*) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 86ecb753..7893233c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.postgresql.exceptions.{MissingCredentialInformationException, NotConnectedException, GenericDatabaseException} +import com.github.mauricio.async.db.postgresql.exceptions.{QueryMustNotBeNullOrEmptyException, MissingCredentialInformationException, NotConnectedException, GenericDatabaseException} import com.github.mauricio.async.db.util.{Log, ExecutorServiceUtils} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import com.github.mauricio.postgresql.MessageEncoder @@ -192,6 +192,10 @@ class DatabaseConnectionHandler case Message.Error => { this.onError(m.asInstanceOf[ErrorMessage]) } + case Message.EmptyQueryString => { + val exception = new QueryMustNotBeNullOrEmptyException(null) + this.setErrorOnFutures(exception) + } case Message.NoData => { log.debug("Statement response does not contain any data") } @@ -226,6 +230,7 @@ class DatabaseConnectionHandler } override def sendQuery(query: String): Future[QueryResult] = { + validateQuery(query) this.readyForQuery = false this.queryPromise = Option(Promise[QueryResult]()) this.currentChannel.write(new QueryMessage(query)) @@ -233,6 +238,7 @@ class DatabaseConnectionHandler } override def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { + validateQuery(query) this.readyForQuery = false this.queryPromise = Some(Promise[QueryResult]()) @@ -391,4 +397,10 @@ class DatabaseConnectionHandler } } + private def validateQuery( query : String ) { + if ( query == null || query.isEmpty ) { + throw new QueryMustNotBeNullOrEmptyException(query) + } + } + } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala new file mode 100644 index 00000000..e268fc2a --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala @@ -0,0 +1,26 @@ +/* + * 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.exceptions + +/** + * + * Raised if the query string is null or empty. + * + * @param query the problematic query + */ +class QueryMustNotBeNullOrEmptyException(query : String) + extends DatabaseException("Query must not be null or empty, original query is [%s]".format(query)) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala new file mode 100644 index 00000000..09e7498e --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala @@ -0,0 +1,19 @@ +/* + * 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.messages.backend + +object EmptyQueryString extends Message( Message.EmptyQueryString ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala index 150f911a..f4473075 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala @@ -29,7 +29,7 @@ object Message { val Describe = 'D' val Error = 'E' val Execute = 'E' - val EmptyQuery = 'I' + val EmptyQueryString = 'I' val NoData = 'n' val Notice = 'N' val Notification = 'A' diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala index 2ee5cb95..2797a709 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -31,6 +31,7 @@ class MessageParsersRegistry(charset: Charset) { Message.CommandComplete -> new CommandCompleteParser(charset), Message.DataRow -> DataRowParser, Message.Error -> new ErrorParser(charset), + Message.EmptyQueryString -> new ReturningMessageParser(EmptyQueryString), Message.NoData -> new ReturningMessageParser(NoData), Message.Notice -> new NoticeParser(charset), Message.ParameterStatus -> new ParameterStatusParser(charset), diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala index 391d430a..5bfa1f29 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala @@ -41,7 +41,7 @@ class ConnectionPool[T <: Connection]( configuration: PoolConfiguration, executionContext: ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) - extends SingleThreadedAsyncObjectPool[T](factory, configuration, executionContext) + extends SingleThreadedAsyncObjectPool[T](factory, configuration) with Connection { /** diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala index a149e614..4ff068d7 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala @@ -43,13 +43,12 @@ object SingleThreadedAsyncObjectPool { class SingleThreadedAsyncObjectPool[T]( factory: ObjectFactory[T], - configuration: PoolConfiguration, - executionContext: ExecutionContext + configuration: PoolConfiguration ) extends AsyncObjectPool[T] { import SingleThreadedAsyncObjectPool.{Counter, log} - private val mainPool = new Worker() + private val mainPool = Worker() private val poolables = new ArrayBuffer[PoolableHolder[T]](configuration.maxObjects) private val checkouts = new ArrayBuffer[T](configuration.maxObjects) private val waitQueue = new ArrayBuffer[Promise[T]](configuration.maxQueueSize) @@ -104,6 +103,7 @@ class SingleThreadedAsyncObjectPool[T]( } } } + promise.future } diff --git a/src/main/scala/com/github/mauricio/async/db/util/Worker.scala b/src/main/scala/com/github/mauricio/async/db/util/Worker.scala index 724579d4..70837b08 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/Worker.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/Worker.scala @@ -16,18 +16,26 @@ package com.github.mauricio.async.db.util +import java.util.concurrent.ExecutorService +import scala.concurrent.{ExecutionContextExecutorService, ExecutionContext} + object Worker { val log = Log.get[Worker] + + def apply() : Worker = apply(ExecutorServiceUtils.newFixedPool(1)) + + def apply( executorService : ExecutorService ) : Worker = { + new Worker(ExecutionContext.fromExecutorService( executorService )) + } + } -class Worker { +class Worker( val executionContext : ExecutionContextExecutorService ) { import Worker.log - private val mainPool = ExecutorServiceUtils.newFixedPool(1) - def action(f: => Unit) { - this.mainPool.submit(new Runnable { + this.executionContext.execute(new Runnable { def run() { try { f @@ -41,7 +49,7 @@ class Worker { } def shutdown { - this.mainPool.shutdown() + this.executionContext.shutdown() } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 979d063a..3a64ad68 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.postgresql.column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} -import com.github.mauricio.async.db.postgresql.exceptions.{GenericDatabaseException, UnsupportedAuthenticationMethodException} +import com.github.mauricio.async.db.postgresql.exceptions.{QueryMustNotBeNullOrEmptyException, GenericDatabaseException, UnsupportedAuthenticationMethodException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} @@ -298,6 +298,40 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } + "execute a prepared statement with limit" in { + + withHandler { + handler => + executeDdl(handler, this.preparedStatementCreate) + executeDdl(handler, this.preparedStatementInsert, 1) + executeDdl(handler, this.preparedStatementInsert2, 1) + executeDdl(handler, this.preparedStatementInsert3, 1) + + val result = executePreparedStatement(handler, "select * from prepared_statement_test LIMIT 1").rows.get + + result("name", 0) === "John Doe" + } + + } + + "execute an empty query" in { + + withHandler { + handler => + executeQuery(handler, "").rows === None + } must throwA[QueryMustNotBeNullOrEmptyException] + + } + + "execute an whitespace query" in { + + withHandler { + handler => + executeQuery(handler, " ").rows === None + } must throwA[QueryMustNotBeNullOrEmptyException] + + } + } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index 07f75368..14993cb3 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -16,9 +16,11 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.Configuration -import concurrent.Await -import concurrent.duration._ +import com.github.mauricio.async.db.{Connection, Configuration} +import java.util.concurrent.TimeUnit +import scala.Some +import scala.concurrent.duration._ +import scala.concurrent.{Future, Await} trait DatabaseTestHelper { @@ -48,7 +50,7 @@ trait DatabaseTestHelper { } - def executeDdl(handler: DatabaseConnectionHandler, data: String, count: Int = 0) = { + def executeDdl(handler: Connection, data: String, count: Int = 0) = { val rows = Await.result(handler.sendQuery(data), Duration(5, SECONDS)).rowsAffected if (rows != count) { @@ -57,15 +59,20 @@ trait DatabaseTestHelper { } - def executeQuery(handler: DatabaseConnectionHandler, data: String) = { + def executeQuery(handler: Connection, data: String) = { Await.result(handler.sendQuery(data), Duration(5, SECONDS)) } def executePreparedStatement( - handler: DatabaseConnectionHandler, + handler: Connection, statement: String, values: Array[Any] = Array.empty[Any]) = { Await.result(handler.sendPreparedStatement(statement, values), Duration(5, SECONDS)) } + def await[T](future: Future[T]): T = { + Await.result(future, Duration(5, TimeUnit.SECONDS)) + } + + } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala new file mode 100644 index 00000000..80a61074 --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala @@ -0,0 +1,64 @@ +/* + * 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.pool + +import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} +import org.specs2.mutable.Specification + +class ConnectionPoolSpec extends Specification with DatabaseTestHelper { + + "pool" should { + + "give you a connection when sending statements" in { + + withPool{ + pool => + executeQuery(pool, "SELECT 8").rows.get(0,0) === 8 + pool.availables.size === 1 + } + + } + + "give you a connection for prepared statements" in { + withPool{ + pool => + executePreparedStatement(pool, "SELECT 8").rows.get(0,0) === 8 + pool.availables.size === 1 + } + } + + "return an empty map when connect is called" in { + withPool { + pool => + await(pool.connect) === Map[String,String]() + } + } + + } + + def withPool[R]( fn : (ConnectionPool[DatabaseConnectionHandler]) => R ) : R = { + + val pool = new ConnectionPool( new ConnectionObjectFactory(defaultConfiguration), PoolConfiguration.Default ) + try { + fn(pool) + } finally { + pool.disconnect + } + + } + +} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala index 48684b60..4153eca6 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala @@ -20,10 +20,9 @@ import com.github.mauricio.async.db.postgresql.{DatabaseTestHelper, DatabaseConn import java.nio.channels.ClosedChannelException import java.util.concurrent.TimeUnit import org.specs2.mutable.Specification +import scala.concurrent.Await import scala.concurrent.duration._ -import scala.concurrent.{Future, Await} import scala.language.postfixOps -import com.github.mauricio.async.db.util.ExecutorServiceUtils class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { @@ -139,7 +138,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH validationInterval = validationInterval ) val factory = new ConnectionObjectFactory(this.defaultConfiguration) - val pool = new SingleThreadedAsyncObjectPool[DatabaseConnectionHandler](factory, poolConfiguration, ExecutorServiceUtils.CachedExecutionContext) + val pool = new SingleThreadedAsyncObjectPool[DatabaseConnectionHandler](factory, poolConfiguration) try { fn(pool) @@ -156,8 +155,4 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH Await.result(future, Duration(5, TimeUnit.SECONDS)) } - def await[T](future: Future[T]): T = { - Await.result(future, Duration(5, TimeUnit.SECONDS)) - } - } From 90229766e39723c0a389d537628509ff583af18e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 10:51:25 -0300 Subject: [PATCH 032/357] Using column definition order from message instead of the column data when creating result sets --- .../github/mauricio/async/db/ResultSet.scala | 23 ----- .../github/mauricio/async/db/RowData.scala | 2 +- .../async/db/general/ArrayRowData.scala | 3 +- ...ableQuery.scala => MutableResultSet.scala} | 25 ++--- .../DatabaseConnectionHandler.scala | 8 +- .../column/DateEncoderDecoder.scala | 4 +- .../parsers/RowDescriptionParser.scala | 1 - .../db/general/MutableResultSetSpec.scala | 91 +++++++++++++++++++ .../async/db/postgresql/ArrayTypesSpec.scala | 12 +-- .../DatabaseConnectionHandlerSpec.scala | 78 ++++++++-------- .../postgresql/pool/ConnectionPoolSpec.scala | 4 +- .../SingleThreadedAsyncObjectPoolSpec.scala | 2 +- .../async/db/util/ChannelUtilsSpec.scala | 40 ++++++++ 13 files changed, 195 insertions(+), 98 deletions(-) rename src/main/scala/com/github/mauricio/async/db/general/{MutableQuery.scala => MutableResultSet.scala} (79%) create mode 100644 src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala create mode 100644 src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala diff --git a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala index f7bb635d..b3e8b7f9 100644 --- a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala +++ b/src/main/scala/com/github/mauricio/async/db/ResultSet.scala @@ -34,27 +34,4 @@ trait ResultSet extends IndexedSeq[RowData] { def columnNames : IndexedSeq[String] - /** - * - * Returns the value in the given column name at the given row. - * - * @param name - * @param row - * @return - */ - - def apply(name: String, row: Int): Any - - /** - * - * Returns the value in the given column index at the given row. The column order is usually defined by the - * "SELECT" clause of the statement executed. - * - * @param column - * @param row - * @return - */ - - def apply(column: Int, row: Int): Any - } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/RowData.scala b/src/main/scala/com/github/mauricio/async/db/RowData.scala index e3a3d1f4..2a5d0845 100644 --- a/src/main/scala/com/github/mauricio/async/db/RowData.scala +++ b/src/main/scala/com/github/mauricio/async/db/RowData.scala @@ -22,7 +22,7 @@ package com.github.mauricio.async.db * */ -trait RowData { +trait RowData extends IndexedSeq[Any] { /** * diff --git a/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala b/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala index 1705de8c..386a343c 100644 --- a/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala +++ b/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.general import com.github.mauricio.async.db.RowData -class ArrayRowData( columnCount : Int, row : Int, mapping : Map[String, Int] ) extends RowData { +class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] ) extends RowData { private val columns = new Array[Any](columnCount) @@ -58,4 +58,5 @@ class ArrayRowData( columnCount : Int, row : Int, mapping : Map[String, Int] ) e def update(i: Int, x: Any) = columns(i) = x + def length: Int = columns.length } diff --git a/src/main/scala/com/github/mauricio/async/db/general/MutableQuery.scala b/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala similarity index 79% rename from src/main/scala/com/github/mauricio/async/db/general/MutableQuery.scala rename to src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index 2d5a8596..7057d60e 100644 --- a/src/main/scala/com/github/mauricio/async/db/general/MutableQuery.scala +++ b/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -24,17 +24,17 @@ import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -object MutableQuery { - val log = Log.get[MutableQuery] +object MutableResultSet { + val log = Log.get[MutableResultSet] } -class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder : ColumnDecoderRegistry) extends ResultSet { +class MutableResultSet(val columnTypes: Array[ColumnData], charset: Charset, decoder : ColumnDecoderRegistry) extends ResultSet { private val rows = new ArrayBuffer[RowData]() - private val columnMapping: Map[String, Int] = this.columnTypes.map { - columnData => - (columnData.name, columnData.columnNumber - 1) - }.toMap + private val columnMapping: Map[String, Int] = this.columnTypes.indices.map( + index => + ( this.columnTypes(index).name, index ) ) + .toMap override def columnNames : IndexedSeq[String] = this.columnTypes.map( data => data.name ) @@ -43,25 +43,18 @@ class MutableQuery(val columnTypes: Array[ColumnData], charset: Charset, decoder override def apply(idx: Int): RowData = this.rows(idx) def addRawRow(row: Array[ChannelBuffer]) { - val realRow = new ArrayRowData(columnMapping.size, this.rows.size, this.columnMapping) - 0.until(row.length).foreach { + realRow.indices.foreach { index => - realRow(index) = if (row(index) == null) { null } else { this.decoder.decode( this.columnTypes(index).dataType, row(index).toString(charset) ) } - } this.rows += realRow } - def apply(name: String, row: Int): Any = this.rows(row)(this.columnMapping(name)) - - def apply(column: Int, row: Int): Any = this.rows(row)(column) - -} +} \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 7893233c..c6e6ff14 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -32,7 +32,7 @@ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} -import com.github.mauricio.async.db.general.MutableQuery +import com.github.mauricio.async.db.general.MutableResultSet object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -72,7 +72,7 @@ class DatabaseConnectionHandler private var connected = false private var queryPromise: Option[Promise[QueryResult]] = None - private var currentQuery: Option[MutableQuery] = None + private var currentQuery: Option[MutableResultSet] = None private var currentPreparedStatement: Option[String] = None private var _currentChannel: Option[Channel] = None @@ -265,7 +265,7 @@ class DatabaseConnectionHandler log.debug("MutableQuery is not parsed yet -> {}", realQuery) this.currentChannel.write(new PreparedStatementOpeningMessage(realQuery, values, this.encoderRegistry)) } else { - this.currentQuery = Some(new MutableQuery(this.parsedStatements.get(realQuery), configuration.charset, this.decoderRegistry)) + this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(realQuery), configuration.charset, this.decoderRegistry)) this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values, this.encoderRegistry)) } @@ -343,7 +343,7 @@ class DatabaseConnectionHandler private def onRowDescription(m: RowDescriptionMessage) { log.debug("received query description {}", m) - this.currentQuery = Option(new MutableQuery(m.columnDatas, configuration.charset, this.decoderRegistry)) + this.currentQuery = Option(new MutableResultSet(m.columnDatas, configuration.charset, this.decoderRegistry)) log.debug("Current prepared statement is {}", this.currentPreparedStatement) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala index 13a07440..b07844ad 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException import org.joda.time.format.DateTimeFormat -import org.joda.time.{ReadableInstant, LocalDate} +import org.joda.time.{ReadablePartial, ReadableInstant, LocalDate} object DateEncoderDecoder extends ColumnEncoderDecoder { @@ -31,7 +31,7 @@ object DateEncoderDecoder extends ColumnEncoderDecoder { override def encode(value: Any): String = { value match { case d: java.sql.Date => this.formatter.print(new LocalDate(d)) - case d: ReadableInstant => this.formatter.print(d) + case d: ReadablePartial => this.formatter.print(d) case _ => throw new DateEncoderNotAvailableException(value) } } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 4f4eb951..7dd96cd3 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -61,7 +61,6 @@ The format code being used for the field. Currently will be zero (text) or one ( class RowDescriptionParser(charset: Charset) extends MessageParser { - override def parseMessage(b: ChannelBuffer): Message = { val columnsCount = b.readShort() diff --git a/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala b/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala new file mode 100644 index 00000000..82b78adf --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala @@ -0,0 +1,91 @@ +/* + * 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.general + +import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnTypes} +import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import org.jboss.netty.util.CharsetUtil +import org.specs2.mutable.Specification + +class MutableResultSetSpec extends Specification { + + val charset = CharsetUtil.UTF_8 + val decoder = new DefaultColumnDecoderRegistry + + "result set" should { + + "correctly map column data to fields" in { + + val columns = Array( + new ColumnData( + name = "id", + tableObjectId = 0, + columnNumber = 0, + dataType = ColumnTypes.Integer, + dataTypeSize = 4, + dataTypeModifier = 0, + fieldFormat = 0 + ), + new ColumnData( + name = "name", + tableObjectId = 0, + columnNumber = 5, + dataType = ColumnTypes.Varchar, + dataTypeSize = -1, + dataTypeModifier = 0, + fieldFormat = 0 + ) + ) + + val text = "some data" + val otherText = "some other data" + + val resultSet = new MutableResultSet(columns, charset, decoder) + + resultSet.addRawRow( Array( toBuffer(1), toBuffer( text ) ) ) + resultSet.addRawRow( Array( toBuffer(2), toBuffer( otherText ) ) ) + + resultSet(0)(0) === 1 + resultSet(0)("id") === 1 + + resultSet(0)(1) === text + resultSet(0)("name") === text + + resultSet(1)(0) === 2 + resultSet(1)("id") === 2 + + resultSet(1)(1) === otherText + resultSet(1)("name") === otherText + + } + + } + + def toBuffer( content : String ) : ChannelBuffer = { + val buffer = ChannelBuffers.dynamicBuffer() + buffer.writeBytes( content.getBytes(charset) ) + buffer + } + + def toBuffer( value : Int ) : ChannelBuffer = { + val buffer = ChannelBuffers.dynamicBuffer() + buffer.writeBytes(value.toString.getBytes(charset)) + buffer + } + +} diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala index 0b0bc122..374ec73a 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala @@ -51,9 +51,9 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { executeDdl(handler, simpleCreate) executeDdl(handler, insert, 1) val result = executeQuery(handler, "select * from type_test_table").rows.get - result("smallint_column", 0) === List(1,2,3,4) - result("text_column", 0) === List("some,\"comma,separated,text", "another line of text", null ) - result("timestamp_column", 0) === List( + result(0)("smallint_column") === List(1,2,3,4) + result(0)("text_column") === List("some,\"comma,separated,text", "another line of text", 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") ) @@ -80,9 +80,9 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { val result = executeQuery(handler, "select * from type_test_table").rows.get - result("smallint_column", 0) === numbers - result("text_column", 0) === texts - result("timestamp_column", 0) === timestamps + result(0)("smallint_column") === numbers + result(0)("text_column") === texts + result(0)("timestamp_column") === timestamps } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 3a64ad68..6a44f2c9 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -126,23 +126,21 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe executeDdl(handler, this.insert, 1) val result = executeQuery(handler, this.select) - val rows = result.rows.get - - List( - rows(0, 0) === 1, - rows(1, 0) === 10, - rows(2, 0) === 11, - rows(3, 0).toString === "14.9990", - rows(4, 0).toString === 78.34.toString, - rows(5, 0) === 15.68, - rows(6, 0) === 1, - rows(7, 0) === "this is a varchar field", - rows(8, 0) === "this is a long text field", - rows(9, 0) === TimestampEncoderDecoder.Instance.decode("1984-08-06 22:13:45.888888"), - rows(10, 0) === DateEncoderDecoder.decode("1984-08-06"), - rows(11, 0) === TimeEncoderDecoder.Instance.decode("22:13:45.888888"), - rows(12, 0) === true - ) + val row = result.rows.get(0) + + row(0) === 1 + row(1) === 10 + row(2) === 11 + row(3).toString === "14.9990" + row(4).toString === 78.34.toString + row(5) === 15.68 + row(6) === 1 + row(7) === "this is a varchar field" + row(8) === "this is a long text field" + row(9) === TimestampEncoderDecoder.Instance.decode("1984-08-06 22:13:45.888888") + row(10) === DateEncoderDecoder.decode("1984-08-06") + row(11) === TimeEncoderDecoder.Instance.decode("22:13:45.888888") + row(12) === true } @@ -157,12 +155,12 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe executeDdl(handler, this.preparedStatementInsert, 1) val result = executePreparedStatement(handler, this.preparedStatementSelect) - val rows = result.rows.get + val row = result.rows.get(0) + + + row(0) === 1 + row(1) === "John Doe" - List( - rows(0, 0) === 1, - rows(1, 0) === "John Doe" - ) } @@ -180,19 +178,17 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe val select = "select * from prepared_statement_test where name like ?" val queryResult = executePreparedStatement(handler, select, Array("Peter Parker")) - val rows = queryResult.rows.get + val row = queryResult.rows.get(0) val queryResult2 = executePreparedStatement(handler, select, Array("Mary Jane")) - val rows2 = queryResult2.rows.get - - List( - rows(0, 0) === 3, - rows(1, 0) === "Peter Parker", - rows.length === 1, - rows2.length === 1, - rows2(0, 0) === 2, - rows2(1, 0) === "Mary Jane" - ) + val row2 = queryResult2.rows.get(0) + + row(0) === 3 + row(1) === "Peter Parker" + + row2(0) === 2 + row2(1) === "Mary Jane" + } } @@ -209,7 +205,7 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe withHandler(configuration, { handler => val result = executeQuery(handler, "SELECT 0") - result.rows.get.apply(0, 0) === 0 + result.rows.get.apply(0)(0) === 0 }) } @@ -226,7 +222,7 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe withHandler(configuration, { handler => val result = executeQuery(handler, "SELECT 0") - result.rows.get.apply(0, 0) === 0 + result.rows.get(0)(0) === 0 }) } @@ -283,7 +279,7 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe val queryResult: QueryResult = Await.result(result, Duration(5, SECONDS)) - queryResult.rows.get(0, 0) === 0 + queryResult.rows.get(0)(0) === 0 } @@ -291,9 +287,9 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe withHandler { connection => - executeDdl( connection, this.preparedStatementCreate ) - val result = executeQuery( connection, this.preparedStatementInsertReturning ) - result.rows.get("id", 0) === 1 + executeDdl(connection, this.preparedStatementCreate) + val result = executeQuery(connection, this.preparedStatementInsertReturning) + result.rows.get(0)("id") === 1 } } @@ -307,9 +303,9 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe executeDdl(handler, this.preparedStatementInsert2, 1) executeDdl(handler, this.preparedStatementInsert3, 1) - val result = executePreparedStatement(handler, "select * from prepared_statement_test LIMIT 1").rows.get + val result = executePreparedStatement(handler, "select * from prepared_statement_test LIMIT 1").rows.get(0) - result("name", 0) === "John Doe" + result("name") === "John Doe" } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala index 80a61074..c4505e5b 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala @@ -27,7 +27,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { withPool{ pool => - executeQuery(pool, "SELECT 8").rows.get(0,0) === 8 + executeQuery(pool, "SELECT 8").rows.get(0)(0) === 8 pool.availables.size === 1 } @@ -36,7 +36,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { "give you a connection for prepared statements" in { withPool{ pool => - executePreparedStatement(pool, "SELECT 8").rows.get(0,0) === 8 + executePreparedStatement(pool, "SELECT 8").rows.get(0)(0) === 8 pool.availables.size === 1 } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala index 4153eca6..1848fb6e 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala @@ -148,7 +148,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH } - def executeTest(connection: DatabaseConnectionHandler) = executeQuery(connection, "SELECT 0").rows.get(0, 0) === 0 + def executeTest(connection: DatabaseConnectionHandler) = executeQuery(connection, "SELECT 0").rows.get(0)(0) === 0 def get(pool: SingleThreadedAsyncObjectPool[DatabaseConnectionHandler]): DatabaseConnectionHandler = { val future = pool.take diff --git a/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala b/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala new file mode 100644 index 00000000..f4c2d0b8 --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala @@ -0,0 +1,40 @@ +package com.github.mauricio.async.db.util + +import org.specs2.mutable.Specification +import org.jboss.netty.util.CharsetUtil +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} + +/* + * 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. + */ + +class ChannelUtilsSpec extends Specification { + + val charset = CharsetUtil.UTF_8 + + "utils" should { + + "correctly write and read a string" in { + val content = "some text" + val buffer = ChannelBuffers.dynamicBuffer() + + ChannelUtils.writeCString(content, buffer, charset) + + ChannelUtils.readCString(buffer, charset) === content + } + + } + +} From 2a8300cdb0d3e803bde3b1fb6f6d23c41296baad Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 11:41:53 -0300 Subject: [PATCH 033/357] Raise exception if parameters count is different than number of given parameters --- .../DatabaseConnectionHandler.scala | 12 ++++++- .../InsufficientParametersException.scala | 32 +++++++++++++++++++ .../DatabaseConnectionHandlerSpec.scala | 26 ++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index c6e6ff14..fb1681a7 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.postgresql.exceptions.{QueryMustNotBeNullOrEmptyException, MissingCredentialInformationException, NotConnectedException, GenericDatabaseException} +import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.{Log, ExecutorServiceUtils} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import com.github.mauricio.postgresql.MessageEncoder @@ -33,6 +33,12 @@ import scala.Some import scala.collection.JavaConversions._ import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} import com.github.mauricio.async.db.general.MutableResultSet +import scala.Some +import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage +import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage +import com.github.mauricio.async.db.postgresql.messages.backend.ProcessData +import com.github.mauricio.async.db.postgresql.messages.backend.RowDescriptionMessage +import com.github.mauricio.async.db.postgresql.messages.backend.ParameterStatusMessage object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -259,6 +265,10 @@ class DatabaseConnectionHandler query } + if ( paramsCount != values.length ) { + throw new InsufficientParametersException(paramsCount, values) + } + this.currentPreparedStatement = Some(realQuery) if (!this.isParsed(realQuery)) { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala new file mode 100644 index 00000000..8137ec3d --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala @@ -0,0 +1,32 @@ +/* + * 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.exceptions + +/** + * + * Raised when the user gives more or less parameters than the query takes. Each parameter is a ? + * (question mark) in the query string. The count of ? should be the same as the count of items in the provided + * sequence of parameters. + * + * @param expected the expected count of parameters + * @param given the collection given + */ +class InsufficientParametersException( expected : Int, given : Seq[Any] ) + extends DatabaseException( + "The query contains %s parameters but you gave it %s (%s)".format(expected, given.length, given.mkString(",") + ) + ) diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 6a44f2c9..07ae39d7 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.postgresql.column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} -import com.github.mauricio.async.db.postgresql.exceptions.{QueryMustNotBeNullOrEmptyException, GenericDatabaseException, UnsupportedAuthenticationMethodException} +import com.github.mauricio.async.db.postgresql.exceptions.{InsufficientParametersException, QueryMustNotBeNullOrEmptyException, GenericDatabaseException, UnsupportedAuthenticationMethodException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} @@ -25,6 +25,7 @@ import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +import org.joda.time.LocalDate class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { @@ -87,6 +88,17 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe val preparedStatementInsertReturning = " insert into prepared_statement_test (name) values ('John Doe') returning id" val preparedStatementSelect = "select * from prepared_statement_test" + val messagesCreate = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + content character varying(255) NOT NULL, + moment date NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + val messagesInsert = "INSERT INTO messages (content,moment) VALUES (?,?) RETURNING id" + val messagesUpdate = "UPDATE messages SET content = ?, moment = ? WHERE id = ?" + val messagesSelectOne = "SELECT id, content, moment FROM messages WHERE id = ?" + "handler" should { "connect to the database" in { @@ -328,6 +340,18 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } + "raise an exception if the parameter count is different from the given parameters count" in { + + withHandler { + handler => + executeDdl( handler, this.messagesCreate ) + executePreparedStatement(handler, this.messagesSelectOne) must throwAn[InsufficientParametersException] + } + + } + + + } } From 426a17d1e9606b882c4572889527e8cce86ff564 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 12:42:29 -0300 Subject: [PATCH 034/357] URLParser accepts Heroku like URLs --- .../async/db/postgresql/util/ParseURL.java | 63 ++++++++++--------- .../mauricio/async/db/util/URLParser.scala | 10 ++- .../db/postgresql/util/URLParserSpec.scala | 12 ++++ 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java b/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java index c3f1d6dd..bc5e4a2c 100644 --- a/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java +++ b/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java @@ -17,8 +17,9 @@ public class ParseURL { public static final String PGPORT = "port"; public static final String PGDBNAME = "database"; public static final String PGHOST = "host"; - public static final String PROTOCOL = "Protocol"; - static private String[] protocols = { "jdbc", "postgresql" }; + public static final String PGUSERNAME = "username"; + public static final String PGPASSWORD = "password"; + public static Properties parseURL(String url) throws SQLException { @@ -49,6 +50,34 @@ public static Properties parseURL(String url) throws SQLException l_urlServer = l_urlServer.substring(0, ipv6start) + "ipv6host" + l_urlServer.substring(ipv6end + 1); } + int serverStartIndex = url.indexOf("://"); + int serverEndIndex = url.indexOf("/", serverStartIndex + 3); + + if ( serverStartIndex >= 0 && serverEndIndex > serverStartIndex ) { + + String serverPart = url.substring( serverStartIndex + 3, serverEndIndex ); + + if ( serverPart.contains("@") ) { + String[] parts = serverPart.split("@"); + + if ( parts[0].contains(":") ) { + String[] userInfo = parts[0].split(":"); + urlProps.setProperty(PGUSERNAME, userInfo[0]); + urlProps.setProperty(PGPASSWORD, userInfo[1]); + } + + if ( parts[1].contains(":") ) { + String[] hostParts = parts[1].split(":"); + urlProps.setProperty(PGHOST, hostParts[0]); + urlProps.setProperty(PGPORT, hostParts[1]); + } else { + urlProps.setProperty(PGHOST, parts[1]); + } + + } + + } + //parse the server part of the url StringTokenizer st = new StringTokenizer(l_urlServer, ":/", true); int count; @@ -56,35 +85,7 @@ public static Properties parseURL(String url) throws SQLException { String token = st.nextToken(); - // PM Aug 2 1997 - Modified to allow multiple backends - if (count <= 3) - { - if ((count % 2) == 1 && token.equals(":")) - ; - else if ((count % 2) == 0) - { - boolean found = (count == 0) ? true : false; - for (int tmp = 0;tmp < protocols.length;tmp++) - { - if (token.equals(protocols[tmp])) - { - // PM June 29 1997 Added this property to enable the driver - // to handle multiple backend protocols. - if (count == 2 && tmp > 0) - { - urlProps.setProperty(PROTOCOL, token); - found = true; - } - } - } - - if (found == false) - return null; - } - else - return null; - } - else if (count > 3) + if (count > 3) { if (count == 4 && token.equals("/")) state = 0; diff --git a/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala b/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala index c6990837..a947f64f 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala @@ -21,9 +21,13 @@ import com.github.mauricio.async.db.postgresql.util.ParseURL import java.nio.charset.Charset import java.util.concurrent.ExecutorService import scala.collection.JavaConversions._ +import java.net.URI object URLParser { + private val Username = "username" + private val Password = "password" + import Configuration.Default def parse(url: String, @@ -34,11 +38,13 @@ object URLParser { val properties = ParseURL.parseURL(url).toMap + println("properties ===> " + properties) + val port = properties(ParseURL.PGPORT).toInt new Configuration( - username = properties.get("username").getOrElse(Default.username), - password = properties.get("password"), + username = properties.get( Username ).getOrElse(Default.username), + password = properties.get( Password ), database = properties.get(ParseURL.PGDBNAME), host = properties(ParseURL.PGHOST), port = port, diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala index 6f6f2c87..87d3999f 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala @@ -35,6 +35,18 @@ class URLParserSpec extends Specification { configuration.port === 9987 } + "create a connection from a heroku like URL" in { + val connectionUri = "postgresql://john:doe@128.567.54.90:9987/my_database" + + val configuration = URLParser.parse(connectionUri) + + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "128.567.54.90" + configuration.port === 9987 + } + } } From 7b3230fc752948c5a23f66224262c2726fbb368f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 13:16:23 -0300 Subject: [PATCH 035/357] Fixing wrong package name in class --- build.sbt | 1 + .../db/postgresql/DatabaseConnectionHandler.scala | 11 ++--------- .../mauricio/async/db/postgresql/MessageEncoder.scala | 7 +++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index 56e8b3d0..899d5dcb 100644 --- a/build.sbt +++ b/build.sbt @@ -19,6 +19,7 @@ libraryDependencies ++= Seq( ) scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature") +javacOptions ++= Seq("-source", "1.5", "-target", "1.5") credentials += Credentials(Path.userHome / ".ivy2" / ".credentials") diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index fb1681a7..9e773a25 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -16,10 +16,11 @@ package com.github.mauricio.async.db.postgresql +import com.github.mauricio.async.db.general.MutableResultSet +import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.{Log, ExecutorServiceUtils} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} -import com.github.mauricio.postgresql.MessageEncoder import concurrent.{Future, Promise} import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap @@ -31,14 +32,6 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ -import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} -import com.github.mauricio.async.db.general.MutableResultSet -import scala.Some -import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage -import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage -import com.github.mauricio.async.db.postgresql.messages.backend.ProcessData -import com.github.mauricio.async.db.postgresql.messages.backend.RowDescriptionMessage -import com.github.mauricio.async.db.postgresql.messages.backend.ParameterStatusMessage object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index 92e4ff35..42316b94 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -14,8 +14,9 @@ * under the License. */ -package com.github.mauricio.postgresql +package com.github.mauricio.async.db.postgresql +import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.encoders._ import com.github.mauricio.async.db.postgresql.exceptions.EncoderNotAvailableException import com.github.mauricio.async.db.postgresql.messages.frontend._ @@ -24,15 +25,13 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry object MessageEncoder { val log = Log.get[MessageEncoder] } -class MessageEncoder(charset: Charset, encoderRegistry : ColumnEncoderRegistry) extends OneToOneEncoder { +class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) extends OneToOneEncoder { - import MessageEncoder.log val encoders: Map[Class[_], Encoder] = Map( classOf[CloseMessage] -> CloseMessageEncoder, From 00b8e9660b1d8d0f807d294e128d4fc3ed3cac99 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 13:22:02 -0300 Subject: [PATCH 036/357] Updating README --- README.markdown | 2 +- build.sbt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index a9d82370..c5924fea 100644 --- a/README.markdown +++ b/README.markdown @@ -31,7 +31,7 @@ to PostgreSQL. - small memory footprint - avoid copying data as much as possible (we're always trying to use wrap and slice on buffers) - easy to use, call a method, get a future or a callback and be happy -- never, ever, block (the only real blocking right now is at the connection pool) +- never, ever, block - all features covered by tests ## How can I help? diff --git a/build.sbt b/build.sbt index 899d5dcb..1d43781d 100644 --- a/build.sbt +++ b/build.sbt @@ -19,6 +19,7 @@ libraryDependencies ++= Seq( ) scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature") + javacOptions ++= Seq("-source", "1.5", "-target", "1.5") credentials += Credentials(Path.userHome / ".ivy2" / ".credentials") From 8f5c374861b430ab3c92ea991d4688bf21e25ecb Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 13:47:48 -0300 Subject: [PATCH 037/357] Remove dangling println --- build.sbt | 2 +- .../com/github/mauricio/async/db/util/URLParser.scala | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 1d43781d..daa23995 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import JacocoPlugin._ name := "postgresql-async" -version := "0.0.1-SNAPSHOT" +version := "0.0.2-SNAPSHOT" organization := "com.github.mauricio" diff --git a/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala b/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala index a947f64f..3a30a3b4 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala @@ -21,7 +21,6 @@ import com.github.mauricio.async.db.postgresql.util.ParseURL import java.nio.charset.Charset import java.util.concurrent.ExecutorService import scala.collection.JavaConversions._ -import java.net.URI object URLParser { @@ -38,13 +37,11 @@ object URLParser { val properties = ParseURL.parseURL(url).toMap - println("properties ===> " + properties) - val port = properties(ParseURL.PGPORT).toInt new Configuration( - username = properties.get( Username ).getOrElse(Default.username), - password = properties.get( Password ), + username = properties.get(Username).getOrElse(Default.username), + password = properties.get(Password), database = properties.get(ParseURL.PGDBNAME), host = properties(ParseURL.PGHOST), port = port, From 3a78cabda4c5a902b7e1efcc8776c35c98881e23 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 14:11:54 -0300 Subject: [PATCH 038/357] Marking 0.1.0 release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index daa23995..b47be971 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import JacocoPlugin._ name := "postgresql-async" -version := "0.0.2-SNAPSHOT" +version := "0.1.0" organization := "com.github.mauricio" From ddf7127b661e7dd2bfc3f2cbb134a9f6b4ea31bc Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 16:17:09 -0300 Subject: [PATCH 039/357] Adding a little more docs o readme --- README.markdown | 54 ++++++++++++++++++ .../DatabaseConnectionHandler.scala | 2 +- .../async/db/examples/BasicExample.scala | 55 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala diff --git a/README.markdown b/README.markdown index c5924fea..5408dfdf 100644 --- a/README.markdown +++ b/README.markdown @@ -41,4 +41,58 @@ to PostgreSQL. - check the **What is missing** piece - send a pull request with specs +## Example usage + +You can find a small Play 2 app using it [here](https://github.com/mauricio/postgresql-async-app) and a blog post about +it [here](http://mauricio.github.io/2013/04/29/async-database-access-with-postgresql-play-scala-and-heroku.html). + +In short, what you would usually do is: +```scala +import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler +import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext +import com.github.mauricio.async.db.util.URLParser +import com.github.mauricio.async.db.{RowData, QueryResult, Connection} +import scala.concurrent.duration._ +import scala.concurrent.{Await, Future} + +object BasicExample { + + def main(args: Array[String]) { + + val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?username=postgres&password=somepassword") + val connection: Connection = new DatabaseConnectionHandler(configuration) + + Await.result(connection.connect, 5 seconds) + + val future: Future[QueryResult] = connection.sendQuery("SELECT 0") + + val mapResult: Future[Any] = future.map(queryResult => queryResult.rows match { + case Some(resultSet) => { + val row : RowData = resultSet.head + row(0) + } + case None => -1 + } + ) + + val result = Await.result( mapResult, 5 seconds ) + + println(result) + + connection.disconnect + + } + +} +``` + +First, create a `DatabaseConnectionHandler`, connect it to the database, execute queries (this object is not thread +safe, so you must execute only one query at a time) and work with the futures it returns. Once you are done, call +disconnect and the connection is closed. + +You can also use the `ConnectionPool` provided by the driver to simplify working with database connections in your app. +Check the blog post above for more details and the project's ScalaDocs. + +## Licence + This project is freely available under the Apache 2 licence, use it at your own risk. \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 9e773a25..f473ef25 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -35,7 +35,7 @@ import scala.collection.JavaConversions._ object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] - val Name = "Netty-PostgreSQL-driver-0.0.1" + val Name = "Netty-PostgreSQL-driver-0.1.0" InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) } diff --git a/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala b/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala new file mode 100644 index 00000000..05d3e40d --- /dev/null +++ b/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala @@ -0,0 +1,55 @@ +/* + * 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.examples + +import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler +import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext +import com.github.mauricio.async.db.util.URLParser +import com.github.mauricio.async.db.{RowData, QueryResult, Connection} +import scala.concurrent.duration._ +import scala.concurrent.{Await, Future} + +object BasicExample { + + def main(args: Array[String]) { + + val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?username=postgres&password=somepassword") + val connection: Connection = new DatabaseConnectionHandler(configuration) + + Await.result(connection.connect, 5 seconds) + + val future: Future[QueryResult] = connection.sendQuery("SELECT 0") + + val mapResult: Future[Any] = future.map(queryResult => queryResult.rows match { + case Some(resultSet) => { + val row : RowData = resultSet.head + row(0) + } + case None => -1 + } + ) + + val result = Await.result( mapResult, 5 seconds ) + + println(result) + + connection.disconnect + + } + + +} From 3a9ded1ff40afdd55af552468f1016e9c877beac Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 16:46:31 -0300 Subject: [PATCH 040/357] More docs update --- README.markdown | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/README.markdown b/README.markdown index 5408dfdf..396a0e18 100644 --- a/README.markdown +++ b/README.markdown @@ -6,6 +6,22 @@ to PostgreSQL. [PostgreSQL protocol information and definition can be found here](http://www.postgresql.org/docs/devel/static/protocol.html) +Include at your SBT project with: + +```scala +"com.github.mauricio" %% "postgresql-async" % "0.1.0" +``` + +Or Maven: + +```xml + + com.github.mauricio + postgresql-async + 0.1.0 + +``` + ## What can it do now? - connect to a database with or without authentication (supports MD5 and cleartext authentication methods) @@ -33,6 +49,7 @@ to PostgreSQL. - easy to use, call a method, get a future or a callback and be happy - never, ever, block - all features covered by tests +- next to zero dependencies (it currently depends on Netty and SFL4J only) ## How can I help? @@ -41,6 +58,52 @@ to PostgreSQL. - check the **What is missing** piece - send a pull request with specs +## Main public interface + +### Connection + +Represents a connection to the database. This is the **root** object you will be using in your application. You will +find two classes that implement this trait, `DatabaseConnectionHandler` and `ConnectionPool`. The different between +them is that `DatabaseConnectionHandler` is a single connection and `ConnectionPool` represents a pool of connections. + +To create both you will need a `Configuration` object with your database details. You can create one manually or +create one from a JDBC or Heroku database URL using the `URLParser` object. + +### QueryResult + +It's the output of running a statement against the database (either using `sendQuery` or `sendPreparedStatement`). +This object will contain the amount of rows, status message and the possible `ResultSet` (Option\[ResultSet]) if the +query returns any rows. + +### ResultSet + +It's an IndexedSeq\[Array\[Any]], every item is a row returned by the database. The database types are returned as Scala +objects that fit the original type, so `smallint` becomes a `Short`, `numeric` becomes `BigDecimal`, `varchar` becomes +`String` and so on. You can find the whole default transformation list at the `DefaultColumnDecoderRegistry` class. + +### Prepared statements + +Databases support prepared or precompiled statements. These statements allow the database to precompile the query +on the first execution and reuse this compiled representation on future executions, this makes it faster and also allows +for safer data escaping when dealing with user provided data. + +To execute a prepared statement you grab a connection and: + +```scala +val connection : Connection = ... +val future = connection.sendPreparedStatement( "SELECT * FROM products WHERE products.name = ?", Array( "Dominion" ) ) +``` + +The `?` (question mark) in the query is a parameter placeholder, it allows you to set a value in that place in the +query without having to escape stuff yourself. The driver itself will make sure this parameter is delivered to the +database in a safe way so you don't have to worry about SQL injection attacks. + +The basic numbers, Joda Time date, time, timestamp objects, strings and arrays of these objects are all valid values +as prepared statement parameters and they will be encoded to their respective PostgreSQL types. + +Remember that parameters are positional the order they show up at query should be the same as the one in the array or +sequence given to the method call. + ## Example usage You can find a small Play 2 app using it [here](https://github.com/mauricio/postgresql-async-app) and a blog post about From 59b6883bb091d85367fa7917dbe7c651c5fbe7b4 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 16:47:32 -0300 Subject: [PATCH 041/357] Adding 2.10 to maven dependency --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 396a0e18..838e9ad5 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -# postgresql-netty - an async Netty/NIO based PostgreSQL driver written in Scala +# postgresql-netty - an async Netty/NIO based PostgreSQL driver written in Scala 2.10 The main goal of this project is to implement a performant and fully functional async PostgreSQL driver. This project has no interest in JDBC, it's supposed to be a clean room implementation for people interested in talking directly @@ -17,7 +17,7 @@ Or Maven: ```xml com.github.mauricio - postgresql-async + postgresql-async_2.10 0.1.0 ``` From 892cd2450362636576f980f04a19aad9311c7a4b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 16:49:08 -0300 Subject: [PATCH 042/357] Updating name --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 838e9ad5..01c8dbed 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -# postgresql-netty - an async Netty/NIO based PostgreSQL driver written in Scala 2.10 +# postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10 The main goal of this project is to implement a performant and fully functional async PostgreSQL driver. This project has no interest in JDBC, it's supposed to be a clean room implementation for people interested in talking directly From c2cfbd21d5927dba7767b25e5e60512684b8925c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 29 Apr 2013 18:54:46 -0300 Subject: [PATCH 043/357] Changing from I to You --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 01c8dbed..75d52ee8 100644 --- a/README.markdown +++ b/README.markdown @@ -51,7 +51,7 @@ Or Maven: - all features covered by tests - next to zero dependencies (it currently depends on Netty and SFL4J only) -## How can I help? +## How can you help? - checkout the source code - find bugs, find places where performance can be improved From 577eeb0cc0607352203dba9fe595c57915202cd7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 30 Apr 2013 18:04:16 -0300 Subject: [PATCH 044/357] Optimizing message encoder a bit, making sure message decoder can handle negative length messages and validating message size to avoid killing the client due to OOM or eternal loops, fixing concurrency condition where the connection would fulfil a promise before cleaning it up making a waiting thread to think a query was still running --- README.markdown | 3 + .../async/db/postgresql/util/ParseURL.java | 8 ++ .../mauricio/async/db/Configuration.scala | 53 +++++-- .../pool/AsyncObjectPool.scala | 6 +- .../pool/ConnectionPool.scala | 6 +- .../{postgresql => }/pool/ObjectFactory.scala | 4 +- .../pool/PoolAlreadyTerminatedException.scala | 2 +- .../pool/PoolConfiguration.scala | 2 +- .../pool/PoolExhaustedException.scala | 2 +- .../pool/SingleThreadedAsyncObjectPool.scala | 6 +- .../DatabaseConnectionHandler.scala | 136 +++++++++++------- .../async/db/postgresql/MessageDecoder.scala | 14 +- .../async/db/postgresql/MessageEncoder.scala | 29 ++-- ...ConnectionStillRunningQueryException.scala | 23 +++ .../exceptions/MessageTooLongException.scala | 20 +++ .../NegativeMessageSizeException.scala | 20 +++ .../ParserNotAvailableException.scala | 2 +- .../messages/backend/BindComplete.scala | 6 +- .../messages/backend/CloseComplete.scala | 6 +- .../messages/backend/InformationMessage.scala | 4 +- .../postgresql/messages/backend/Message.scala | 54 +++---- .../messages/backend/ParseComplete.scala | 6 +- .../messages/frontend/CloseMessage.scala | 6 +- .../messages/frontend/FrontendMessage.scala | 2 +- .../frontend/PreparedStatementMessage.scala | 2 +- .../parsers/MessageParsersRegistry.scala | 50 ++++--- .../parsers/ReturningMessageParser.scala | 18 ++- .../pool/ConnectionObjectFactory.scala | 3 +- .../async/db/util/ExecutorServiceUtils.scala | 4 +- src/test/resources/logback.xml | 10 +- .../async/db/examples/BasicExample.scala | 3 +- .../DatabaseConnectionHandlerSpec.scala | 1 - .../db/postgresql/DatabaseTestHelper.scala | 32 ++++- .../db/postgresql/MessageDecoderSpec.scala | 19 ++- .../postgresql/pool/ConnectionPoolSpec.scala | 3 +- .../SingleThreadedAsyncObjectPoolSpec.scala | 3 +- 36 files changed, 375 insertions(+), 193 deletions(-) rename src/main/scala/com/github/mauricio/async/db/{postgresql => }/pool/AsyncObjectPool.scala (90%) rename src/main/scala/com/github/mauricio/async/db/{postgresql => }/pool/ConnectionPool.scala (97%) rename src/main/scala/com/github/mauricio/async/db/{postgresql => }/pool/ObjectFactory.scala (96%) rename src/main/scala/com/github/mauricio/async/db/{postgresql => }/pool/PoolAlreadyTerminatedException.scala (93%) rename src/main/scala/com/github/mauricio/async/db/{postgresql => }/pool/PoolConfiguration.scala (96%) rename src/main/scala/com/github/mauricio/async/db/{postgresql => }/pool/PoolExhaustedException.scala (93%) rename src/main/scala/com/github/mauricio/async/db/{postgresql => }/pool/SingleThreadedAsyncObjectPool.scala (96%) create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala create mode 100644 src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala diff --git a/README.markdown b/README.markdown index 75d52ee8..cf626a46 100644 --- a/README.markdown +++ b/README.markdown @@ -22,6 +22,9 @@ Or Maven: ``` +This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql.org/) driver under the +`com.github.mauricio.async.db.postgresql.util` package consisting of `PostgreSQLMD5Digest` and `ParseURL` classes. + ## What can it do now? - connect to a database with or without authentication (supports MD5 and cleartext authentication methods) diff --git a/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java b/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java index bc5e4a2c..e5d9def2 100644 --- a/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java +++ b/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java @@ -1,3 +1,11 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2003-2011, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- +*/ + package com.github.mauricio.async.db.postgresql.util; import com.github.mauricio.async.db.Configuration; diff --git a/src/main/scala/com/github/mauricio/async/db/Configuration.scala b/src/main/scala/com/github/mauricio/async/db/Configuration.scala index 55826783..06ddd777 100644 --- a/src/main/scala/com/github/mauricio/async/db/Configuration.scala +++ b/src/main/scala/com/github/mauricio/async/db/Configuration.scala @@ -19,24 +19,50 @@ package com.github.mauricio.async.db import java.nio.charset.Charset import java.util.concurrent.ExecutorService import org.jboss.netty.util.CharsetUtil -import util.ExecutorServiceUtils +import com.github.mauricio.async.db.util.{URLParser, ExecutorServiceUtils} +import com.github.mauricio.async.db.postgresql.MessageDecoder object Configuration { val Default = new Configuration("postgres") + + /** + * + * Creates a [[com.github.mauricio.async.db.Configuration]] object from a database URL, accepts both the + * JDBC URL as a Heroku like URL. + * + * @param url + * @param bossPool + * @param workerPool + * @param charset + * @return + */ + + def from(url: String, + bossPool: ExecutorService = Default.bossPool, + workerPool: ExecutorService = Default.workerPool, + charset: Charset = Default.charset): Configuration = URLParser.parse(url, bossPool, workerPool, charset) + } /** * - * Contains the configuration to be able to connect to a database. + * Contains the configuration necessary to connect to a database. * - * @param username - * @param host - * @param port - * @param password - * @param database - * @param bossPool - * @param workerPool - * @param charset + * @param username database username + * @param host database host, defaults to "localhost" + * @param port database port, defaults to 5432 + * @param password password, defaults to no password + * @param database database name, defaults to no database + * @param bossPool executor service used by by the Netty boss handler, defaults to the driver's + * cached thread pool at [[com.github.mauricio.async.db.util.ExecutorServiceUtils]] + * @param workerPool executor service used by the Netty worker handler, defaults to the driver's + * cached thread pool at [[com.github.mauricio.async.db.util.ExecutorServiceUtils]] + * @param charset charset for the connection, defaults to UTF-8, make sure you know what you are doing if you + * change this + * @param maximumMessageSize the maximum size a message from the server could possibly have, this limits possible + * OOM or eternal loop attacks the client could have, defaults to 16 MB. You can set this + * to any value you would like but again, make sure you know what you are doing if you do + * change it. */ case class Configuration(val username: String, @@ -44,7 +70,8 @@ case class Configuration(val username: String, val port: Int = 5432, val password: Option[String] = None, val database: Option[String] = None, - val bossPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, - val workerPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, - val charset: Charset = CharsetUtil.UTF_8 + val bossPool: ExecutorService = ExecutorServiceUtils.FixedThreadPool, + val workerPool: ExecutorService = ExecutorServiceUtils.FixedThreadPool, + val charset: Charset = CharsetUtil.UTF_8, + val maximumMessageSize: Int = MessageDecoder.DefaultMaximumSize ) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala similarity index 90% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala rename to src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala index 1793681d..f3f42680 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/AsyncObjectPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.pool +package com.github.mauricio.async.db.pool import scala.concurrent.Future @@ -33,7 +33,7 @@ trait AsyncObjectPool[T] { * * Returns an object from the pool to the callee with the returned future. If the pool can not create or enqueue * requests it will fill the returned [[scala.concurrent.Future]] with an - * [[com.github.mauricio.async.db.postgresql.pool.PoolExhaustedException]]. + * [[com.github.mauricio.async.db.pool.PoolExhaustedException]]. * * @return future that will eventually return a usable pool object. */ @@ -55,7 +55,7 @@ trait AsyncObjectPool[T] { /** * * Closes this pool and future calls to **take** will cause the [[scala.concurrent.Future]] to raise an - * [[com.github.mauricio.async.db.postgresql.pool.PoolAlreadyTerminatedException]]. + * [[com.github.mauricio.async.db.pool.PoolAlreadyTerminatedException]]. * * @return */ diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala b/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala similarity index 97% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala rename to src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala index 5bfa1f29..3b1cf218 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala @@ -14,11 +14,11 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.pool +package com.github.mauricio.async.db.pool +import com.github.mauricio.async.db.util.ExecutorServiceUtils import com.github.mauricio.async.db.{QueryResult, Connection} import scala.concurrent.{ExecutionContext, Future} -import com.github.mauricio.async.db.util.ExecutorServiceUtils /** * @@ -39,7 +39,7 @@ import com.github.mauricio.async.db.util.ExecutorServiceUtils class ConnectionPool[T <: Connection]( factory: ObjectFactory[T], configuration: PoolConfiguration, - executionContext: ExecutionContext = ExecutorServiceUtils.CachedExecutionContext + executionContext: ExecutionContext = ExecutorServiceUtils.FixedExecutionContext ) extends SingleThreadedAsyncObjectPool[T](factory, configuration) with Connection { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala similarity index 96% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala rename to src/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala index 1b96e25a..bf6c610e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala @@ -14,13 +14,13 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.pool +package com.github.mauricio.async.db.pool import scala.util.Try /** * - * Definition for objects that can be used as a factory for [[com.github.mauricio.async.db.postgresql.pool.AsyncObjectPool]] + * Definition for objects that can be used as a factory for [[com.github.mauricio.async.db.pool.AsyncObjectPool]] * objects. * * @tparam T the kind of object this factory produces. diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala b/src/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala similarity index 93% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala rename to src/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala index af247a57..8626eb39 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolAlreadyTerminatedException.scala +++ b/src/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala @@ -15,7 +15,7 @@ */ -package com.github.mauricio.async.db.postgresql.pool +package com.github.mauricio.async.db.pool /** * diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala b/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala similarity index 96% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala rename to src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala index 4bb20d4f..a245de5c 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolConfiguration.scala +++ b/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.pool +package com.github.mauricio.async.db.pool object PoolConfiguration { val Default = new PoolConfiguration(10, 4, 10) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala b/src/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala similarity index 93% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala rename to src/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala index 366058aa..15bab085 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PoolExhaustedException.scala +++ b/src/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.pool +package com.github.mauricio.async.db.pool /** * diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala b/src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala similarity index 96% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala rename to src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala index 4ff068d7..b17778f2 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPool.scala +++ b/src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala @@ -14,13 +14,13 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.pool +package com.github.mauricio.async.db.pool import com.github.mauricio.async.db.util.{Log, Worker} import java.util.concurrent.atomic.AtomicLong import java.util.{TimerTask, Timer} import scala.collection.mutable.ArrayBuffer -import scala.concurrent.{ExecutionContext, Promise, Future} +import scala.concurrent.{Promise, Future} import scala.util.{Failure, Success} object SingleThreadedAsyncObjectPool { @@ -30,7 +30,7 @@ object SingleThreadedAsyncObjectPool { /** * - * Implements an [[com.github.mauricio.async.db.postgresql.pool.AsyncObjectPool]] using a single thread from a + * Implements an [[com.github.mauricio.async.db.pool.AsyncObjectPool]] using a single thread from a * fixed executor service with a single thread as an event loop to cause all calls to be sequential. * * Once you are done with this object remember to call it's close method to clean up the thread pool and diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index f473ef25..cd4567ca 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -19,11 +19,12 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ -import com.github.mauricio.async.db.util.{Log, ExecutorServiceUtils} +import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import concurrent.{Future, Promise} import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.{AtomicReference, AtomicLong} import messages.backend._ import messages.frontend._ import org.jboss.netty.bootstrap.ClientBootstrap @@ -37,17 +38,19 @@ object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] val Name = "Netty-PostgreSQL-driver-0.1.0" InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) + val Counter = new AtomicLong() } class DatabaseConnectionHandler ( configuration: Configuration = Configuration.Default, - encoderRegistry : ColumnEncoderRegistry = DefaultColumnEncoderRegistry.Instance, - decoderRegistry : ColumnDecoderRegistry = DefaultColumnDecoderRegistry.Instance + encoderRegistry: ColumnEncoderRegistry = DefaultColumnEncoderRegistry.Instance, + decoderRegistry: ColumnDecoderRegistry = DefaultColumnDecoderRegistry.Instance ) extends SimpleChannelHandler with Connection { import DatabaseConnectionHandler._ + private val currentCount = Counter.incrementAndGet() private val properties = List( "user" -> configuration.username, "database" -> configuration.database, @@ -63,14 +66,15 @@ class DatabaseConnectionHandler private var authenticated = false private val factory = new NioClientSocketChannelFactory( - ExecutorServiceUtils.CachedThreadPool, - ExecutorServiceUtils.CachedThreadPool) + configuration.bossPool, + configuration.workerPool) private val bootstrap = new ClientBootstrap(this.factory) private val connectionFuture = Promise[Map[String, String]]() private var connected = false - private var queryPromise: Option[Promise[QueryResult]] = None + private var recentError = false + private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None) private var currentQuery: Option[MutableResultSet] = None private var currentPreparedStatement: Option[String] = None private var _currentChannel: Option[Channel] = None @@ -83,7 +87,7 @@ class DatabaseConnectionHandler override def getPipeline(): ChannelPipeline = { Channels.pipeline( - new MessageDecoder(configuration.charset), + new MessageDecoder(configuration.charset, configuration.maximumMessageSize), new MessageEncoder(configuration.charset, encoderRegistry), DatabaseConnectionHandler.this) } @@ -110,17 +114,16 @@ class DatabaseConnectionHandler } override def disconnect: Future[Connection] = { - val closingPromise = Promise[Connection]() if (this.currentChannel.isConnected) { - this.currentChannel.write(CloseMessage.Instance).addListener(new ChannelFutureListener { + this.currentChannel.write(CloseMessage).addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.getCause != null) { closingPromise.failure(future.getCause) } else { - if ( future.getChannel.isOpen ) { + if (future.getChannel.isOpen) { future.getChannel.close().addListener(new ChannelFutureListener { def operationComplete(internalFuture: ChannelFuture) { if (internalFuture.isSuccess) { @@ -145,7 +148,7 @@ class DatabaseConnectionHandler } override def isConnected: Boolean = { - if ( this.currentChannel != null ) { + if (this.currentChannel != null) { this.currentChannel.isConnected } else { this.connected @@ -174,7 +177,6 @@ class DatabaseConnectionHandler this._processData = Some(m.asInstanceOf[ProcessData]) } case Message.BindComplete => { - log.debug("Finished binding statement - {}", this.currentPreparedStatement) } case Message.Authentication => { this.onAuthenticationResponse(ctx.getChannel, m.asInstanceOf[AuthenticationMessage]) @@ -183,7 +185,6 @@ class DatabaseConnectionHandler this.onCommandComplete(m.asInstanceOf[CommandCompleteMessage]) } case Message.CloseComplete => { - log.debug("Successfully closed portal for [{}]", this.currentPreparedStatement) } case Message.DataRow => { this.onDataRow(m.asInstanceOf[DataRowMessage]) @@ -196,16 +197,13 @@ class DatabaseConnectionHandler this.setErrorOnFutures(exception) } case Message.NoData => { - log.debug("Statement response does not contain any data") } case Message.Notice => { - log.info("notice -> {}", m.asInstanceOf[NoticeMessage]) } case Message.ParameterStatus => { this.onParameterStatus(m.asInstanceOf[ParameterStatusMessage]) } case Message.ParseComplete => { - log.debug("Finished parsing statement") } case Message.ReadyForQuery => { this.onReadyForQuery @@ -220,7 +218,7 @@ class DatabaseConnectionHandler } case _ => { - log.error("Unknown message type {}", e.getMessage) + log.error("[{}] - Unknown message type {}", this.currentCount, e.getMessage) throw new IllegalArgumentException("Unknown message type - %s".format(e.getMessage())) } @@ -231,15 +229,17 @@ class DatabaseConnectionHandler override def sendQuery(query: String): Future[QueryResult] = { validateQuery(query) this.readyForQuery = false - this.queryPromise = Option(Promise[QueryResult]()) + + val promise = Promise[QueryResult]() + this.setQueryPromise(promise) + this.currentChannel.write(new QueryMessage(query)) - this.queryPromise.get.future + + promise.future } override def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { validateQuery(query) - this.readyForQuery = false - this.queryPromise = Some(Promise[QueryResult]()) var paramsCount = 0 @@ -258,52 +258,55 @@ class DatabaseConnectionHandler query } - if ( paramsCount != values.length ) { + if (paramsCount != values.length) { throw new InsufficientParametersException(paramsCount, values) } + this.readyForQuery = false + val promise = Promise[QueryResult]() + this.setQueryPromise(promise) this.currentPreparedStatement = Some(realQuery) if (!this.isParsed(realQuery)) { - log.debug("MutableQuery is not parsed yet -> {}", realQuery) this.currentChannel.write(new PreparedStatementOpeningMessage(realQuery, values, this.encoderRegistry)) } else { this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(realQuery), configuration.charset, this.decoderRegistry)) this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values, this.encoderRegistry)) } - this.queryPromise.get.future + promise.future } override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { this.setErrorOnFutures(e.getCause) } + def hasRecentError: Boolean = this.recentError + private def setErrorOnFutures(e: Throwable) { + this.recentError = true - log.error("Error on connection", e) + log.error("[%s] - Error on connection".format(currentCount), e) if (!this.connectionFuture.isCompleted) { this.connectionFuture.failure(e) this.disconnect - } else { - if (this.queryPromise.isDefined) { - log.error("Setting error on future {}", this.queryPromise.get) - this.queryPromise.get.failure(e) - this.queryPromise = None - this.currentPreparedStatement = None - } } + this.failQueryPromise(e) + + this.currentPreparedStatement = None } override def channelDisconnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { - log.warn("Connection disconnected - {}", ctx.getChannel.getRemoteAddress) + log.info("[{}] - Connection disconnected - {}", this.currentCount, ctx.getChannel.getRemoteAddress) this.connected = false } private def onReadyForQuery { + this.recentError = false this.readyForQuery = true + this.clearQueryPromise if (!this.connectionFuture.isCompleted) { this.connectionFuture.success(this.parameterStatus.toMap) @@ -311,7 +314,7 @@ class DatabaseConnectionHandler } private def onError(m: ErrorMessage) { - log.error("Error with message -> {}", m) + log.error("[%s] - Error with message -> {}".format(currentCount), m) val error = new GenericDatabaseException(m) error.fillInStackTrace() @@ -320,20 +323,8 @@ class DatabaseConnectionHandler } private def onCommandComplete(m: CommandCompleteMessage) { - - if (this.queryPromise.isDefined) { - - val queryResult = if (this.currentQuery.isDefined) { - new QueryResult(m.rowsAffected, m.statusMessage, Some(this.currentQuery.get)) - } else { - new QueryResult(m.rowsAffected, m.statusMessage, None) - } - - this.queryPromise.get.success(queryResult) - this.queryPromise = None - this.currentPreparedStatement = None - - } + this.currentPreparedStatement = None + this.succeedQueryPromise(new QueryResult(m.rowsAffected, m.statusMessage, this.currentQuery)) } private def onParameterStatus(m: ParameterStatusMessage) { @@ -345,11 +336,8 @@ class DatabaseConnectionHandler } private def onRowDescription(m: RowDescriptionMessage) { - log.debug("received query description {}", m) this.currentQuery = Option(new MutableResultSet(m.columnDatas, configuration.charset, this.decoderRegistry)) - log.debug("Current prepared statement is {}", this.currentPreparedStatement) - if (this.currentPreparedStatement.isDefined) { this.parsedStatements.put(this.currentPreparedStatement.get, m.columnDatas) } @@ -363,7 +351,7 @@ class DatabaseConnectionHandler message match { case m: AuthenticationOkMessage => { - log.debug("Successfully logged in to database") + log.debug("[{}] - Successfully logged in to database", this.currentCount) this.authenticated = true } case m: AuthenticationChallengeCleartextMessage => { @@ -400,10 +388,50 @@ class DatabaseConnectionHandler } } - private def validateQuery( query : String ) { - if ( query == null || query.isEmpty ) { + private def validateQuery(query: String) { + if (this.queryPromise.isDefined) { + log.error("[{}] - Can't run query because there is one query pending already", this.currentCount) + throw new ConnectionStillRunningQueryException( + this.currentCount, + this.readyForQuery + ) + } + + if (query == null || query.isEmpty) { throw new QueryMustNotBeNullOrEmptyException(query) } } + private def queryPromise: Option[Promise[QueryResult]] = queryPromiseReference.get() + + private def setQueryPromise(promise: Promise[QueryResult]) { + this.queryPromiseReference.set(Some(promise)) + } + + private def clearQueryPromise { + this.queryPromiseReference.set(None) + } + + private def failQueryPromise(t: Throwable) { + val promise = this.queryPromise + + if (promise.isDefined) { + this.clearQueryPromise + log.error("[{}] - Setting error on future {}", this.currentCount, promise) + promise.get.failure(t) + } + } + + private def succeedQueryPromise(result: QueryResult) { + val promise = this.queryPromise + + if (promise.isDefined) { + this.clearQueryPromise + promise.get.success(result) + } + } + + override def toString: String = { + "%s{counter=%s}".format(this.getClass.getSimpleName, this.currentCount) + } } \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala index 0aaa2837..79faecc7 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala @@ -23,12 +23,14 @@ import messages.backend.Message import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{ChannelHandlerContext, Channel} import org.jboss.netty.handler.codec.frame.FrameDecoder +import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} object MessageDecoder { val log = Log.get[MessageDecoder] + val DefaultMaximumSize = 16777216 } -class MessageDecoder(charset: Charset) extends FrameDecoder { +class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder.DefaultMaximumSize) extends FrameDecoder { private val parser = new MessageParsersRegistry(charset) @@ -38,10 +40,18 @@ class MessageDecoder(charset: Charset) extends FrameDecoder { b.markReaderIndex() - val code = b.readByte().asInstanceOf[Char] + val code = b.readByte() val lengthWithSelf = b.readInt() val length = lengthWithSelf - 4 + if ( length < 0 ) { + throw new NegativeMessageSizeException(code, length) + } + + if ( length > maximumMessageSize ) { + throw new MessageTooLongException(code, length, maximumMessageSize) + } + if (b.readableBytes() >= length) { code match { case Message.Authentication => { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index 42316b94..0d63fccb 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.encoders._ import com.github.mauricio.async.db.postgresql.exceptions.EncoderNotAvailableException +import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend._ import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset @@ -32,26 +33,26 @@ object MessageEncoder { class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) extends OneToOneEncoder { - - val encoders: Map[Class[_], Encoder] = Map( - classOf[CloseMessage] -> CloseMessageEncoder, - classOf[PreparedStatementExecuteMessage] -> new ExecutePreparedStatementEncoder(charset, encoderRegistry), - classOf[PreparedStatementOpeningMessage] -> new PreparedStatementOpeningEncoder(charset, encoderRegistry), - classOf[StartupMessage] -> new StartupMessageEncoder(charset), - classOf[QueryMessage] -> new QueryMessageEncoder(charset), - classOf[CredentialMessage] -> new CredentialEncoder(charset) - ) + private val executeEncoder = new ExecutePreparedStatementEncoder(charset, encoderRegistry) + private val openEncoder = new PreparedStatementOpeningEncoder(charset, encoderRegistry) + private val startupEncoder = new StartupMessageEncoder(charset) + private val queryEncoder = new QueryMessageEncoder(charset) + private val credentialEncoder = new CredentialEncoder(charset) override def encode(ctx: ChannelHandlerContext, channel: Channel, msg: AnyRef): ChannelBuffer = { val buffer = msg match { case message: FrontendMessage => { - val option = this.encoders.get(message.getClass) - if (option.isDefined) { - option.get.encode(message) - } else { - throw new EncoderNotAvailableException(message) + val encoder = message.kind match { + case Message.Close => CloseMessageEncoder + case Message.Execute => this.executeEncoder + case Message.Parse => this.openEncoder + case Message.Startup => this.startupEncoder + case Message.Query => this.queryEncoder + case Message.PasswordMessage => this.credentialEncoder + case _ => throw new EncoderNotAvailableException(message) } + encoder.encode(message) } case _ => { throw new IllegalArgumentException("Can not encode message %s".format(msg)) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala new file mode 100644 index 00000000..435afd1c --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala @@ -0,0 +1,23 @@ +/* + * 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.exceptions + +class ConnectionStillRunningQueryException( connectionCount : Long, readyForQuery : Boolean ) + extends DatabaseException ( "[%s] - There is a query still being run here - readyForQuery -> %s".format( + connectionCount, + readyForQuery + )) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala new file mode 100644 index 00000000..b831ec0a --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala @@ -0,0 +1,20 @@ +/* + * 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.exceptions + +class MessageTooLongException( code : Byte, length : Int, limit : Int ) + extends DatabaseException("Message of type %d has size %d, higher than the limit %d".format(code, length, limit)) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala new file mode 100644 index 00000000..26169f2c --- /dev/null +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala @@ -0,0 +1,20 @@ +/* + * 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.exceptions + +class NegativeMessageSizeException( code : Byte, size : Int ) + extends DatabaseException( "Message of type %d had negative size %s".format(code, size) ) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala index 453af50f..ab180152 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala @@ -16,5 +16,5 @@ package com.github.mauricio.async.db.postgresql.exceptions -class ParserNotAvailableException(t: Char) +class ParserNotAvailableException(t: Byte) extends DatabaseException("There is no parser available for message type '%s'".format(t)) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala index 0eece8dd..90cd8595 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala @@ -16,8 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object BindComplete { - val Instance = new BindComplete() -} - -class BindComplete extends Message(Message.BindComplete) +object BindComplete extends Message(Message.BindComplete) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala index 0c5af081..cb1bf6a4 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala @@ -16,8 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object CloseComplete { - val Instance = new CloseComplete() -} - -class CloseComplete extends Message(Message.CloseComplete) \ No newline at end of file +object CloseComplete extends Message(Message.CloseComplete) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala index aec73521..b1eeed29 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala @@ -50,8 +50,8 @@ object InformationMessage { } -abstract class InformationMessage(statusCode: Char, val fields: Map[Char, String]) - extends Message(statusCode) { +abstract class InformationMessage(messageType: Byte, val fields: Map[Char, String]) + extends Message(messageType) { def message: String = this.fields('M') diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala index f4473075..bfe26176 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala @@ -17,32 +17,32 @@ package com.github.mauricio.async.db.postgresql.messages.backend object Message { - val Authentication = 'R' - val BackendKeyData = 'K' - val Bind = 'B' - val BindComplete = '2' - val CommandComplete = 'C' - val Close = 'X' - val CloseStatementOrPortal = 'C' - val CloseComplete = '3' - val DataRow = 'D' - val Describe = 'D' - val Error = 'E' - val Execute = 'E' - val EmptyQueryString = 'I' - val NoData = 'n' - val Notice = 'N' - val Notification = 'A' - val ParameterStatus = 'S' - val Parse = 'P' - val ParseComplete = '1' - val PasswordMessage = 'p' - val PortalSuspended = 's' - val Query = 'Q' - val RowDescription = 'T' - val ReadyForQuery = 'Z' - val Startup: Char = 0 - val Sync = 'S' + val Authentication: Byte = 'R' + val BackendKeyData: Byte = 'K' + val Bind: Byte = 'B' + val BindComplete: Byte = '2' + val CommandComplete: Byte = 'C' + val Close: Byte = 'X' + val CloseStatementOrPortal: Byte = 'C' + val CloseComplete: Byte = '3' + val DataRow: Byte = 'D' + val Describe: Byte = 'D' + val Error: Byte = 'E' + val Execute: Byte = 'E' + val EmptyQueryString: Byte = 'I' + val NoData: Byte = 'n' + val Notice: Byte = 'N' + val Notification: Byte = 'A' + val ParameterStatus: Byte = 'S' + val Parse: Byte = 'P' + val ParseComplete: Byte = '1' + val PasswordMessage: Byte = 'p' + val PortalSuspended: Byte = 's' + val Query: Byte = 'Q' + val RowDescription: Byte = 'T' + val ReadyForQuery: Byte = 'Z' + val Startup: Byte = 0 + val Sync: Byte = 'S' } -class Message(val name: Char) \ No newline at end of file +class Message(val name: Byte) \ No newline at end of file diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala index 3e8441c2..1100765a 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala @@ -16,8 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object ParseComplete { - val Instance = new ParseComplete() -} - -class ParseComplete extends Message(Message.ParseComplete) +object ParseComplete extends Message(Message.ParseComplete) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala index e48ad72b..fd85007d 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala @@ -18,8 +18,4 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.postgresql.messages.backend.Message -object CloseMessage { - val Instance = new CloseMessage() -} - -class CloseMessage extends FrontendMessage(Message.Close) +object CloseMessage extends FrontendMessage(Message.Close) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala index 5230ddd8..b80d3137 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -class FrontendMessage(val kind: Char) +class FrontendMessage(val kind: Byte) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index f8c4b5df..4a106f4e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry class PreparedStatementMessage( - kind: Char, + kind: Byte, val query: String, val values: Seq[Any], encoderRegistry : ColumnEncoderRegistry diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala index 2797a709..ff55dfab 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -23,35 +23,33 @@ import org.jboss.netty.buffer.ChannelBuffer class MessageParsersRegistry(charset: Charset) { - private val parsers = Map( - Message.Authentication -> AuthenticationStartupParser, - Message.BackendKeyData -> BackendKeyDataParser, - Message.BindComplete -> new ReturningMessageParser(BindComplete.Instance), - Message.CloseComplete -> new ReturningMessageParser(CloseComplete.Instance), - Message.CommandComplete -> new CommandCompleteParser(charset), - Message.DataRow -> DataRowParser, - Message.Error -> new ErrorParser(charset), - Message.EmptyQueryString -> new ReturningMessageParser(EmptyQueryString), - Message.NoData -> new ReturningMessageParser(NoData), - Message.Notice -> new NoticeParser(charset), - Message.ParameterStatus -> new ParameterStatusParser(charset), - Message.ParseComplete -> new ReturningMessageParser(ParseComplete.Instance), - Message.RowDescription -> new RowDescriptionParser(charset), - Message.ReadyForQuery -> ReadyForQueryParser - ) - - private def parserFor(t: Char): MessageParser = { - val option = this.parsers.get(t) - - if (option.isDefined) { - option.get - } else { - throw new ParserNotAvailableException(t) + private val commandCompleteParser = new CommandCompleteParser(charset) + private val errorParser = new ErrorParser(charset) + private val noticeParser = new NoticeParser(charset) + private val parameterStatusParser = new ParameterStatusParser(charset) + private val rowDescriptionParser = new RowDescriptionParser(charset) + + private def parserFor(t: Byte): MessageParser = { + t match { + case Message.Authentication => AuthenticationStartupParser + case Message.BackendKeyData => BackendKeyDataParser + case Message.BindComplete => ReturningMessageParser.BindCompleteMessageParser + case Message.CloseComplete => ReturningMessageParser.CloseCompleteMessageParser + case Message.CommandComplete => this.commandCompleteParser + case Message.DataRow => DataRowParser + case Message.Error => this.errorParser + case Message.EmptyQueryString => ReturningMessageParser.EmptyQueryStringMessageParser + case Message.NoData => ReturningMessageParser.NoDataMessageParser + case Message.Notice => this.noticeParser + case Message.ParameterStatus => this.parameterStatusParser + case Message.ParseComplete => ReturningMessageParser.ParseCompleteMessageParser + case Message.RowDescription => this.rowDescriptionParser + case Message.ReadyForQuery => ReadyForQueryParser + case _ => throw new ParserNotAvailableException(t) } - } - def parse(t: Char, b: ChannelBuffer): Message = { + def parse(t: Byte, b: ChannelBuffer): Message = { this.parserFor(t).parseMessage(b) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala index 8f4fec18..466355c7 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala @@ -16,11 +16,21 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend._ import org.jboss.netty.buffer.ChannelBuffer +object ReturningMessageParser { + + val BindCompleteMessageParser = new ReturningMessageParser(BindComplete) + val CloseCompleteMessageParser = new ReturningMessageParser(CloseComplete) + val EmptyQueryStringMessageParser = new ReturningMessageParser(EmptyQueryString) + val NoDataMessageParser = new ReturningMessageParser(NoData) + val ParseCompleteMessageParser = new ReturningMessageParser(ParseComplete) + +} + class ReturningMessageParser(val message: Message) extends MessageParser { - def parseMessage(buffer: ChannelBuffer): Message = { - this.message - } + + def parseMessage(buffer: ChannelBuffer): Message = this.message + } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index a982afe8..636a4274 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -24,6 +24,7 @@ import scala.concurrent.Await import com.github.mauricio.async.db.util.Log import scala.util.{Success, Failure, Try} import java.nio.channels.ClosedChannelException +import com.github.mauricio.async.db.pool.ObjectFactory object ConnectionObjectFactory { val log = Log.get[ConnectionObjectFactory] @@ -61,7 +62,7 @@ class ConnectionObjectFactory( val configuration : Configuration ) extends Objec def validate( item : DatabaseConnectionHandler ) : Try[DatabaseConnectionHandler] = { Try { - if ( item.isConnected ) { + if ( item.isConnected && !item.hasRecentError ) { item } else { throw new ClosedChannelException() diff --git a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala index 44ba2c83..755879d7 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala +++ b/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -20,8 +20,8 @@ import java.util.concurrent.{ExecutorService, Executors} import scala.concurrent.ExecutionContext object ExecutorServiceUtils { - implicit val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) - implicit val CachedExecutionContext = ExecutionContext.fromExecutor( CachedThreadPool ) + implicit val FixedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) + implicit val FixedExecutionContext = ExecutionContext.fromExecutor( FixedThreadPool ) def newFixedPool( count : Int ) : ExecutorService = { Executors.newFixedThreadPool( count, DaemonThreadsFactory ) diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index a2804fc6..30bf6dbe 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -2,11 +2,19 @@ - [%level][%thread][%file:%line] %msg%n + [%level][%thread][%d] %msg%ex%n + + + + + target/postgresql-async-tests.log + + [%level][%thread][%d] %msg%ex%n + \ No newline at end of file diff --git a/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala b/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala index 05d3e40d..e44c797d 100644 --- a/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala +++ b/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala @@ -17,11 +17,12 @@ package com.github.mauricio.async.db.examples import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler -import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext +import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext import com.github.mauricio.async.db.util.URLParser import com.github.mauricio.async.db.{RowData, QueryResult, Connection} import scala.concurrent.duration._ import scala.concurrent.{Await, Future} +import scala.language.postfixOps object BasicExample { diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 07ae39d7..a8665061 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -25,7 +25,6 @@ import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ -import org.joda.time.LocalDate class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index 14993cb3..396db5ef 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -17,13 +17,20 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.{Connection, Configuration} -import java.util.concurrent.TimeUnit +import java.util.concurrent.{TimeoutException, TimeUnit} import scala.Some import scala.concurrent.duration._ import scala.concurrent.{Future, Await} +import com.github.mauricio.async.db.util.Log + +object DatabaseTestHelper { + val log = Log.get[DatabaseTestHelper] +} trait DatabaseTestHelper { + import DatabaseTestHelper.log + def databaseName = Some("netty_driver_test") def databasePort = 5433 @@ -45,29 +52,44 @@ trait DatabaseTestHelper { Await.result(handler.connect, Duration(5, SECONDS)) fn(handler) } finally { - handler.disconnect + handleTimeout(handler, handler.disconnect) } } def executeDdl(handler: Connection, data: String, count: Int = 0) = { - val rows = Await.result(handler.sendQuery(data), Duration(5, SECONDS)).rowsAffected + val rows = handleTimeout(handler, { + Await.result(handler.sendQuery(data), Duration(5, SECONDS)).rowsAffected + }) if (rows != count) { throw new IllegalStateException("We expected %s rows but there were %s".format(count, rows)) } + } + private def handleTimeout[R]( handler : Connection, fn : => R ) = { + try { + fn + } catch { + case e : TimeoutException => { + throw new IllegalStateException("Timeout executing call from handler -> %s".format( handler)) + } + } } def executeQuery(handler: Connection, data: String) = { - Await.result(handler.sendQuery(data), Duration(5, SECONDS)) + handleTimeout( handler, { + Await.result(handler.sendQuery(data), Duration(5, SECONDS)) + } ) } def executePreparedStatement( handler: Connection, statement: String, values: Array[Any] = Array.empty[Any]) = { - Await.result(handler.sendPreparedStatement(statement, values), Duration(5, SECONDS)) + handleTimeout( handler, { + Await.result(handler.sendPreparedStatement(statement, values), Duration(5, SECONDS)) + } ) } def await[T](future: Future[T]): T = { diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index 26b57193..ef653cad 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -17,10 +17,11 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.postgresql.MessageDecoder -import com.github.mauricio.async.db.postgresql.messages.backend.ErrorMessage +import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ErrorMessage} import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification +import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} class MessageDecoderSpec extends Specification { @@ -69,7 +70,23 @@ class MessageDecoderSpec extends Specification { result.message === text buffer.readerIndex() === (textBytes.length + 4 + 1 + 1 + 1) + } + + "should raise an exception if the length is negative" in { + val buffer = ChannelBuffers.dynamicBuffer() + buffer.writeByte( Message.Close ) + buffer.writeInt( 2 ) + + this.decoder.decode(null, null, buffer) must throwA[NegativeMessageSizeException] + } + + "should raise an exception if the length is too big" in { + + val buffer = ChannelBuffers.dynamicBuffer() + buffer.writeByte( Message.Close ) + buffer.writeInt( MessageDecoder.DefaultMaximumSize + 10 ) + this.decoder.decode(null, null, buffer) must throwA[MessageTooLongException] } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala index c4505e5b..5a757ac7 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} import org.specs2.mutable.Specification +import com.github.mauricio.async.db.pool.{ConnectionPool, PoolConfiguration} class ConnectionPoolSpec extends Specification with DatabaseTestHelper { @@ -56,7 +57,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { try { fn(pool) } finally { - pool.disconnect + await(pool.disconnect) } } diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala index 1848fb6e..4b5e36e8 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala +++ b/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala @@ -23,6 +23,7 @@ import org.specs2.mutable.Specification import scala.concurrent.Await import scala.concurrent.duration._ import scala.language.postfixOps +import com.github.mauricio.async.db.pool.{SingleThreadedAsyncObjectPool, PoolExhaustedException, PoolConfiguration} class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { @@ -143,7 +144,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH try { fn(pool) } finally { - pool.close + await(pool.close) } } From 12c801249561951b18990419e6d9e3051c926722 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 1 May 2013 00:16:28 -0300 Subject: [PATCH 045/357] Starting 0.1.2 cycle --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b47be971..f7537219 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import JacocoPlugin._ name := "postgresql-async" -version := "0.1.0" +version := "0.1.2-SNAPSHOT" organization := "com.github.mauricio" From 90ec4061cdaab783e013c244a17df73eda950185 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 1 May 2013 00:19:01 -0300 Subject: [PATCH 046/357] fixing project url for pom generation --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f7537219..096500f0 100644 --- a/build.sbt +++ b/build.sbt @@ -39,7 +39,7 @@ publishTo <<= version { v: String => } pomExtra := ( - http://your.project.url + https://github.com/mauricio/postgresql-async APACHE-2.0 From 719c2f213b8f5841b31264076ba1ba51d0bea3ce Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 1 May 2013 10:26:49 -0300 Subject: [PATCH 047/357] Optimizing switches --- .../DatabaseConnectionHandler.scala | 3 +- .../async/db/postgresql/MessageEncoder.scala | 3 +- .../db/postgresql/column/ColumnTypes.scala | 74 +++++++++---------- .../column/DefaultColumnDecoderRegistry.scala | 3 +- .../postgresql/messages/backend/Message.scala | 52 ++++++------- 5 files changed, 69 insertions(+), 66 deletions(-) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index cd4567ca..820ea567 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -33,6 +33,7 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ +import scala.annotation.switch object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -172,7 +173,7 @@ class DatabaseConnectionHandler case m: Message => { - m.name match { + (m.name : @switch) match { case Message.BackendKeyData => { this._processData = Some(m.asInstanceOf[ProcessData]) } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index 0d63fccb..eac31fa7 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -26,6 +26,7 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder +import scala.annotation.switch object MessageEncoder { val log = Log.get[MessageEncoder] @@ -43,7 +44,7 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e val buffer = msg match { case message: FrontendMessage => { - val encoder = message.kind match { + val encoder = (message.kind : @switch) match { case Message.Close => CloseMessageEncoder case Message.Execute => this.executeEncoder case Message.Parse => this.openEncoder diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala index ebba8332..2478ca56 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala @@ -18,46 +18,46 @@ package com.github.mauricio.async.db.postgresql.column object ColumnTypes { - val Bigserial = 20 - val Char = 18 - val CharArray = 1002 - val Smallint = 21 - val SmallintArray = 1005 - val Integer = 23 - val IntegerArray = 1007 - val Numeric = 1700 + final val Bigserial = 20 + final val Char = 18 + final val CharArray = 1002 + final val Smallint = 21 + final val SmallintArray = 1005 + final val Integer = 23 + final val IntegerArray = 1007 + final val Numeric = 1700 // Decimal is the same as Numeric on PostgreSQL - val NumericArray = 1231 - val Real = 700 - val RealArray = 1021 - val Double = 701 - val DoubleArray = 1022 - val Serial = 23 - val Bpchar = 1042 - val BpcharArray = 1014 - val Varchar = 1043 + final val NumericArray = 1231 + final val Real = 700 + final val RealArray = 1021 + final val Double = 701 + final val DoubleArray = 1022 + final val Serial = 23 + final val Bpchar = 1042 + final val BpcharArray = 1014 + final val Varchar = 1043 // Char is the same as Varchar on PostgreSQL - val VarcharArray = 1015 - val Text = 25 - val TextArray = 1009 - val Timestamp = 1114 - val TimestampArray = 1115 - val TimestampWithTimezone = 1184 - val TimestampWithTimezoneArray = 1185 - val Date = 1082 - val DateArray = 1182 - val Time = 1083 - val TimeArray = 1183 - val TimeWithTimezone = 1266 - val TimeWithTimezoneArray = 1270 - val Boolean = 16 - val BooleanArray = 1000 + final val VarcharArray = 1015 + final val Text = 25 + final val TextArray = 1009 + final val Timestamp = 1114 + final val TimestampArray = 1115 + final val TimestampWithTimezone = 1184 + final val TimestampWithTimezoneArray = 1185 + final val Date = 1082 + final val DateArray = 1182 + final val Time = 1083 + final val TimeArray = 1183 + final val TimeWithTimezone = 1266 + final val TimeWithTimezoneArray = 1270 + final val Boolean = 16 + final val BooleanArray = 1000 - val OIDArray = 1028 - val MoneyArray = 791 - val NameArray = 1003 - val UUIDArray = 2951 - val XMLArray = 143 + final val OIDArray = 1028 + final val MoneyArray = 791 + final val NameArray = 1003 + final val UUIDArray = 2951 + final val XMLArray = 143 } diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala index f1d70abd..5c3d8d42 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.postgresql.column.ColumnTypes._ +import scala.annotation.switch object DefaultColumnDecoderRegistry { val Instance = new DefaultColumnDecoderRegistry() @@ -27,7 +28,7 @@ class DefaultColumnDecoderRegistry extends ColumnDecoderRegistry { def decode(kind: Int, value: String) : Any = decoderFor(kind).decode(value) def decoderFor(kind: Int): ColumnDecoder = { - kind match { + (kind : @switch) match { case Boolean => BooleanEncoderDecoder case BooleanArray => new ArrayDecoder(BooleanEncoderDecoder) diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala index bfe26176..d5cb5973 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala +++ b/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala @@ -17,32 +17,32 @@ package com.github.mauricio.async.db.postgresql.messages.backend object Message { - val Authentication: Byte = 'R' - val BackendKeyData: Byte = 'K' - val Bind: Byte = 'B' - val BindComplete: Byte = '2' - val CommandComplete: Byte = 'C' - val Close: Byte = 'X' - val CloseStatementOrPortal: Byte = 'C' - val CloseComplete: Byte = '3' - val DataRow: Byte = 'D' - val Describe: Byte = 'D' - val Error: Byte = 'E' - val Execute: Byte = 'E' - val EmptyQueryString: Byte = 'I' - val NoData: Byte = 'n' - val Notice: Byte = 'N' - val Notification: Byte = 'A' - val ParameterStatus: Byte = 'S' - val Parse: Byte = 'P' - val ParseComplete: Byte = '1' - val PasswordMessage: Byte = 'p' - val PortalSuspended: Byte = 's' - val Query: Byte = 'Q' - val RowDescription: Byte = 'T' - val ReadyForQuery: Byte = 'Z' - val Startup: Byte = 0 - val Sync: Byte = 'S' + final val Authentication = 'R' + final val BackendKeyData = 'K' + final val Bind = 'B' + final val BindComplete = '2' + final val CommandComplete = 'C' + final val Close = 'X' + final val CloseStatementOrPortal = 'C' + final val CloseComplete = '3' + final val DataRow = 'D' + final val Describe = 'D' + final val Error = 'E' + final val Execute = 'E' + final val EmptyQueryString = 'I' + final val NoData = 'n' + final val Notice = 'N' + final val Notification = 'A' + final val ParameterStatus = 'S' + final val Parse = 'P' + final val ParseComplete = '1' + final val PasswordMessage = 'p' + final val PortalSuspended = 's' + final val Query = 'Q' + final val RowDescription = 'T' + final val ReadyForQuery = 'Z' + final val Startup = '0' + final val Sync = 'S' } class Message(val name: Byte) \ No newline at end of file From b6d9560752118057fd95aaff78fb3bdc2e662a18 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 1 May 2013 10:57:27 -0300 Subject: [PATCH 048/357] Updating version in documentation --- README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index cf626a46..75bfd4fb 100644 --- a/README.markdown +++ b/README.markdown @@ -9,7 +9,7 @@ to PostgreSQL. Include at your SBT project with: ```scala -"com.github.mauricio" %% "postgresql-async" % "0.1.0" +"com.github.mauricio" %% "postgresql-async" % "0.1.1" ``` Or Maven: @@ -18,7 +18,7 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.1.0 + 0.1.1 ``` @@ -115,7 +115,7 @@ it [here](http://mauricio.github.io/2013/04/29/async-database-access-with-postgr In short, what you would usually do is: ```scala import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler -import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext +import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext import com.github.mauricio.async.db.util.URLParser import com.github.mauricio.async.db.{RowData, QueryResult, Connection} import scala.concurrent.duration._ From ce905860eb4e8620aa3589dbce6d987d72fc024b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 1 May 2013 11:09:00 -0300 Subject: [PATCH 049/357] Creating changelog --- CHANGELOG.md | 16 ++++++++++++++++ README.markdown | 1 + 2 files changed, 17 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ae627ff8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## 0.1.2 (unreleased) + +* Optimize match/cases to `@switch` (#10) + +## 0.1.1 - 2013-04-30 + +* Query promises fulfilled before cleaning up the query promise cause the futures to either hang forever or fail with a "query already running" message (#2) +* Optimize MessageEncoder to use a match instead of a map (#3) +* MessageDecoder should validate sizes and correctly handle negative or too large messages (#4) +* Move generic pool classes to the com.github.mauricio.async.db package (#9) + +## 0.1.0 - 2013-04-29 + +* First public release \ No newline at end of file diff --git a/README.markdown b/README.markdown index 75bfd4fb..7fa04c22 100644 --- a/README.markdown +++ b/README.markdown @@ -59,6 +59,7 @@ This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql - checkout the source code - find bugs, find places where performance can be improved - check the **What is missing** piece +- check the [issues page](https://github.com/mauricio/postgresql-async/issues) for bugs or new features - send a pull request with specs ## Main public interface From 8b7b3d6889a792495324da2ef20da37072949531 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 1 May 2013 23:17:19 -0300 Subject: [PATCH 050/357] Setting up multimodule build --- .gitignore | 2 + README.markdown | 4 ++ build.sbt | 27 +++------ .../mauricio/async/db/Configuration.scala | 29 ++------- .../github/mauricio/async/db/Connection.scala | 0 .../mauricio/async/db/QueryResult.scala | 0 .../github/mauricio/async/db/ResultSet.scala | 0 .../github/mauricio/async/db/RowData.scala | 0 .../async/db/pool/AsyncObjectPool.scala | 0 .../async/db/pool/ConnectionPool.scala | 0 .../async/db/pool/ObjectFactory.scala | 0 .../pool/PoolAlreadyTerminatedException.scala | 0 .../async/db/pool/PoolConfiguration.scala | 0 .../db/pool/PoolExhaustedException.scala | 0 .../pool/SingleThreadedAsyncObjectPool.scala | 0 .../async/db/util/DaemonThreadsFactory.scala | 0 .../async/db/util/ExecutorServiceUtils.scala | 0 .../github/mauricio/async/db/util/Log.scala | 0 .../mauricio/async/db/util/Worker.scala | 0 .../async/db/postgresql/util/ParseURL.java | 0 .../postgresql/util/PostgreSQLMD5Digest.java | 0 .../DatabaseConnectionHandler.scala | 2 +- .../async/db/postgresql/MessageDecoder.scala | 0 .../async/db/postgresql/MessageEncoder.scala | 0 .../db/postgresql/column/ArrayDecoder.scala | 2 +- .../column/BigDecimalEncoderDecoder.scala | 0 .../column/BooleanEncoderDecoder.scala | 0 .../column/CharEncoderDecoder.scala | 0 .../db/postgresql/column/ColumnDecoder.scala | 0 .../column/ColumnDecoderRegistry.scala | 0 .../db/postgresql/column/ColumnEncoder.scala | 0 .../column/ColumnEncoderDecoder.scala | 0 .../column/ColumnEncoderRegistry.scala | 0 .../db/postgresql/column/ColumnTypes.scala | 0 .../column/DateEncoderDecoder.scala | 0 .../column/DefaultColumnDecoderRegistry.scala | 0 .../column/DefaultColumnEncoderRegistry.scala | 0 .../column/DoubleEncoderDecoder.scala | 0 .../column/FloatEncoderDecoder.scala | 0 .../column/IntegerEncoderDecoder.scala | 0 .../column/LongEncoderDecoder.scala | 0 .../column/ShortEncoderDecoder.scala | 0 .../column/StringEncoderDecoder.scala | 0 .../column/TimeEncoderDecoder.scala | 0 .../TimeWithTimezoneEncoderDecoder.scala | 0 .../column/TimestampEncoderDecoder.scala | 0 .../TimestampWithTimezoneEncoderDecoder.scala | 0 .../encoders/CloseMessageEncoder.scala | 0 .../encoders/CredentialEncoder.scala | 3 +- .../db/postgresql/encoders/Encoder.scala | 0 .../ExecutePreparedStatementEncoder.scala | 2 +- .../PreparedStatementOpeningEncoder.scala | 2 +- .../encoders/QueryMessageEncoder.scala | 2 +- .../encoders/StartupMessageEncoder.scala | 2 +- .../ColumnDecoderNotFoundException.scala | 0 ...ConnectionStillRunningQueryException.scala | 0 .../exceptions/DatabaseException.scala | 0 .../DateEncoderNotAvailableException.scala | 0 .../EncoderNotAvailableException.scala | 0 .../exceptions/GenericDatabaseException.scala | 0 .../InsufficientParametersException.scala | 0 .../exceptions/InvalidArrayException.scala | 0 .../exceptions/MessageTooLongException.scala | 0 ...issingCredentialInformationException.scala | 0 .../NegativeMessageSizeException.scala | 0 .../exceptions/NotConnectedException.scala | 0 .../ParserNotAvailableException.scala | 0 .../QueryMustNotBeNullOrEmptyException.scala | 0 ...pportedAuthenticationMethodException.scala | 0 .../db/postgresql}/general/ArrayRowData.scala | 2 +- .../general/MutableResultSet.scala | 2 +- ...henticationChallengeCleartextMessage.scala | 0 .../backend/AuthenticationChallengeMD5.scala | 0 .../AuthenticationChallengeMessage.scala | 0 .../backend/AuthenticationMessage.scala | 0 .../backend/AuthenticationOkMessage.scala | 0 .../backend/AuthenticationResponseType.scala | 0 .../messages/backend/BindComplete.scala | 0 .../messages/backend/CloseComplete.scala | 0 .../messages/backend/ColumnData.scala | 0 .../backend/CommandCompleteMessage.scala | 0 .../messages/backend/DataRowMessage.scala | 0 .../messages/backend/EmptyQueryString.scala | 0 .../messages/backend/ErrorMessage.scala | 0 .../messages/backend/InformationMessage.scala | 0 .../postgresql/messages/backend/Message.scala | 0 .../postgresql/messages/backend/NoData.scala | 0 .../messages/backend/NoticeMessage.scala | 0 .../backend/ParameterStatusMessage.scala | 0 .../messages/backend/ParseComplete.scala | 0 .../messages/backend/ProcessData.scala | 0 .../backend/ReadyForQueryMessage.scala | 0 .../backend/RowDescriptionMessage.scala | 0 .../messages/frontend/CloseMessage.scala | 0 .../messages/frontend/CredentialMessage.scala | 0 .../messages/frontend/FrontendMessage.scala | 0 .../PreparedStatementExecuteMessage.scala | 0 .../frontend/PreparedStatementMessage.scala | 0 .../PreparedStatementOpeningMessage.scala | 0 .../messages/frontend/QueryMessage.scala | 0 .../messages/frontend/StartupMessage.scala | 0 .../parsers/AuthenticationStartupParser.scala | 0 .../parsers/BackendKeyDataParser.scala | 0 .../parsers/CommandCompleteParser.scala | 2 +- .../db/postgresql/parsers/DataRowParser.scala | 0 .../db/postgresql/parsers/ErrorParser.scala | 0 .../parsers/InformationParser.scala | 2 +- .../db/postgresql/parsers/MessageParser.scala | 0 .../parsers/MessageParsersRegistry.scala | 0 .../db/postgresql/parsers/NoticeParser.scala | 0 .../parsers/ParameterStatusParser.scala | 2 +- .../parsers/ReadyForQueryParser.scala | 0 .../parsers/ReturningMessageParser.scala | 0 .../parsers/RowDescriptionParser.scala | 2 +- .../pool/ConnectionObjectFactory.scala | 0 .../util/ArrayStreamingParser.scala | 3 +- .../util/ArrayStreamingParserDelegate.scala | 2 +- .../db/postgresql}/util/ChannelUtils.scala | 2 +- .../async/db/postgresql}/util/URLParser.scala | 3 +- .../src}/test/resources/logback.xml | 0 .../async/db/examples/BasicExample.scala | 2 +- .../db/general/MutableResultSetSpec.scala | 1 + .../async/db/postgresql/ArrayTypesSpec.scala | 0 .../DatabaseConnectionHandlerSpec.scala | 0 .../db/postgresql/DatabaseTestHelper.scala | 0 .../db/postgresql/MessageDecoderSpec.scala | 0 .../async/db/postgresql/TestUtils.scala | 0 .../postgresql/column/ArrayDecoderSpec.scala | 0 .../DefaultColumnEncoderRegistrySpec.scala | 0 .../db/postgresql/parsers/ParserESpec.scala | 0 .../db/postgresql/parsers/ParserKSpec.scala | 0 .../db/postgresql/parsers/ParserSSpec.scala | 0 .../postgresql/pool/ConnectionPoolSpec.scala | 0 .../SingleThreadedAsyncObjectPoolSpec.scala | 0 .../util/ArrayStreamingParserSpec.scala | 1 - .../db/postgresql/util/URLParserSpec.scala | 1 - .../async/db/util/ChannelUtilsSpec.scala | 1 + project/Build.scala | 60 +++++++++++++++++++ 138 files changed, 100 insertions(+), 65 deletions(-) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/Configuration.scala (75%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/Connection.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/QueryResult.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/ResultSet.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/RowData.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/util/Log.scala (100%) rename {src => db-async-common/src}/main/scala/com/github/mauricio/async/db/util/Worker.scala (100%) rename {src => postgresql-async/src}/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java (100%) rename {src => postgresql-async/src}/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala (99%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala (94%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala (93%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala (97%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala (98%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala (95%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala (96%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala (100%) rename {src/main/scala/com/github/mauricio/async/db => postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql}/general/ArrayRowData.scala (96%) rename {src/main/scala/com/github/mauricio/async/db => postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql}/general/MutableResultSet.scala (97%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala (95%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala (95%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala (94%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala (100%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala (97%) rename {src => postgresql-async/src}/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala (100%) rename {src/main/scala/com/github/mauricio/async/db => postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql}/util/ArrayStreamingParser.scala (96%) rename {src/main/scala/com/github/mauricio/async/db => postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql}/util/ArrayStreamingParserDelegate.scala (93%) rename {src/main/scala/com/github/mauricio/async/db => postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql}/util/ChannelUtils.scala (96%) rename {src/main/scala/com/github/mauricio/async/db => postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql}/util/URLParser.scala (93%) rename {src => postgresql-async/src}/test/resources/logback.xml (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala (96%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala (97%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala (100%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala (96%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala (97%) rename {src => postgresql-async/src}/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala (94%) create mode 100644 project/Build.scala diff --git a/.gitignore b/.gitignore index 98ee5565..643ed184 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ bin/* .settings/* project/target/* project/project/* +postgresql-async/target/* +db-async-common/target/* .rvmrc .ruby-version .ruby-gemset diff --git a/README.markdown b/README.markdown index 7fa04c22..83238c94 100644 --- a/README.markdown +++ b/README.markdown @@ -160,6 +160,10 @@ disconnect and the connection is closed. You can also use the `ConnectionPool` provided by the driver to simplify working with database connections in your app. Check the blog post above for more details and the project's ScalaDocs. +## Supported Scala/Java types and their destination types on PostgreSQL + + + ## Licence This project is freely available under the Apache 2 licence, use it at your own risk. \ No newline at end of file diff --git a/build.sbt b/build.sbt index 096500f0..1f334a46 100644 --- a/build.sbt +++ b/build.sbt @@ -1,32 +1,19 @@ import de.johoop.jacoco4sbt._ import JacocoPlugin._ -name := "postgresql-async" +scalaVersion in ThisBuild := "2.10.1" -version := "0.1.2-SNAPSHOT" +javacOptions in ThisBuild := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8") -organization := "com.github.mauricio" +scalacOptions in ThisBuild := Seq("-deprecation", "-unchecked", "-feature", "–encoding", "UTF8", "–explaintypes") -scalaVersion := "2.10.1" +organization in ThisBuild := "com.github.mauricio" -libraryDependencies ++= Seq( - "commons-pool" % "commons-pool" % "1.6", - "ch.qos.logback" % "logback-classic" % "1.0.9", - "io.netty" % "netty" % "3.6.5.Final", - "joda-time" % "joda-time" % "2.2", - "org.joda" % "joda-convert" % "1.3.1", - "org.specs2" %% "specs2" % "1.14" % "test" -) - -scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature") - -javacOptions ++= Seq("-source", "1.5", "-target", "1.5") - -credentials += Credentials(Path.userHome / ".ivy2" / ".credentials") +publishArtifact in Test in ThisBuild := false -publishMavenStyle := true +publishMavenStyle in ThisBuild := true -publishArtifact in Test := false +autoScalaLibrary in ThisBuild := true pomIncludeRepository := { _ => false } diff --git a/src/main/scala/com/github/mauricio/async/db/Configuration.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/Configuration.scala similarity index 75% rename from src/main/scala/com/github/mauricio/async/db/Configuration.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/Configuration.scala index 06ddd777..65aeaef7 100644 --- a/src/main/scala/com/github/mauricio/async/db/Configuration.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/Configuration.scala @@ -16,32 +16,15 @@ package com.github.mauricio.async.db +import com.github.mauricio.async.db.util.ExecutorServiceUtils import java.nio.charset.Charset import java.util.concurrent.ExecutorService -import org.jboss.netty.util.CharsetUtil -import com.github.mauricio.async.db.util.{URLParser, ExecutorServiceUtils} -import com.github.mauricio.async.db.postgresql.MessageDecoder +import scala.{None, Option, Int} +import scala.Predef._ object Configuration { val Default = new Configuration("postgres") - - /** - * - * Creates a [[com.github.mauricio.async.db.Configuration]] object from a database URL, accepts both the - * JDBC URL as a Heroku like URL. - * - * @param url - * @param bossPool - * @param workerPool - * @param charset - * @return - */ - - def from(url: String, - bossPool: ExecutorService = Default.bossPool, - workerPool: ExecutorService = Default.workerPool, - charset: Charset = Default.charset): Configuration = URLParser.parse(url, bossPool, workerPool, charset) - + val DefaultCharset = Charset.forName("UTF-8") } /** @@ -72,6 +55,6 @@ case class Configuration(val username: String, val database: Option[String] = None, val bossPool: ExecutorService = ExecutorServiceUtils.FixedThreadPool, val workerPool: ExecutorService = ExecutorServiceUtils.FixedThreadPool, - val charset: Charset = CharsetUtil.UTF_8, - val maximumMessageSize: Int = MessageDecoder.DefaultMaximumSize + val charset: Charset = Configuration.DefaultCharset, + val maximumMessageSize: Int = 16777216 ) diff --git a/src/main/scala/com/github/mauricio/async/db/Connection.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/Connection.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala diff --git a/src/main/scala/com/github/mauricio/async/db/QueryResult.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/QueryResult.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/QueryResult.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/QueryResult.scala diff --git a/src/main/scala/com/github/mauricio/async/db/ResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/ResultSet.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/ResultSet.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/ResultSet.scala diff --git a/src/main/scala/com/github/mauricio/async/db/RowData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/RowData.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/RowData.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/RowData.scala diff --git a/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala diff --git a/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala diff --git a/src/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ObjectFactory.scala diff --git a/src/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolAlreadyTerminatedException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala diff --git a/src/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolExhaustedException.scala diff --git a/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 similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala diff --git a/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala diff --git a/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala diff --git a/src/main/scala/com/github/mauricio/async/db/util/Log.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Log.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/util/Log.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/util/Log.scala diff --git a/src/main/scala/com/github/mauricio/async/db/util/Worker.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Worker.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/util/Worker.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/util/Worker.scala diff --git a/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java b/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java similarity index 100% rename from src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java rename to postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java diff --git a/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java b/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java similarity index 100% rename from src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java rename to postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala similarity index 99% rename from src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 820ea567..786fa24e 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -16,7 +16,6 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.Log @@ -34,6 +33,7 @@ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ import scala.annotation.switch +import com.github.mauricio.async.db.postgresql.general.MutableResultSet object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala diff --git a/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 similarity index 94% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala index ef221b24..05970c28 100644 --- a/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 @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.postgresql.column -import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} import scala.collection.IndexedSeq import scala.collection.mutable.{ArrayBuffer, Stack} +import com.github.mauricio.async.db.postgresql.util.{ArrayStreamingParserDelegate, ArrayStreamingParser} class ArrayDecoder(private val encoder: ColumnDecoder) extends ColumnDecoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala diff --git a/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 similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala similarity index 93% rename from src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index e1f134db..d1cb4338 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -18,8 +18,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.{Message, AuthenticationResponseType} import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} -import com.github.mauricio.async.db.postgresql.util.PostgreSQLMD5Digest -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.postgresql.util.{ChannelUtils, PostgreSQLMD5Digest} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala similarity index 97% rename from src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index cad61016..242d7d63 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -19,9 +19,9 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.column.{ColumnEncoderRegistry, ColumnEncoderDecoder} import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.postgresql.util.ChannelUtils class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala similarity index 98% rename from src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 8eac943b..ae5c8787 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -19,9 +19,9 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.column.{ColumnEncoderRegistry, ColumnEncoderDecoder} import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.postgresql.util.ChannelUtils class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala similarity index 95% rename from src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index d40a476a..07a75aa0 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -18,9 +18,9 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, FrontendMessage} -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.postgresql.util.ChannelUtils class QueryMessageEncoder(charset: Charset) extends Encoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala similarity index 96% rename from src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index 71f3b625..9bf4cd49 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, StartupMessage} -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.postgresql.util.ChannelUtils class StartupMessageEncoder(charset: Charset) extends Encoder { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ColumnDecoderNotFoundException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala diff --git a/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/ArrayRowData.scala similarity index 96% rename from src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/ArrayRowData.scala index 386a343c..e5b89ac2 100644 --- a/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/ArrayRowData.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.general +package com.github.mauricio.async.db.postgresql.general import com.github.mauricio.async.db.RowData diff --git a/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/MutableResultSet.scala similarity index 97% rename from src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/MutableResultSet.scala index 7057d60e..b61c2925 100644 --- a/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/MutableResultSet.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.general +package com.github.mauricio.async.db.postgresql.general import collection.mutable.ArrayBuffer import com.github.mauricio.async.db.{RowData, ResultSet} diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeCleartextMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMD5.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationChallengeMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationOkMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationResponseType.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala similarity index 95% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 5152c5d1..3f8c6385 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, Message} -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.postgresql.util.ChannelUtils class CommandCompleteParser(charset: Charset) extends MessageParser { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala similarity index 95% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 062095c5..99e1dcb6 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.Message -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.postgresql.util.ChannelUtils abstract class InformationParser(charset: Charset) extends MessageParser { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala similarity index 94% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index a4b0cd33..0ae39269 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, Message} -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.postgresql.util.ChannelUtils class ParameterStatusParser(charset: Charset) extends MessageParser { diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala similarity index 97% rename from src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 7dd96cd3..c685ad92 100644 --- a/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} -import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.postgresql.util.ChannelUtils /** diff --git a/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala similarity index 100% rename from src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParser.scala similarity index 96% rename from src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParser.scala index 6cc99042..dca643b6 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParser.scala @@ -13,11 +13,12 @@ * under the License. */ -package com.github.mauricio.async.db.util +package com.github.mauricio.async.db.postgresql.util import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException import scala.collection.mutable import scala.collection.mutable.StringBuilder +import com.github.mauricio.async.db.util.Log object ArrayStreamingParser { diff --git a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserDelegate.scala similarity index 93% rename from src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserDelegate.scala index d185f335..1d167e5f 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ArrayStreamingParserDelegate.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserDelegate.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.util +package com.github.mauricio.async.db.postgresql.util trait ArrayStreamingParserDelegate { diff --git a/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ChannelUtils.scala similarity index 96% rename from src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ChannelUtils.scala index bbc5c049..d068b21f 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ChannelUtils.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.util +package com.github.mauricio.async.db.postgresql.util import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer diff --git a/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/URLParser.scala similarity index 93% rename from src/main/scala/com/github/mauricio/async/db/util/URLParser.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/URLParser.scala index 3a30a3b4..0fb8703c 100644 --- a/src/main/scala/com/github/mauricio/async/db/util/URLParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/URLParser.scala @@ -14,10 +14,9 @@ * under the License. */ -package com.github.mauricio.async.db.util +package com.github.mauricio.async.db.postgresql.util import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.postgresql.util.ParseURL import java.nio.charset.Charset import java.util.concurrent.ExecutorService import scala.collection.JavaConversions._ diff --git a/src/test/resources/logback.xml b/postgresql-async/src/test/resources/logback.xml similarity index 100% rename from src/test/resources/logback.xml rename to postgresql-async/src/test/resources/logback.xml diff --git a/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala similarity index 96% rename from src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala index e44c797d..1bafad03 100644 --- a/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala @@ -18,11 +18,11 @@ package com.github.mauricio.async.db.examples import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext -import com.github.mauricio.async.db.util.URLParser import com.github.mauricio.async.db.{RowData, QueryResult, Connection} import scala.concurrent.duration._ import scala.concurrent.{Await, Future} import scala.language.postfixOps +import com.github.mauricio.async.db.postgresql.util.URLParser object BasicExample { diff --git a/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala similarity index 97% rename from src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala index 82b78adf..f1806cb8 100644 --- a/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala @@ -21,6 +21,7 @@ import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification +import com.github.mauricio.async.db.postgresql.general.MutableResultSet class MutableResultSetSpec extends Specification { diff --git a/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 similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TestUtils.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala diff --git a/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 similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala diff --git a/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 similarity index 100% rename from src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala diff --git a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala similarity index 96% rename from src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala index 15074cd6..61be30ac 100644 --- a/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParserSpec.scala @@ -16,7 +16,6 @@ package com.github.mauricio.async.db.postgresql.util -import com.github.mauricio.async.db.util.{ArrayStreamingParser, ArrayStreamingParserDelegate} import org.specs2.mutable.Specification import scala.collection.mutable.ArrayBuffer diff --git a/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 similarity index 97% rename from src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala index 87d3999f..e331ccc1 100644 --- a/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,7 +17,6 @@ package com.github.mauricio.async.db.postgresql.util import org.specs2.mutable.Specification -import com.github.mauricio.async.db.util.URLParser class URLParserSpec extends Specification { diff --git a/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala similarity index 94% rename from src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala index f4c2d0b8..96371063 100644 --- a/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala @@ -3,6 +3,7 @@ package com.github.mauricio.async.db.util import org.specs2.mutable.Specification import org.jboss.netty.util.CharsetUtil import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.postgresql.util.ChannelUtils /* * Copyright 2013 Maurício Linhares diff --git a/project/Build.scala b/project/Build.scala new file mode 100644 index 00000000..199fad56 --- /dev/null +++ b/project/Build.scala @@ -0,0 +1,60 @@ +import sbt._ +import Keys._ + +object ProjectBuild extends Build { + + + lazy val root = Project( + id = "db-async-base", + base = file("."), + settings = Defaults.defaultSettings ++ Seq( + scalaVersion := "2.10.1", + autoScalaLibrary := true + ), + aggregate = Seq(common, postgresql) + ) + + lazy val common = Project( + id = "db-async-common", + base = file("db-async-common"), + settings = Defaults.defaultSettings ++ Seq( + name := "db-async-common", + version := "0.1.2-SNAPSHOT", + scalaVersion := "2.10.1", + libraryDependencies := Configuration.commonDependencies, + autoScalaLibrary := true + ) + ) + + lazy val postgresql = Project( + id = "postgresql-async", + base = file("postgresql-async"), + settings = Defaults.defaultSettings ++ Seq( + name := "postgresql-async", + version := "0.1.2-SNAPSHOT", + scalaVersion := "2.10.1", + libraryDependencies ++= Configuration.postgresqlAsyncDependencies, + autoScalaLibrary := true + ) + ) aggregate(common) dependsOn(common) + +} + +object Configuration { + + val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" + + val commonDependencies = Seq( + "commons-pool" % "commons-pool" % "1.6", + "ch.qos.logback" % "logback-classic" % "1.0.9", + "joda-time" % "joda-time" % "2.2", + "org.joda" % "joda-convert" % "1.3.1", + specs2Dependency + ) + + val postgresqlAsyncDependencies = Seq( + "io.netty" % "netty" % "3.6.5.Final", + specs2Dependency + ) + +} \ No newline at end of file From 7b00bd1610c948613ff68488b3cf54a35b51031c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 2 May 2013 12:11:01 -0300 Subject: [PATCH 051/357] Adding Scala library to make sbt happy --- project/Build.scala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 199fad56..9c40dab1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -21,8 +21,7 @@ object ProjectBuild extends Build { name := "db-async-common", version := "0.1.2-SNAPSHOT", scalaVersion := "2.10.1", - libraryDependencies := Configuration.commonDependencies, - autoScalaLibrary := true + libraryDependencies := Configuration.commonDependencies ) ) @@ -33,8 +32,7 @@ object ProjectBuild extends Build { name := "postgresql-async", version := "0.1.2-SNAPSHOT", scalaVersion := "2.10.1", - libraryDependencies ++= Configuration.postgresqlAsyncDependencies, - autoScalaLibrary := true + libraryDependencies ++= Configuration.postgresqlAsyncDependencies ) ) aggregate(common) dependsOn(common) @@ -42,18 +40,19 @@ object ProjectBuild extends Build { object Configuration { - val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" + val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" withSources() val commonDependencies = Seq( - "commons-pool" % "commons-pool" % "1.6", - "ch.qos.logback" % "logback-classic" % "1.0.9", - "joda-time" % "joda-time" % "2.2", - "org.joda" % "joda-convert" % "1.3.1", + "commons-pool" % "commons-pool" % "1.6" withSources(), + "ch.qos.logback" % "logback-classic" % "1.0.9" withSources(), + "joda-time" % "joda-time" % "2.2" withSources(), + "org.joda" % "joda-convert" % "1.3.1" withSources(), + "org.scala-lang" % "scala-library" % "2.10.1" withSources() , specs2Dependency ) val postgresqlAsyncDependencies = Seq( - "io.netty" % "netty" % "3.6.5.Final", + "io.netty" % "netty" % "3.6.5.Final" withSources(), specs2Dependency ) From 1899f0b40a0b3a77a7c49d35b6619f2213498cb2 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 2 May 2013 14:04:13 -0300 Subject: [PATCH 052/357] Getting scaladoc to point to the right place --- build.sbt | 44 ------------------------------ project/Build.scala | 65 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/build.sbt b/build.sbt index 1f334a46..2e2fd1f6 100644 --- a/build.sbt +++ b/build.sbt @@ -1,50 +1,6 @@ import de.johoop.jacoco4sbt._ import JacocoPlugin._ -scalaVersion in ThisBuild := "2.10.1" -javacOptions in ThisBuild := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8") - -scalacOptions in ThisBuild := Seq("-deprecation", "-unchecked", "-feature", "–encoding", "UTF8", "–explaintypes") - -organization in ThisBuild := "com.github.mauricio" - -publishArtifact in Test in ThisBuild := false - -publishMavenStyle in ThisBuild := true - -autoScalaLibrary in ThisBuild := true - -pomIncludeRepository := { _ => false } - -publishTo <<= version { v: String => - val nexus = "https://oss.sonatype.org/" - if (v.trim.endsWith("SNAPSHOT")) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") -} - -pomExtra := ( - https://github.com/mauricio/postgresql-async - - - APACHE-2.0 - http://www.apache.org/licenses/LICENSE-2.0 - repo - - - - git@github.com:mauricio/postgresql-netty.git - scm:git:git@github.com:mauricio/postgresql-netty.git - - - - mauricio-linhares - Maurício Linhares - https://github.com/mauricio - - -) seq(jacoco.settings : _*) \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index 9c40dab1..b1c5167d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -7,20 +7,16 @@ object ProjectBuild extends Build { lazy val root = Project( id = "db-async-base", base = file("."), - settings = Defaults.defaultSettings ++ Seq( - scalaVersion := "2.10.1", - autoScalaLibrary := true - ), + settings = Configuration.baseSettings, aggregate = Seq(common, postgresql) ) lazy val common = Project( id = "db-async-common", base = file("db-async-common"), - settings = Defaults.defaultSettings ++ Seq( + settings = Configuration.baseSettings ++ Seq( name := "db-async-common", version := "0.1.2-SNAPSHOT", - scalaVersion := "2.10.1", libraryDependencies := Configuration.commonDependencies ) ) @@ -28,13 +24,12 @@ object ProjectBuild extends Build { lazy val postgresql = Project( id = "postgresql-async", base = file("postgresql-async"), - settings = Defaults.defaultSettings ++ Seq( + settings = Configuration.baseSettings ++ Seq( name := "postgresql-async", version := "0.1.2-SNAPSHOT", - scalaVersion := "2.10.1", libraryDependencies ++= Configuration.postgresqlAsyncDependencies ) - ) aggregate(common) dependsOn(common) + ) aggregate (common) dependsOn (common) } @@ -42,12 +37,12 @@ object Configuration { val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" withSources() - val commonDependencies = Seq( + val commonDependencies = Seq( "commons-pool" % "commons-pool" % "1.6" withSources(), "ch.qos.logback" % "logback-classic" % "1.0.9" withSources(), "joda-time" % "joda-time" % "2.2" withSources(), "org.joda" % "joda-convert" % "1.3.1" withSources(), - "org.scala-lang" % "scala-library" % "2.10.1" withSources() , + "org.scala-lang" % "scala-library" % "2.10.1" withSources(), specs2Dependency ) @@ -56,4 +51,52 @@ object Configuration { specs2Dependency ) + val baseSettings = Defaults.defaultSettings ++ Seq( + scalacOptions := + Opts.compile.encoding("UTF8") + :+ Opts.compile.deprecation + :+ Opts.compile.unchecked + :+ Opts.compile.explaintypes + :+ "-feature" + , + scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), + scalaVersion := "2.10.1", + javacOptions := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8"), + organization := "com.github.mauricio", + publishArtifact in Test := false, + publishMavenStyle := true, + pomIncludeRepository := { + _ => false + }, + publishTo <<= version { + v: String => + val nexus = "https://oss.sonatype.org/" + if (v.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + pomExtra := ( + https://github.com/mauricio/postgresql-async + + + APACHE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + git@github.com:mauricio/postgresql-netty.git + scm:git:git@github.com:mauricio/postgresql-netty.git + + + + mauricio-linhares + Maurício Linhares + https://github.com/mauricio + + + ) + ) + } \ No newline at end of file From c2c8beb5592473a15b83f0f98d9b29eb3f51aad7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 7 May 2013 10:17:17 -0300 Subject: [PATCH 053/357] Working on MySQL handshake --- .gitignore | 1 + Procfile | 1 + .../mauricio/async/db/KindedMessage.scala | 23 +++ .../BufferNotFullyConsumedException.scala | 22 +++ .../db}/exceptions/DatabaseException.scala | 2 +- .../EncoderNotAvailableException.scala | 6 +- .../ParserNotAvailableException.scala | 4 +- ...pportedAuthenticationMethodException.scala | 12 +- .../mauricio/async/db/util/ChannelUtils.scala | 125 +++++++++++++++ .../async/db/util/ChannelWrapper.scala | 38 +++++ .../mauricio/async/db/util/FutureUtils.scala | 29 ++++ .../async/db/util/ChannelUtilsSpec.scala | 26 ++- .../async/db/mysql/MySQLConnection.scala | 148 ++++++++++++++++++ .../async/db/mysql/MySQLFrameDecoder.scala | 76 +++++++++ .../async/db/mysql/MySQLOneToOneEncoder.scala | 60 +++++++ .../async/db/mysql/decoder/ErrorDecoder.scala | 37 +++++ .../mysql/decoder/HandshakeV10Decoder.scala | 73 +++++++++ .../db/mysql/decoder/MessageDecoder.scala | 26 +++ .../encoder/HandshakeResponseEncoder.scala | 115 ++++++++++++++ .../db/mysql/encoder/MessageEncoder.scala | 26 +++ .../encoder/auth/AuthenticationMethod.scala | 23 +++ .../MySQLNativePasswordAuthentication.scala | 66 ++++++++ .../CharsetMappingNotAvailableException.scala | 23 +++ .../db/mysql/exceptions/MySQLException.scala | 23 +++ .../mysql/message/client/ClientMessage.scala | 27 ++++ .../client/HandshakeResponseMessage.scala | 29 ++++ .../mysql/message/server/ErrorMessage.scala | 20 +++ .../message/server/HandshakeMessage.scala | 28 ++++ .../mysql/message/server/ServerMessage.scala | 28 ++++ .../async/db/mysql/util/CharsetMapper.scala | 40 +++++ mysql-async/src/test/resources/logback.xml | 20 +++ .../async/db/mysql/MySQLConnectionSpec.scala | 42 +++++ .../async/db/postgresql/MessageEncoder.scala | 2 +- .../encoders/CredentialEncoder.scala | 3 +- .../ExecutePreparedStatementEncoder.scala | 2 +- .../PreparedStatementOpeningEncoder.scala | 2 +- .../encoders/QueryMessageEncoder.scala | 2 +- .../encoders/StartupMessageEncoder.scala | 2 +- ...ConnectionStillRunningQueryException.scala | 2 + .../DateEncoderNotAvailableException.scala | 2 + .../exceptions/GenericDatabaseException.scala | 1 + .../InsufficientParametersException.scala | 2 + .../exceptions/InvalidArrayException.scala | 2 + .../exceptions/MessageTooLongException.scala | 2 + ...issingCredentialInformationException.scala | 1 + .../NegativeMessageSizeException.scala | 2 + .../exceptions/NotConnectedException.scala | 2 + .../QueryMustNotBeNullOrEmptyException.scala | 2 + .../messages/frontend/FrontendMessage.scala | 4 +- .../parsers/AuthenticationStartupParser.scala | 2 +- .../parsers/CommandCompleteParser.scala | 2 +- .../parsers/InformationParser.scala | 2 +- .../parsers/MessageParsersRegistry.scala | 2 +- .../parsers/ParameterStatusParser.scala | 2 +- .../parsers/RowDescriptionParser.scala | 2 +- .../db/postgresql/util/ChannelUtils.scala | 73 --------- .../DatabaseConnectionHandlerSpec.scala | 3 +- project/Build.scala | 54 ++++--- 58 files changed, 1278 insertions(+), 118 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/exceptions/DatabaseException.scala (92%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/exceptions/EncoderNotAvailableException.scala (78%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/exceptions/ParserNotAvailableException.scala (87%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/exceptions/UnsupportedAuthenticationMethodException.scala (82%) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala rename {postgresql-async => db-async-common}/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala (61%) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/CharsetMappingNotAvailableException.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ErrorMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala create mode 100644 mysql-async/src/test/resources/logback.xml create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala delete mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ChannelUtils.scala diff --git a/.gitignore b/.gitignore index 643ed184..5184f39d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ project/target/* project/project/* postgresql-async/target/* db-async-common/target/* +mysql-async/target/* .rvmrc .ruby-version .ruby-gemset diff --git a/Procfile b/Procfile index 9c41932a..04210f34 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ postgresql: /usr/local/Cellar/postgresql/9.1.5/bin/postgres -D /Users/mauricio/databases/postgresql +mysql: mysqld --log-warnings --console \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala new file mode 100644 index 00000000..b1f8eceb --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala @@ -0,0 +1,23 @@ +/* + * 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 + +trait KindedMessage { + + def kind : Int + +} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala new file mode 100644 index 00000000..bae6723d --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala @@ -0,0 +1,22 @@ +/* + * 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.exceptions + +import org.jboss.netty.buffer.ChannelBuffer + +class BufferNotFullyConsumedException ( buffer : ChannelBuffer ) + extends DatabaseException( "Buffer was not fully consumed by decoder, %s bytes to read".format(buffer.readableBytes()) ) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DatabaseException.scala similarity index 92% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DatabaseException.scala index afb48486..0dfaa9b8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DatabaseException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DatabaseException.scala @@ -14,6 +14,6 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.exceptions +package com.github.mauricio.async.db.exceptions class DatabaseException(message: String) extends RuntimeException(message) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/EncoderNotAvailableException.scala similarity index 78% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/EncoderNotAvailableException.scala index a0584cdd..09ff916a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/EncoderNotAvailableException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/EncoderNotAvailableException.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.exceptions +package com.github.mauricio.async.db.exceptions -import com.github.mauricio.async.db.postgresql.messages.frontend.FrontendMessage +import com.github.mauricio.async.db.KindedMessage -class EncoderNotAvailableException(message: FrontendMessage) +class EncoderNotAvailableException(message: KindedMessage) extends DatabaseException("Encoder not available for name %s".format(message.kind)) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ParserNotAvailableException.scala similarity index 87% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ParserNotAvailableException.scala index ab180152..06b3b549 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ParserNotAvailableException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ParserNotAvailableException.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.exceptions +package com.github.mauricio.async.db.exceptions class ParserNotAvailableException(t: Byte) - extends DatabaseException("There is no parser available for message type '%s'".format(t)) \ No newline at end of file + extends DatabaseException("There is no parser available for message type '%s' (%s)".format(t, Integer.toHexString(t))) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnsupportedAuthenticationMethodException.scala similarity index 82% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnsupportedAuthenticationMethodException.scala index fa1d9418..c28e8c1e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/UnsupportedAuthenticationMethodException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnsupportedAuthenticationMethodException.scala @@ -14,7 +14,13 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.exceptions +package com.github.mauricio.async.db.exceptions -class UnsupportedAuthenticationMethodException(val authenticationType: Int) - extends DatabaseException("Unknown authentication method -> '%s'".format(authenticationType)) \ No newline at end of file +class UnsupportedAuthenticationMethodException(val authenticationType: String) + extends DatabaseException("Unknown authentication method -> '%s'".format(authenticationType)) { + + def this( authType : Int ) { + this(authType.toString) + } + +} \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala new file mode 100644 index 00000000..9a493659 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -0,0 +1,125 @@ +/* + * 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.util + +import java.nio.charset.Charset +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} + +object ChannelUtils { + + def writeLength(buffer: ChannelBuffer) { + + val length = buffer.writerIndex() - 1 + buffer.markWriterIndex() + buffer.writerIndex(1) + buffer.writeInt(length) + + buffer.resetWriterIndex() + + } + + def writeCString(content: String, b: ChannelBuffer, charset: Charset): Unit = { + b.writeBytes(content.getBytes(charset)) + b.writeByte(0) + } + + def writeSizedString( content : String, b : ChannelBuffer, charset : Charset ) { + val bytes = content.getBytes(charset) + b.writeByte(bytes.length) + b.writeBytes(bytes) + } + + def readCString(b: ChannelBuffer, charset: Charset): String = { + b.markReaderIndex() + + var byte: Byte = 0 + var count = 0 + + do { + byte = b.readByte() + count += 1 + } while (byte != 0) + + b.resetReaderIndex() + + val result = b.toString(b.readerIndex(), count - 1, charset) + + b.readerIndex(b.readerIndex() + count) + + return result + } + + def readUntilEOF( b : ChannelBuffer, charset : Charset ) : String = { + if ( b.readableBytes() == 0 ) { + return "" + } + + b.markReaderIndex() + + var byte: Byte = -1 + var count = 0 + var offset = 1 + + while (byte != 0) { + if ( b.readableBytes() > 0 ) { + byte = b.readByte() + count += 1 + } else { + byte = 0 + offset = 0 + } + } + + b.resetReaderIndex() + + val result = b.toString(b.readerIndex(), count - offset, charset) + + b.readerIndex(b.readerIndex() + count) + + return result + } + + def readLongInt( b : ChannelBuffer ) : Int = { + (b.readByte() & 0xff) | ((b.readByte() & 0xff) << 8) | ((b.readByte() & 0xff) << 16) + } + + def writeLongInt( b : ChannelBuffer, value : Int ) { + b.writeByte( value & 0xff ) + b.writeByte( value >>> 8 ) + b.writeByte( value >>> 16 ) + } + + def writePacketLength(buffer: ChannelBuffer, sequence : Int = 1) { + val length = buffer.writerIndex() - 4 + buffer.markWriterIndex() + buffer.writerIndex(0) + + writeLongInt( buffer, length ) + buffer.writeByte(sequence) + + buffer.resetWriterIndex() + } + + def packetBuffer( estimate : Int = 1024 ) : ChannelBuffer = { + val buffer = ChannelBuffers.dynamicBuffer(estimate) + + buffer.writeInt(0) + + buffer + } + +} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala new file mode 100644 index 00000000..ef64fc2f --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -0,0 +1,38 @@ +/* + * 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.util + +import org.jboss.netty.buffer.ChannelBuffer +import java.nio.charset.Charset + +object ChannelWrapper { + implicit def bufferToWrapper( buffer : ChannelBuffer ) = new ChannelWrapper(buffer) +} + +class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { + + def readFixedString( length : Int, charset : Charset ) : String = { + val result = buffer.toString(0, length, charset) + buffer.readerIndex( buffer.readerIndex() + length ) + result + } + + def readCString( charset : Charset ) = ChannelUtils.readCString(buffer, charset) + + def readUntilEOF( charset: Charset ) = ChannelUtils.readUntilEOF(buffer, charset) + +} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala new file mode 100644 index 00000000..763159b9 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala @@ -0,0 +1,29 @@ +/* + * 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.util + +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ +import scala.language.postfixOps + +object FutureUtils { + + def await[T]( future : Future[T] ) : T = { + Await.result(future, 5 seconds ) + } + +} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala similarity index 61% rename from postgresql-async/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala rename to db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala index 96371063..9b839e3e 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala @@ -2,8 +2,7 @@ package com.github.mauricio.async.db.util import org.specs2.mutable.Specification import org.jboss.netty.util.CharsetUtil -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import org.jboss.netty.buffer.ChannelBuffers /* * Copyright 2013 Maurício Linhares @@ -34,6 +33,29 @@ class ChannelUtilsSpec extends Specification { ChannelUtils.writeCString(content, buffer, charset) ChannelUtils.readCString(buffer, charset) === content + buffer.readableBytes() === 0 + } + + "correctly read the buggy MySQL EOF string when there is an EOF" in { + val content = "some text" + val buffer = ChannelBuffers.dynamicBuffer() + + ChannelUtils.writeCString(content, buffer, charset) + + ChannelUtils.readUntilEOF(buffer, charset) === content + buffer.readableBytes() === 0 + } + + "correctly read the buggy MySQL EOF string when there is no EOF" in { + + val content = "some text" + val buffer = ChannelBuffers.dynamicBuffer() + + buffer.writeBytes(content.getBytes(charset)) + + ChannelUtils.readUntilEOF(buffer, charset) === content + buffer.readableBytes() === 0 + } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala new file mode 100644 index 00000000..525b2011 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -0,0 +1,148 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.Configuration +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory +import org.jboss.netty.bootstrap.ClientBootstrap +import scala.concurrent.{Promise, Future} +import org.jboss.netty.channel._ +import java.net.InetSocketAddress +import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.mysql.message.server.{ErrorMessage, HandshakeMessage, ServerMessage} +import scala.annotation.switch +import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, HandshakeResponseMessage} +import com.github.mauricio.async.db.mysql.util.CharsetMapper +import com.github.mauricio.async.db.mysql.exceptions.MySQLException + +object MySQLConnection { + val log = Log.get[MySQLConnection] +} + +class MySQLConnection( + configuration : Configuration, + charsetMapper : CharsetMapper = CharsetMapper.Instance ) + extends SimpleChannelHandler + with LifeCycleAwareChannelHandler +{ + + import MySQLConnection.log + + // validate that this charset is supported + charsetMapper.toInt(configuration.charset) + + private val factory = new NioClientSocketChannelFactory( + configuration.bossPool, + configuration.workerPool) + + private val bootstrap = new ClientBootstrap(this.factory) + private val connectionPromise = Promise[MySQLConnection]() + private var connected = false + private var currentContext : ChannelHandlerContext = null + + def connect: Future[MySQLConnection] = { + + this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + + override def getPipeline(): ChannelPipeline = { + Channels.pipeline( + new MySQLFrameDecoder(configuration.charset), + new MySQLOneToOneEncoder(configuration.charset, charsetMapper), + MySQLConnection.this) + } + + }) + + this.bootstrap.setOption("child.tcpNoDelay", true) + this.bootstrap.setOption("child.keepAlive", true) + + this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if (!future.isSuccess) { + connectionPromise.failure(future.getCause) + } + } + }) + + this.connectionPromise.future + } + + override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { + log.debug("Connected to {}", ctx.getChannel.getRemoteAddress) + this.connected = true + } + + override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { + log.error("Transport failure", e.getCause) + if ( !this.connectionPromise.isCompleted ) { + this.connectionPromise.failure(e.getCause) + } + } + + override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) { + + e.getMessage match { + case m : ServerMessage => { + (m.kind : @switch) match { + case ServerMessage.ServerProtocolVersion => { + this.onHandshake( m.asInstanceOf[HandshakeMessage] ) + } + case ServerMessage.Error => this.onError(m.asInstanceOf[ErrorMessage]) + } + } + } + + } + + private def onHandshake( message : HandshakeMessage ) { + log.debug("Received handshake message - {}", message) + + this.write(new HandshakeResponseMessage( + configuration.username, + configuration.charset, + message.seed, + message.authenticationMethod, + database = configuration.database, + password = configuration.password + )) + } + + private def onError( message : ErrorMessage ) { + val exception = new MySQLException(message) + exception.fillInStackTrace() + if ( !this.connectionPromise.isCompleted ) { + this.connectionPromise.failure(exception) + } + } + + def beforeAdd(ctx: ChannelHandlerContext) {} + + def beforeRemove(ctx: ChannelHandlerContext) {} + + def afterAdd(ctx: ChannelHandlerContext) { + this.currentContext = ctx + } + + def afterRemove(ctx: ChannelHandlerContext) { + this.currentContext = null + } + + private def write( message : ClientMessage ) { + this.currentContext.getChannel.write(message) + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala new file mode 100644 index 00000000..e92c1d75 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala @@ -0,0 +1,76 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} +import com.github.mauricio.async.db.mysql.decoder.{ErrorDecoder, HandshakeV10Decoder} +import com.github.mauricio.async.db.mysql.message.server.ServerMessage +import com.github.mauricio.async.db.util.ChannelUtils.readLongInt +import com.github.mauricio.async.db.util.Log +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer +import org.jboss.netty.channel.{Channel, ChannelHandlerContext} +import org.jboss.netty.handler.codec.frame.FrameDecoder + +object MySQLFrameDecoder { + val log = Log.get[MySQLFrameDecoder] +} + +class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { + + private val handshakeDecoder = new HandshakeV10Decoder(charset) + private val errorDecoder = new ErrorDecoder(charset) + + def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { + if (buffer.readableBytes() > 4) { + + buffer.markReaderIndex() + + val size = readLongInt(buffer) + val sequence = buffer.readByte() + + if (buffer.readableBytes() >= size) { + val messageType = buffer.readByte() + + val slice = buffer.readSlice(size - 1) + + val decoder = messageType match { + case ServerMessage.ServerProtocolVersion => this.handshakeDecoder + case ServerMessage.Error => this.errorDecoder + case _ => { + throw new ParserNotAvailableException(messageType) + } + } + + val result = decoder.decode(slice) + + if ( slice.readableBytes() != 0 ) { + throw new BufferNotFullyConsumedException(slice) + } + + return result + + } else { + buffer.resetReaderIndex() + } + + } + + return null + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala new file mode 100644 index 00000000..5a08d4d9 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala @@ -0,0 +1,60 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException +import com.github.mauricio.async.db.mysql.encoder.HandshakeResponseEncoder +import com.github.mauricio.async.db.mysql.message.client.ClientMessage +import com.github.mauricio.async.db.mysql.util.CharsetMapper +import com.github.mauricio.async.db.util.{ChannelUtils, Log} +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer +import org.jboss.netty.channel.{Channel, ChannelHandlerContext} +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder +import scala.annotation.switch + +object MySQLOneToOneEncoder { + val log = Log.get[MySQLOneToOneEncoder] +} + +class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) extends OneToOneEncoder { + + private val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) + private var sequence = 1 + + def encode(ctx: ChannelHandlerContext, channel: Channel, msg: Any): ChannelBuffer = { + + msg match { + case message : ClientMessage => { + val encoder = (message.kind : @switch) match { + case ClientMessage.ClientProtocolVersion => this.handshakeResponseEncoder + case _ => throw new EncoderNotAvailableException(message) + } + + val result = encoder.encode(message) + + ChannelUtils.writePacketLength(result, sequence) + + sequence += 1 + + result + } + } + + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala new file mode 100644 index 00000000..1fcacb49 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala @@ -0,0 +1,37 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.{ErrorMessage, ServerMessage} +import java.nio.charset.Charset +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import scala.language.implicitConversions + +class ErrorDecoder( charset : Charset ) extends MessageDecoder { + + def decode(buffer: ChannelBuffer): ServerMessage = { + + new ErrorMessage( + buffer.readShort(), + buffer.readFixedString( 5, charset ), + buffer.readUntilEOF(charset) + ) + + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala new file mode 100644 index 00000000..73ca7a76 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala @@ -0,0 +1,73 @@ +/* + * 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.mysql.decoder + +import com.github.mauricio.async.db.mysql.message.server.{HandshakeMessage, ServerMessage} +import com.github.mauricio.async.db.util.{Log, ChannelUtils} +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer + +object HandshakeV10Decoder { + val log = Log.get[HandshakeV10Decoder] +} + +class HandshakeV10Decoder( charset : Charset ) extends MessageDecoder { + + import HandshakeV10Decoder.log + + def decode(buffer: ChannelBuffer): ServerMessage = { + + val serverVersion = ChannelUtils.readCString(buffer, charset) + val connectionId = buffer.readInt() + var seed = buffer.readSlice(8).toString(charset) + var serverCapabilityFlags : Int = buffer.readShort() + + if ( buffer.readableBytes() > 0 ) { + val characterSet = buffer.readByte() & 0xff + val statusFlags = buffer.readShort() + + serverCapabilityFlags += 65536 * buffer.readShort().asInstanceOf[Int] + + val authPluginDataLength = buffer.readByte() & 0xff + var authenticationMethod : Option[String] = None + + if ( authPluginDataLength > 0 ) { + buffer.readerIndex( buffer.readerIndex() + 16 ) + seed += ChannelUtils.readUntilEOF(buffer, charset) + authenticationMethod = Some(ChannelUtils.readUntilEOF(buffer, charset)) + } + + new HandshakeMessage( + serverVersion, + connectionId, + seed, + serverCapabilityFlags, + characterSet = Some(characterSet), + statusFlags = Some(statusFlags), + authenticationMethod = authenticationMethod + ) + } else { + new HandshakeMessage( + serverVersion, + connectionId, + seed, + serverCapabilityFlags ) + } + + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala new file mode 100644 index 00000000..3a038869 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala @@ -0,0 +1,26 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.ServerMessage + +trait MessageDecoder { + + def decode( buffer : ChannelBuffer ) : ServerMessage + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala new file mode 100644 index 00000000..162b9374 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -0,0 +1,115 @@ +/* + * 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.mysql.encoder + +import com.github.mauricio.async.db.mysql.message.client.{HandshakeResponseMessage, ClientMessage} +import org.jboss.netty.buffer.ChannelBuffer +import java.nio.charset.Charset +import com.github.mauricio.async.db.mysql.util.CharsetMapper +import com.github.mauricio.async.db.mysql.encoder.auth.MySQLNativePasswordAuthentication +import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException + +object HandshakeResponseEncoder { + + final val CLIENT_COMPRESS = 32 + final val CLIENT_CONNECT_WITH_DB = 8 + final val CLIENT_FOUND_ROWS = 2 + final val CLIENT_LOCAL_FILES = 128 + /* Can use LOAD DATA LOCAL */ + final val CLIENT_LONG_FLAG = 4 + /* Get all column flags */ + final val CLIENT_LONG_PASSWORD = 1 + /* new more secure passwords */ + final val CLIENT_PROTOCOL_41 = 512 + // for > 4.1.1 + final val CLIENT_INTERACTIVE = 1024 + final val CLIENT_SSL = 2048 + final val CLIENT_TRANSACTIONS = 8192 + // Client knows about transactions + final val CLIENT_RESERVED = 16384 + // for 4.1.0 only + final val CLIENT_SECURE_CONNECTION = 32768 + final val CLIENT_MULTI_QUERIES = 65536 + // Enable/disable multiquery support + final val CLIENT_MULTI_RESULTS = 131072 + // Enable/disable multi-results + final val CLIENT_PLUGIN_AUTH = 524288 + final val CLIENT_CAN_HANDLE_EXPIRED_PASSWORD = 4194304 + final val CLIENT_CONNECT_ATTRS = 1048576 + final val MAX_3_BYTES = 255 * 255 * 255 + final val PADDING: Array[Byte] = List.fill(23) { + 0.toByte + }.toArray +} + +class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) extends MessageEncoder { + + import HandshakeResponseEncoder._ + + private val authenticationMethods = Map("mysql_native_password" -> new MySQLNativePasswordAuthentication(charset)) + + def encode(message: ClientMessage): ChannelBuffer = { + + val m = message.asInstanceOf[HandshakeResponseMessage] + + var clientCapabilities = 0 | + CLIENT_PLUGIN_AUTH | + CLIENT_LONG_PASSWORD | + CLIENT_PROTOCOL_41 | + CLIENT_TRANSACTIONS | + CLIENT_MULTI_RESULTS | + CLIENT_SECURE_CONNECTION | + CLIENT_LONG_FLAG + + if (m.database.isDefined) { + clientCapabilities |= CLIENT_CONNECT_WITH_DB + } + + val buffer = ChannelUtils.packetBuffer() + + buffer.writeInt(clientCapabilities) + buffer.writeInt(MAX_3_BYTES) + buffer.writeByte(charsetMapper.toInt(charset)) + buffer.writeBytes(PADDING) + ChannelUtils.writeCString( m.username, buffer, charset ) + + if ( m.password.isDefined ) { + val method = m.authenticationMethod.get + val authenticator = this.authenticationMethods.getOrElse( + method, { throw new UnsupportedAuthenticationMethodException(method) }) + val bytes = authenticator.generateAuthentication( m.username, m.password, m.seed ) + buffer.writeByte(bytes.length) + buffer.writeBytes(bytes) + } else { + buffer.writeByte(0) + } + + if ( m.database.isDefined ) { + ChannelUtils.writeCString( m.database.get, buffer, charset ) + } else { + buffer.writeByte(0) + } + + if ( m.authenticationMethod.isDefined ) { + ChannelUtils.writeCString( m.authenticationMethod.get, buffer, charset ) + } + + buffer + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala new file mode 100644 index 00000000..cc40a4ed --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala @@ -0,0 +1,26 @@ +/* + * 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.mysql.encoder + +import com.github.mauricio.async.db.mysql.message.client.ClientMessage +import org.jboss.netty.buffer.ChannelBuffer + +trait MessageEncoder { + + def encode( message : ClientMessage ) : ChannelBuffer + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala new file mode 100644 index 00000000..162f95ed --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.encoder.auth + +trait AuthenticationMethod { + + def generateAuthentication( username : String, password : Option[String], seed : String ) : Array[Byte] + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala new file mode 100644 index 00000000..ac22fa2e --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala @@ -0,0 +1,66 @@ +/* + * 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.mysql.encoder.auth + +import java.nio.charset.Charset +import java.security.MessageDigest +import org.jboss.netty.util.CharsetUtil + +object MySQLNativePasswordAuthentication { + final val EmptyArray = Array.empty[Byte] +} + +class MySQLNativePasswordAuthentication( charset : Charset ) extends AuthenticationMethod { + + import MySQLNativePasswordAuthentication.EmptyArray + + def generateAuthentication(username: String, password: Option[String], seed : String): Array[Byte] = { + + if ( password.isDefined ) { + scramble411(password.get, seed) + } else { + EmptyArray + } + + } + + private def scramble411( password : String, seed : String ) : Array[Byte] = { + + val messageDigest = MessageDigest.getInstance("SHA-1") + val initialDigest = messageDigest.digest(password.getBytes(charset)) + + messageDigest.reset() + + val finalDigest = messageDigest.digest(initialDigest) + + messageDigest.reset() + + messageDigest.update(seed.getBytes(CharsetUtil.US_ASCII)) + messageDigest.update(finalDigest) + + val result = messageDigest.digest() + var counter = 0 + + while ( counter < result.length ) { + result(counter) = (result(counter) ^ finalDigest(counter)).asInstanceOf[Byte] + counter += 1 + } + + result + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/CharsetMappingNotAvailableException.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/CharsetMappingNotAvailableException.scala new file mode 100644 index 00000000..b63b2c90 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/CharsetMappingNotAvailableException.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.exceptions + +import com.github.mauricio.async.db.exceptions.DatabaseException +import java.nio.charset.Charset + +class CharsetMappingNotAvailableException (charset : Charset) + extends DatabaseException( "There is no MySQL charset mapping name for the Java Charset %s".format(charset.name()) ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala new file mode 100644 index 00000000..b6f079e4 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.exceptions + +import com.github.mauricio.async.db.exceptions.DatabaseException +import com.github.mauricio.async.db.mysql.message.server.ErrorMessage + +class MySQLException( errorMessage : ErrorMessage ) + extends DatabaseException("Error %d - %s - %s".format(errorMessage.errorCode, errorMessage.sqlState, errorMessage.errorMessage)) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala new file mode 100644 index 00000000..884ff2b3 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala @@ -0,0 +1,27 @@ +/* + * 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.mysql.message.client + +import com.github.mauricio.async.db.KindedMessage + +object ClientMessage { + + final val ClientProtocolVersion = 0x09 + +} + +class ClientMessage ( val kind : Int ) extends KindedMessage \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala new file mode 100644 index 00000000..aded9bb2 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala @@ -0,0 +1,29 @@ +/* + * 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.mysql.message.client + +import java.nio.charset.Charset + +case class HandshakeResponseMessage( + username: String, + charset: Charset, + seed: String, + authenticationMethod: Option[String] = None, + password: Option[String] = None, + database: Option[String] = None + ) + extends ClientMessage(ClientMessage.ClientProtocolVersion) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ErrorMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ErrorMessage.scala new file mode 100644 index 00000000..68e68eeb --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ErrorMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.server + +case class ErrorMessage( errorCode : Int, sqlState : String, errorMessage : String ) + extends ServerMessage( ServerMessage.Error ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala new file mode 100644 index 00000000..df2a961c --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.message.server + +case class HandshakeMessage( + serverVersion: String, + connectionId: Int, + seed: String, + serverCapabilities: Int, + characterSet: Option[Int] = None, + statusFlags: Option[Short] = None, + authenticationMethod: Option[String] = None + ) + extends ServerMessage(ServerMessage.ServerProtocolVersion) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala new file mode 100644 index 00000000..5c4f2eb1 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.message.server + +import com.github.mauricio.async.db.KindedMessage + +object ServerMessage { + + final val ServerProtocolVersion = 0x0a + final val Error = 0xffffffff + +} + +class ServerMessage( val kind : Int ) extends KindedMessage diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala new file mode 100644 index 00000000..2e9404e1 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala @@ -0,0 +1,40 @@ +/* + * 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.mysql.util + +import com.github.mauricio.async.db.mysql.exceptions.CharsetMappingNotAvailableException +import java.nio.charset.Charset +import org.jboss.netty.util.CharsetUtil + +object CharsetMapper { + val Instance = new CharsetMapper() +} + +class CharsetMapper { + + private var charsetsToInt = Map[Charset,Int]( + CharsetUtil.UTF_8 -> 3, + CharsetUtil.US_ASCII -> 1 + ) + + def toInt( charset : Charset ) : Int = { + charsetsToInt.getOrElse(charset, { + throw new CharsetMappingNotAvailableException(charset) + }) + } + +} \ No newline at end of file diff --git a/mysql-async/src/test/resources/logback.xml b/mysql-async/src/test/resources/logback.xml new file mode 100644 index 00000000..11413563 --- /dev/null +++ b/mysql-async/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + [%level][%thread][%d] %msg%ex%n + + + + + target/mysql-async-tests.log + + [%level][%thread][%d] %msg%ex%n + + + + + + + + \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala new file mode 100644 index 00000000..2a932b4f --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala @@ -0,0 +1,42 @@ +/* + * 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.mysql + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.util.FutureUtils.await + +class MySQLConnectionSpec extends Specification { + + val configuration = new Configuration( + "root", + "localhost", + port = 3306, + password = None, + database = Some("mysql_async_tests") + ) + + "connection" should { + + "connect to a MySQL instance" in { + val connection = new MySQLConnection(configuration) + await( connection.connect ) === connection + } + + } + +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index eac31fa7..01566f10 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -18,7 +18,6 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.encoders._ -import com.github.mauricio.async.db.postgresql.exceptions.EncoderNotAvailableException import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend._ import com.github.mauricio.async.db.util.Log @@ -27,6 +26,7 @@ import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import scala.annotation.switch +import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException object MessageEncoder { val log = Log.get[MessageEncoder] diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index d1cb4338..91ea27d3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -18,9 +18,10 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.{Message, AuthenticationResponseType} import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} -import com.github.mauricio.async.db.postgresql.util.{ChannelUtils, PostgreSQLMD5Digest} +import com.github.mauricio.async.db.postgresql.util.{PostgreSQLMD5Digest} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.util.ChannelUtils class CredentialEncoder(charset: Charset) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 242d7d63..8c05a043 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index ae5c8787..5925751a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index 07a75aa0..6e62a74b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -20,7 +20,7 @@ import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, FrontendMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils class QueryMessageEncoder(charset: Charset) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index 9bf4cd49..46c3c0a1 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, StartupMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils class StartupMessageEncoder(charset: Charset) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala index 435afd1c..a184fb9a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala @@ -16,6 +16,8 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + class ConnectionStillRunningQueryException( connectionCount : Long, readyForQuery : Boolean ) extends DatabaseException ( "[%s] - There is a query still being run here - readyForQuery -> %s".format( connectionCount, diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala index 4534caad..81a596e4 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala @@ -1,5 +1,7 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + /** * User: mauricio * Date: 4/4/13 diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala index 33339b24..801f4423 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.exceptions import com.github.mauricio.async.db.postgresql.messages.backend.ErrorMessage +import com.github.mauricio.async.db.exceptions.DatabaseException class GenericDatabaseException(val errorMessage: ErrorMessage) extends DatabaseException(errorMessage.toString) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala index 8137ec3d..d3163c15 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala @@ -16,6 +16,8 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + /** * * Raised when the user gives more or less parameters than the query takes. Each parameter is a ? diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala index 120d1f64..054cf485 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InvalidArrayException.scala @@ -17,4 +17,6 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + class InvalidArrayException(message: String) extends DatabaseException(message) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala index b831ec0a..b37912fc 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MessageTooLongException.scala @@ -16,5 +16,7 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + class MessageTooLongException( code : Byte, length : Int, limit : Int ) extends DatabaseException("Message of type %d has size %d, higher than the limit %d".format(code, length, limit)) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala index 3bdf8ed9..18f8361b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.postgresql.exceptions import com.github.mauricio.async.db.postgresql.messages.backend.AuthenticationResponseType +import com.github.mauricio.async.db.exceptions.DatabaseException class MissingCredentialInformationException( val username: String, diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala index 26169f2c..09123d33 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala @@ -16,5 +16,7 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + class NegativeMessageSizeException( code : Byte, size : Int ) extends DatabaseException( "Message of type %d had negative size %s".format(code, size) ) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala index 7bd48c4a..0f64866e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NotConnectedException.scala @@ -16,4 +16,6 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + class NotConnectedException(message: String) extends DatabaseException(message) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala index e268fc2a..3b9a6d26 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/QueryMustNotBeNullOrEmptyException.scala @@ -16,6 +16,8 @@ package com.github.mauricio.async.db.postgresql.exceptions +import com.github.mauricio.async.db.exceptions.DatabaseException + /** * * Raised if the query string is null or empty. diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala index b80d3137..a84ff4be 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala @@ -16,4 +16,6 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -class FrontendMessage(val kind: Byte) +import com.github.mauricio.async.db.KindedMessage + +class FrontendMessage(val kind: Int) extends KindedMessage diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index 220c1206..10e24dc9 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.async.db.postgresql.messages.backend.{AuthenticationChallengeMD5, AuthenticationChallengeCleartextMessage, AuthenticationOkMessage, Message} import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException object AuthenticationStartupParser extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 3f8c6385..923b9d63 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, Message} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils class CommandCompleteParser(charset: Charset) extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 99e1dcb6..9ae99e09 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.Message import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils abstract class InformationParser(charset: Charset) extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala index ff55dfab..297f2102 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.exceptions.ParserNotAvailableException import com.github.mauricio.async.db.postgresql.messages.backend._ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.exceptions.ParserNotAvailableException class MessageParsersRegistry(charset: Charset) { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index 0ae39269..4b9ccbb0 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, Message} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils class ParameterStatusParser(charset: Charset) extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index c685ad92..fa6f7939 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.postgresql.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelUtils /** diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ChannelUtils.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ChannelUtils.scala deleted file mode 100644 index d068b21f..00000000 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ChannelUtils.scala +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer - -object ChannelUtils { - - def writeLength(buffer: ChannelBuffer) { - - val length = buffer.writerIndex() - 1 - buffer.markWriterIndex() - buffer.writerIndex(1) - buffer.writeInt(length) - - buffer.resetWriterIndex() - - } - - /* - def printBuffer(b: ChannelBuffer): Unit = { - - val bytes = new Array[Byte](b.readableBytes()) - b.markReaderIndex() - b.readBytes(bytes) - b.resetReaderIndex() - - println(bytes.mkString("-")) - - }*/ - - def writeCString(content: String, b: ChannelBuffer, charset: Charset): Unit = { - b.writeBytes(content.getBytes(charset)) - b.writeByte(0) - } - - def readCString(b: ChannelBuffer, charset: Charset): String = { - - b.markReaderIndex() - - var byte: Byte = 0 - var count = 0 - - do { - byte = b.readByte() - count += 1 - } while (byte != 0) - - b.resetReaderIndex() - - val result = b.toString(b.readerIndex(), count - 1, charset) - - b.readerIndex(b.readerIndex() + count) - - return result - } - -} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index a8665061..841f8a0d 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.postgresql.column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} -import com.github.mauricio.async.db.postgresql.exceptions.{InsufficientParametersException, QueryMustNotBeNullOrEmptyException, GenericDatabaseException, UnsupportedAuthenticationMethodException} +import com.github.mauricio.async.db.postgresql.exceptions.{InsufficientParametersException, QueryMustNotBeNullOrEmptyException, GenericDatabaseException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} @@ -25,6 +25,7 @@ import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { diff --git a/project/Build.scala b/project/Build.scala index b1c5167d..e5711e55 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -3,31 +3,45 @@ import Keys._ object ProjectBuild extends Build { + val commonName = "db-async-common" + val postgresqlName = "postgresql-async" + val mysqlName = "mysql-async" + val commonVersion = "0.1.2-SNAPSHOT" lazy val root = Project( id = "db-async-base", base = file("."), settings = Configuration.baseSettings, - aggregate = Seq(common, postgresql) + aggregate = Seq(common, postgresql, mysql) ) lazy val common = Project( - id = "db-async-common", - base = file("db-async-common"), + id = commonName, + base = file(commonName), settings = Configuration.baseSettings ++ Seq( - name := "db-async-common", - version := "0.1.2-SNAPSHOT", + name := commonName, + version := commonVersion, libraryDependencies := Configuration.commonDependencies ) ) lazy val postgresql = Project( - id = "postgresql-async", - base = file("postgresql-async"), + id = postgresqlName, + base = file(postgresqlName), settings = Configuration.baseSettings ++ Seq( - name := "postgresql-async", - version := "0.1.2-SNAPSHOT", - libraryDependencies ++= Configuration.postgresqlAsyncDependencies + name := postgresqlName, + version := commonVersion, + libraryDependencies ++= Configuration.implementationDependencies + ) + ) aggregate (common) dependsOn (common) + + lazy val mysql = Project( + id = mysqlName, + base = file(mysqlName), + settings = Configuration.baseSettings ++ Seq( + name := mysqlName, + version := commonVersion, + libraryDependencies ++= Configuration.implementationDependencies ) ) aggregate (common) dependsOn (common) @@ -35,21 +49,21 @@ object ProjectBuild extends Build { object Configuration { - val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" withSources() + val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" val commonDependencies = Seq( - "commons-pool" % "commons-pool" % "1.6" withSources(), - "ch.qos.logback" % "logback-classic" % "1.0.9" withSources(), - "joda-time" % "joda-time" % "2.2" withSources(), - "org.joda" % "joda-convert" % "1.3.1" withSources(), - "org.scala-lang" % "scala-library" % "2.10.1" withSources(), + "commons-pool" % "commons-pool" % "1.6", + "ch.qos.logback" % "logback-classic" % "1.0.9", + "joda-time" % "joda-time" % "2.2", + "org.joda" % "joda-convert" % "1.3.1", + "org.scala-lang" % "scala-library" % "2.10.1", + "io.netty" % "netty" % "3.6.5.Final", specs2Dependency - ) + ).map( d => d.withSources().withJavadoc() ) - val postgresqlAsyncDependencies = Seq( - "io.netty" % "netty" % "3.6.5.Final" withSources(), + val implementationDependencies = Seq( specs2Dependency - ) + ).map( d => d.withSources().withJavadoc() ) val baseSettings = Defaults.defaultSettings ++ Seq( scalacOptions := From 8ffb327210517729d202fe5b8adf93359b28889c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 7 May 2013 16:28:54 -0300 Subject: [PATCH 054/357] Connecting to MySQL with and without password --- .../exceptions/UnknownLengthException.scala | 20 +++++ .../mauricio/async/db/util/ChannelUtils.scala | 9 +- .../async/db/util/ChannelWrapper.scala | 45 +++++++++- .../mauricio/async/db/mysql/MySQLHelper.java | 84 +++++++++++++++++++ .../async/db/mysql/MySQLConnection.scala | 26 +++++- .../async/db/mysql/MySQLFrameDecoder.scala | 16 ++-- .../async/db/mysql/MySQLOneToOneEncoder.scala | 4 + .../async/db/mysql/decoder/ErrorDecoder.scala | 2 +- .../mysql/decoder/HandshakeV10Decoder.scala | 72 +++++++++------- .../async/db/mysql/decoder/OkDecoder.scala | 51 +++++++++++ .../encoder/HandshakeResponseEncoder.scala | 49 +++++------ .../encoder/auth/AuthenticationMethod.scala | 2 +- .../MySQLNativePasswordAuthentication.scala | 11 +-- .../client/HandshakeResponseMessage.scala | 2 +- .../message/server/HandshakeMessage.scala | 2 +- .../db/mysql/message/server/OkMessage.scala | 20 +++++ .../mysql/message/server/ServerMessage.scala | 1 + .../async/db/mysql/MySQLConnectionSpec.scala | 31 ++++++- 18 files changed, 362 insertions(+), 85 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnknownLengthException.scala create mode 100644 mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnknownLengthException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnknownLengthException.scala new file mode 100644 index 00000000..6cae6f68 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnknownLengthException.scala @@ -0,0 +1,20 @@ +/* + * 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.exceptions + +class UnknownLengthException ( length : Int ) + extends DatabaseException( "Can't handle the length %d".format(length) ) \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala index 9a493659..73072047 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.util import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import java.nio.ByteOrder object ChannelUtils { @@ -93,11 +94,11 @@ object ChannelUtils { return result } - def readLongInt( b : ChannelBuffer ) : Int = { + def read4BytesInt( b : ChannelBuffer ) : Int = { (b.readByte() & 0xff) | ((b.readByte() & 0xff) << 8) | ((b.readByte() & 0xff) << 16) } - def writeLongInt( b : ChannelBuffer, value : Int ) { + def write3BytesInt( b : ChannelBuffer, value : Int ) { b.writeByte( value & 0xff ) b.writeByte( value >>> 8 ) b.writeByte( value >>> 16 ) @@ -108,14 +109,14 @@ object ChannelUtils { buffer.markWriterIndex() buffer.writerIndex(0) - writeLongInt( buffer, length ) + write3BytesInt( buffer, length ) buffer.writeByte(sequence) buffer.resetWriterIndex() } def packetBuffer( estimate : Int = 1024 ) : ChannelBuffer = { - val buffer = ChannelBuffers.dynamicBuffer(estimate) + val buffer = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, estimate) buffer.writeInt(0) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index ef64fc2f..46fa1db0 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -16,23 +16,60 @@ package com.github.mauricio.async.db.util -import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.exceptions.UnknownLengthException import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer object ChannelWrapper { implicit def bufferToWrapper( buffer : ChannelBuffer ) = new ChannelWrapper(buffer) + + final val MySQL_NULL = 0xfb + } class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { + import ChannelWrapper.MySQL_NULL + def readFixedString( length : Int, charset : Charset ) : String = { - val result = buffer.toString(0, length, charset) - buffer.readerIndex( buffer.readerIndex() + length ) - result + val bytes = new Array[Byte](length) + buffer.readBytes( bytes ) + new String( bytes, charset ) } def readCString( charset : Charset ) = ChannelUtils.readCString(buffer, charset) def readUntilEOF( charset: Charset ) = ChannelUtils.readUntilEOF(buffer, charset) + def read3BytesInt : Int = { + val first = buffer.readByte() + val second = buffer.readByte() + val third = buffer.readByte() + var i = third << 16 | second << 8 | first + + if ((third & 0x80) == 0x80) { + i |= 0xff000000 + } + + i + } + + def readBinaryLength : Long = { + + val firstByte = buffer.readUnsignedByte() + + if ( firstByte <= 250 ) { + firstByte + } else { + firstByte match { + case MySQL_NULL => -1 + case 252 => buffer.readUnsignedShort() + case 253 => read3BytesInt + case 254 => buffer.readLong() + case _ => throw new UnknownLengthException(firstByte) + } + } + + } + } diff --git a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java new file mode 100644 index 00000000..3197e8f3 --- /dev/null +++ b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java @@ -0,0 +1,84 @@ +package com.github.mauricio.async.db.mysql; + +import org.jboss.netty.buffer.ChannelBuffer; + +public class MySQLHelper { + + public static final String dumpAsHex(ChannelBuffer buffer, int length) { + byte[] byteBuffer = new byte[length]; + + buffer.markReaderIndex(); + buffer.readBytes(byteBuffer); + buffer.resetReaderIndex(); + + StringBuffer outputBuf = new StringBuffer(length * 4); + + int p = 0; + int rows = length / 8; + + for (int i = 0; (i < rows) && (p < length); i++) { + int ptemp = p; + + for (int j = 0; j < 8; j++) { + String hexVal = Integer.toHexString(byteBuffer[ptemp] & 0xff); + + if (hexVal.length() == 1) { + hexVal = "0" + hexVal; //$NON-NLS-1$ + } + + outputBuf.append(hexVal + " "); //$NON-NLS-1$ + ptemp++; + } + + outputBuf.append(" "); //$NON-NLS-1$ + + for (int j = 0; j < 8; j++) { + int b = 0xff & byteBuffer[p]; + + if (b > 32 && b < 127) { + outputBuf.append((char) b + " "); //$NON-NLS-1$ + } else { + outputBuf.append(". "); //$NON-NLS-1$ + } + + p++; + } + + outputBuf.append("\n"); //$NON-NLS-1$ + } + + int n = 0; + + for (int i = p; i < length; i++) { + String hexVal = Integer.toHexString(byteBuffer[i] & 0xff); + + if (hexVal.length() == 1) { + hexVal = "0" + hexVal; //$NON-NLS-1$ + } + + outputBuf.append(hexVal + " "); //$NON-NLS-1$ + n++; + } + + for (int i = n; i < 8; i++) { + outputBuf.append(" "); //$NON-NLS-1$ + } + + outputBuf.append(" "); //$NON-NLS-1$ + + for (int i = p; i < length; i++) { + int b = 0xff & byteBuffer[i]; + + if (b > 32 && b < 127) { + outputBuf.append((char) b + " "); //$NON-NLS-1$ + } else { + outputBuf.append(". "); //$NON-NLS-1$ + } + } + + outputBuf.append("\n"); //$NON-NLS-1$ + + return outputBuf.toString(); + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 525b2011..90c55955 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -23,7 +23,7 @@ import scala.concurrent.{Promise, Future} import org.jboss.netty.channel._ import java.net.InetSocketAddress import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.mysql.message.server.{ErrorMessage, HandshakeMessage, ServerMessage} +import com.github.mauricio.async.db.mysql.message.server.{OkMessage, ErrorMessage, HandshakeMessage, ServerMessage} import scala.annotation.switch import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, HandshakeResponseMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper @@ -81,6 +81,22 @@ class MySQLConnection( this.connectionPromise.future } + def close : Future[MySQLConnection] = { + val promise = Promise[MySQLConnection] + + this.currentContext.getChannel.close().addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if ( future.isSuccess ) { + promise.success(MySQLConnection.this) + } else { + promise.failure(future.getCause) + } + } + }) + + promise.future + } + override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { log.debug("Connected to {}", ctx.getChannel.getRemoteAddress) this.connected = true @@ -101,6 +117,7 @@ class MySQLConnection( case ServerMessage.ServerProtocolVersion => { this.onHandshake( m.asInstanceOf[HandshakeMessage] ) } + case ServerMessage.Ok => this.onOk(m.asInstanceOf[OkMessage]) case ServerMessage.Error => this.onError(m.asInstanceOf[ErrorMessage]) } } @@ -108,6 +125,13 @@ class MySQLConnection( } + private def onOk( message : OkMessage ) { + log.debug("Received OK {}", message) + if ( !this.connectionPromise.isCompleted ) { + this.connectionPromise.success(this) + } + } + private def onHandshake( message : HandshakeMessage ) { log.debug("Received handshake message - {}", message) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala index e92c1d75..70ad644c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} -import com.github.mauricio.async.db.mysql.decoder.{ErrorDecoder, HandshakeV10Decoder} +import com.github.mauricio.async.db.mysql.decoder.{OkDecoder, ErrorDecoder, HandshakeV10Decoder} import com.github.mauricio.async.db.mysql.message.server.ServerMessage -import com.github.mauricio.async.db.util.ChannelUtils.readLongInt +import com.github.mauricio.async.db.util.ChannelUtils.read4BytesInt import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer @@ -32,15 +32,20 @@ object MySQLFrameDecoder { class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { - private val handshakeDecoder = new HandshakeV10Decoder(charset) - private val errorDecoder = new ErrorDecoder(charset) + import MySQLFrameDecoder.log + + private final val handshakeDecoder = new HandshakeV10Decoder(charset) + private final val errorDecoder = new ErrorDecoder(charset) + private final val okDecoder = new OkDecoder(charset) def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { if (buffer.readableBytes() > 4) { + log.debug("Dumping request \n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) + buffer.markReaderIndex() - val size = readLongInt(buffer) + val size = read4BytesInt(buffer) val sequence = buffer.readByte() if (buffer.readableBytes() >= size) { @@ -51,6 +56,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val decoder = messageType match { case ServerMessage.ServerProtocolVersion => this.handshakeDecoder case ServerMessage.Error => this.errorDecoder + case ServerMessage.Ok => this.okDecoder case _ => { throw new ParserNotAvailableException(messageType) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala index 5a08d4d9..157819a8 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala @@ -33,6 +33,8 @@ object MySQLOneToOneEncoder { class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) extends OneToOneEncoder { + import MySQLOneToOneEncoder.log + private val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private var sequence = 1 @@ -49,6 +51,8 @@ class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) e ChannelUtils.writePacketLength(result, sequence) + log.debug("Dumping response {}\n{}", result.readableBytes(), MySQLHelper.dumpAsHex(result, result.readableBytes())) + sequence += 1 result diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala index 1fcacb49..e778d615 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala @@ -28,7 +28,7 @@ class ErrorDecoder( charset : Charset ) extends MessageDecoder { new ErrorMessage( buffer.readShort(), - buffer.readFixedString( 5, charset ), + buffer.readFixedString( 6, charset ), buffer.readUntilEOF(charset) ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala index 73ca7a76..0c5a16b6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala @@ -23,51 +23,65 @@ import org.jboss.netty.buffer.ChannelBuffer object HandshakeV10Decoder { val log = Log.get[HandshakeV10Decoder] + val SeedSize = 8 + val SeedComplementSize = 12 + val Padding = 10 } -class HandshakeV10Decoder( charset : Charset ) extends MessageDecoder { +class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { - import HandshakeV10Decoder.log + import HandshakeV10Decoder._ def decode(buffer: ChannelBuffer): ServerMessage = { val serverVersion = ChannelUtils.readCString(buffer, charset) val connectionId = buffer.readInt() - var seed = buffer.readSlice(8).toString(charset) - var serverCapabilityFlags : Int = buffer.readShort() - if ( buffer.readableBytes() > 0 ) { - val characterSet = buffer.readByte() & 0xff - val statusFlags = buffer.readShort() + var seed = new Array[Byte]( SeedSize + SeedComplementSize ) + buffer.readBytes(seed, 0, SeedSize) - serverCapabilityFlags += 65536 * buffer.readShort().asInstanceOf[Int] + // filler + buffer.readByte() - val authPluginDataLength = buffer.readByte() & 0xff - var authenticationMethod : Option[String] = None + var serverCapabilityFlags: Int = buffer.readShort() - if ( authPluginDataLength > 0 ) { - buffer.readerIndex( buffer.readerIndex() + 16 ) - seed += ChannelUtils.readUntilEOF(buffer, charset) - authenticationMethod = Some(ChannelUtils.readUntilEOF(buffer, charset)) + val characterSet = buffer.readByte() & 0xff + val statusFlags = buffer.readShort() + + serverCapabilityFlags += 65536 * buffer.readShort().asInstanceOf[Int] + + val authPluginDataLength = buffer.readUnsignedByte() + var authenticationMethod: Option[String] = None + + if (authPluginDataLength > 0) { + log.debug("Auth plugin data size {}", authPluginDataLength) + buffer.readerIndex(buffer.readerIndex() + Padding) + buffer.readBytes(seed, SeedSize, SeedComplementSize) + + var count = 0 + while ( count < seed.length ) { + if ( seed(count) == 0 ) { + log.debug("Index {} is zeroed", count) + } + count += 1 } - new HandshakeMessage( - serverVersion, - connectionId, - seed, - serverCapabilityFlags, - characterSet = Some(characterSet), - statusFlags = Some(statusFlags), - authenticationMethod = authenticationMethod - ) - } else { - new HandshakeMessage( - serverVersion, - connectionId, - seed, - serverCapabilityFlags ) + buffer.readByte() + authenticationMethod = Some(ChannelUtils.readUntilEOF(buffer, charset)) } + log.debug("Full seed is {}", new String( seed )) + log.debug("Method is {}", authenticationMethod) + + new HandshakeMessage( + serverVersion, + connectionId, + seed, + serverCapabilityFlags, + characterSet = Some(characterSet), + statusFlags = Some(statusFlags), + authenticationMethod = authenticationMethod + ) } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala new file mode 100644 index 00000000..841035de --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala @@ -0,0 +1,51 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.{OkMessage, ServerMessage} +import java.nio.charset.Charset +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper + +class OkDecoder( charset : Charset ) extends MessageDecoder { + + /* + 1 [00] the OK header + lenenc-int affected rows + lenenc-int last-insert-id + if capabilities & CLIENT_PROTOCOL_41 { + 2 status_flags + 2 warnings + } elseif capabilities & CLIENT_TRANSACTIONS { + 2 status_flags + } + string[EOF] info + */ + + def decode(buffer: ChannelBuffer): ServerMessage = { + + new OkMessage( + buffer.readBinaryLength, + buffer.readBinaryLength, + buffer.readShort(), + buffer.readShort(), + buffer.readUntilEOF(charset) + ) + + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala index 162b9374..d62e8086 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -21,40 +21,26 @@ import org.jboss.netty.buffer.ChannelBuffer import java.nio.charset.Charset import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.mysql.encoder.auth.MySQLNativePasswordAuthentication -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.{Log, ChannelUtils} import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException object HandshakeResponseEncoder { - final val CLIENT_COMPRESS = 32 - final val CLIENT_CONNECT_WITH_DB = 8 - final val CLIENT_FOUND_ROWS = 2 - final val CLIENT_LOCAL_FILES = 128 - /* Can use LOAD DATA LOCAL */ - final val CLIENT_LONG_FLAG = 4 - /* Get all column flags */ - final val CLIENT_LONG_PASSWORD = 1 - /* new more secure passwords */ - final val CLIENT_PROTOCOL_41 = 512 - // for > 4.1.1 - final val CLIENT_INTERACTIVE = 1024 - final val CLIENT_SSL = 2048 - final val CLIENT_TRANSACTIONS = 8192 - // Client knows about transactions - final val CLIENT_RESERVED = 16384 - // for 4.1.0 only - final val CLIENT_SECURE_CONNECTION = 32768 - final val CLIENT_MULTI_QUERIES = 65536 - // Enable/disable multiquery support - final val CLIENT_MULTI_RESULTS = 131072 - // Enable/disable multi-results + final val CLIENT_PROTOCOL_41 = 0x0200 + final val CLIENT_SECURE_CONNECTION = 0x8000 + final val CLIENT_CONNECT_WITH_DB = 0x0008 + final val CLIENT_TRANSACTIONS = 0x2000 + final val CLIENT_MULTI_RESULTS = 0x200000 + final val CLIENT_LONG_FLAG = 0x0001 final val CLIENT_PLUGIN_AUTH = 524288 - final val CLIENT_CAN_HANDLE_EXPIRED_PASSWORD = 4194304 - final val CLIENT_CONNECT_ATTRS = 1048576 - final val MAX_3_BYTES = 255 * 255 * 255 + + final val MAX_3_BYTES = 0x00ffffff final val PADDING: Array[Byte] = List.fill(23) { 0.toByte }.toArray + + final val log = Log.get[HandshakeResponseEncoder] + } class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) extends MessageEncoder { @@ -67,14 +53,14 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e val m = message.asInstanceOf[HandshakeResponseMessage] - var clientCapabilities = 0 | + var clientCapabilities = 0 + + clientCapabilities |= CLIENT_PLUGIN_AUTH | - CLIENT_LONG_PASSWORD | CLIENT_PROTOCOL_41 | CLIENT_TRANSACTIONS | CLIENT_MULTI_RESULTS | - CLIENT_SECURE_CONNECTION | - CLIENT_LONG_FLAG + CLIENT_SECURE_CONNECTION if (m.database.isDefined) { clientCapabilities |= CLIENT_CONNECT_WITH_DB @@ -107,9 +93,12 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e if ( m.authenticationMethod.isDefined ) { ChannelUtils.writeCString( m.authenticationMethod.get, buffer, charset ) + } else { + buffer.writeByte(0) } buffer + } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala index 162f95ed..b3b6f059 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala @@ -18,6 +18,6 @@ package com.github.mauricio.async.db.mysql.encoder.auth trait AuthenticationMethod { - def generateAuthentication( username : String, password : Option[String], seed : String ) : Array[Byte] + def generateAuthentication( username : String, password : Option[String], seed : Array[Byte] ) : Array[Byte] } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala index ac22fa2e..7d21797d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.mysql.encoder.auth import java.nio.charset.Charset import java.security.MessageDigest import org.jboss.netty.util.CharsetUtil +import com.github.mauricio.async.db.mysql.MySQLHelper object MySQLNativePasswordAuthentication { final val EmptyArray = Array.empty[Byte] @@ -28,17 +29,17 @@ class MySQLNativePasswordAuthentication( charset : Charset ) extends Authenticat import MySQLNativePasswordAuthentication.EmptyArray - def generateAuthentication(username: String, password: Option[String], seed : String): Array[Byte] = { + def generateAuthentication(username: String, password: Option[String], seed : Array[Byte]): Array[Byte] = { if ( password.isDefined ) { - scramble411(password.get, seed) + scramble411( password.get, seed ) } else { EmptyArray } } - private def scramble411( password : String, seed : String ) : Array[Byte] = { + private def scramble411( password : String, seed : Array[Byte] ) : Array[Byte] = { val messageDigest = MessageDigest.getInstance("SHA-1") val initialDigest = messageDigest.digest(password.getBytes(charset)) @@ -49,14 +50,14 @@ class MySQLNativePasswordAuthentication( charset : Charset ) extends Authenticat messageDigest.reset() - messageDigest.update(seed.getBytes(CharsetUtil.US_ASCII)) + messageDigest.update(seed) messageDigest.update(finalDigest) val result = messageDigest.digest() var counter = 0 while ( counter < result.length ) { - result(counter) = (result(counter) ^ finalDigest(counter)).asInstanceOf[Byte] + result(counter) = (result(counter) ^ initialDigest(counter)).asInstanceOf[Byte] counter += 1 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala index aded9bb2..77dfaee4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala @@ -21,7 +21,7 @@ import java.nio.charset.Charset case class HandshakeResponseMessage( username: String, charset: Charset, - seed: String, + seed: Array[Byte], authenticationMethod: Option[String] = None, password: Option[String] = None, database: Option[String] = None diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala index df2a961c..1bbcf12c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.mysql.message.server case class HandshakeMessage( serverVersion: String, connectionId: Int, - seed: String, + seed: Array[Byte], serverCapabilities: Int, characterSet: Option[Int] = None, statusFlags: Option[Short] = None, diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala new file mode 100644 index 00000000..911d0631 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.server + +case class OkMessage( affectedRows : Long, lastInsertId : Long, statusFlags : Int, warnings : Int, message : String ) + extends ServerMessage( ServerMessage.Ok ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala index 5c4f2eb1..406ab738 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala @@ -22,6 +22,7 @@ object ServerMessage { final val ServerProtocolVersion = 0x0a final val Error = 0xffffffff + final val Ok = 0 } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala index 2a932b4f..6f010c02 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala @@ -23,6 +23,14 @@ import com.github.mauricio.async.db.util.FutureUtils.await class MySQLConnectionSpec extends Specification { val configuration = new Configuration( + "mysql_async", + "localhost", + port = 3306, + password = Some("root"), + database = Some("mysql_async_tests") + ) + + val rootConfiguration = new Configuration( "root", "localhost", port = 3306, @@ -32,11 +40,28 @@ class MySQLConnectionSpec extends Specification { "connection" should { - "connect to a MySQL instance" in { - val connection = new MySQLConnection(configuration) - await( connection.connect ) === connection + "connect to a MySQL instance with a password" in { + val connection = new MySQLConnection(configuration) + await(connection.connect) === connection + } + + "connect to a MySQL instance without password" in { + val connection = new MySQLConnection(rootConfiguration) + await(connection.connect) === connection } } + def withConnection[T]( fn : (MySQLConnection) => T )( cfg : Configuration = configuration ) : T = { + + val connection = new MySQLConnection(cfg) + try { + fn(connection) + } finally { + connection.close + } + + + } + } \ No newline at end of file From 29bad97124ff54268bea0bfbbf1ecbc9f991cc49 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 7 May 2013 17:16:11 -0300 Subject: [PATCH 055/357] Implicitly wrapping ChannelFutures to simplify event handling --- .../CanceledChannelFutureException.scala | 22 +++++++++ .../db/util/ChannelFutureTransformer.scala | 49 +++++++++++++++++++ .../async/db/mysql/MySQLConnection.scala | 45 +++++++++-------- .../async/db/mysql/MySQLOneToOneEncoder.scala | 3 +- .../db/mysql/encoder/QuitMessageEncoder.scala | 31 ++++++++++++ .../mysql/message/client/ClientMessage.scala | 1 + .../db/mysql/message/client/QuitMessage.scala | 3 ++ .../async/db/mysql/MySQLConnectionSpec.scala | 2 +- 8 files changed, 134 insertions(+), 22 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala new file mode 100644 index 00000000..322db656 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala @@ -0,0 +1,22 @@ +/* +* 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.exceptions + +import org.jboss.netty.channel.ChannelFuture + +class CanceledChannelFutureException( val channelFuture : ChannelFuture ) + extends IllegalStateException ( "This channel future was canceled -> %s".format(channelFuture) ) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala new file mode 100644 index 00000000..cdd842db --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala @@ -0,0 +1,49 @@ +/* + * 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.util + +import org.jboss.netty.channel.{ChannelFutureListener, ChannelFuture} +import scala.concurrent.{Promise, Future} +import com.github.mauricio.async.db.exceptions.CanceledChannelFutureException + +object ChannelFutureTransformer { + + implicit def toFuture(channelFuture: ChannelFuture): Future[ChannelFuture] = { + val promise = Promise[ChannelFuture] + + channelFuture.addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if ( future.isSuccess ) { + promise.success(future) + } else { + val exception = if ( future.getCause == null ) { + new CanceledChannelFutureException(future) + .fillInStackTrace() + } else { + future.getCause + } + + promise.failure(exception) + + } + } + }) + + promise.future + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 90c55955..d41d2a17 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -19,15 +19,17 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.Configuration import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.bootstrap.ClientBootstrap -import scala.concurrent.{Promise, Future} +import scala.concurrent.{ExecutionContext, Promise, Future} import org.jboss.netty.channel._ import java.net.InetSocketAddress import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.mysql.message.server.{OkMessage, ErrorMessage, HandshakeMessage, ServerMessage} import scala.annotation.switch -import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, HandshakeResponseMessage} +import com.github.mauricio.async.db.mysql.message.client.{QuitMessage, ClientMessage, HandshakeResponseMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.mysql.exceptions.MySQLException +import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture +import scala.util.{Failure, Success} object MySQLConnection { val log = Log.get[MySQLConnection] @@ -45,6 +47,7 @@ class MySQLConnection( // validate that this charset is supported charsetMapper.toInt(configuration.charset) + private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) private val factory = new NioClientSocketChannelFactory( configuration.bossPool, configuration.workerPool) @@ -70,31 +73,33 @@ class MySQLConnection( this.bootstrap.setOption("child.tcpNoDelay", true) this.bootstrap.setOption("child.keepAlive", true) - this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { - if (!future.isSuccess) { - connectionPromise.failure(future.getCause) - } - } - }) + this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { + case exception => this.connectionPromise.failure(exception) + } this.connectionPromise.future } def close : Future[MySQLConnection] = { - val promise = Promise[MySQLConnection] - - this.currentContext.getChannel.close().addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { - if ( future.isSuccess ) { - promise.success(MySQLConnection.this) - } else { - promise.failure(future.getCause) + + if ( this.currentContext.getChannel.isConnected ) { + val promise = Promise[MySQLConnection] + + this.write( QuitMessage ).onComplete { + case Success( channelFuture ) => { + promise.success(this) + if ( this.currentContext.getChannel.isConnected ) { + this.currentContext.getChannel.close() + } } + case Failure(exception) => promise.failure(exception) } - }) - promise.future + promise.future + } else { + Promise.successful(this).future + } + } override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { @@ -165,7 +170,7 @@ class MySQLConnection( this.currentContext = null } - private def write( message : ClientMessage ) { + private def write( message : ClientMessage ) : ChannelFuture = { this.currentContext.getChannel.write(message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala index 157819a8..ffcb09fd 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException -import com.github.mauricio.async.db.mysql.encoder.HandshakeResponseEncoder +import com.github.mauricio.async.db.mysql.encoder.{QuitMessageEncoder, HandshakeResponseEncoder} import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.{ChannelUtils, Log} @@ -44,6 +44,7 @@ class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) e case message : ClientMessage => { val encoder = (message.kind : @switch) match { case ClientMessage.ClientProtocolVersion => this.handshakeResponseEncoder + case ClientMessage.Quit => QuitMessageEncoder case _ => throw new EncoderNotAvailableException(message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala new file mode 100644 index 00000000..f25a2764 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala @@ -0,0 +1,31 @@ +/* + * 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.mysql.encoder + +import com.github.mauricio.async.db.mysql.message.client.ClientMessage +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelUtils + +object QuitMessageEncoder extends MessageEncoder { + + def encode(message: ClientMessage): ChannelBuffer = { + val buffer = ChannelUtils.packetBuffer(5) + buffer.writeByte( ClientMessage.Quit ) + buffer + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala index 884ff2b3..d782ec8a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala @@ -21,6 +21,7 @@ import com.github.mauricio.async.db.KindedMessage object ClientMessage { final val ClientProtocolVersion = 0x09 + final val Quit = 0x01 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala new file mode 100644 index 00000000..7f796c3a --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala @@ -0,0 +1,3 @@ +package com.github.mauricio.async.db.mysql.message.client + +object QuitMessage extends ClientMessage( ClientMessage.Quit ) \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala index 6f010c02..5529f3aa 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala @@ -58,7 +58,7 @@ class MySQLConnectionSpec extends Specification { try { fn(connection) } finally { - connection.close + await( connection.close ) } From d1f52e16907caf3ee9cf42774785997f13570dcc Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 7 May 2013 22:52:30 -0300 Subject: [PATCH 056/357] Fixing issue with MySQL not sendind database --- .../mauricio/async/db/util/ChannelUtils.scala | 2 +- .../mauricio/async/db/mysql/MySQLHelper.java | 5 + .../async/db/mysql/MySQLConnection.scala | 125 +++++------------ .../db/mysql/MySQLConnectionHandler.scala | 126 ++++++++++++++++++ .../async/db/mysql/MySQLFrameDecoder.scala | 13 +- .../async/db/mysql/MySQLHandlerDelegate.scala | 31 +++++ .../async/db/mysql/MySQLOneToOneEncoder.scala | 9 +- .../db/mysql/decoder/EOFMessageDecoder.scala | 30 +++++ .../mysql/decoder/HandshakeV10Decoder.scala | 13 +- .../encoder/HandshakeResponseEncoder.scala | 2 - .../db/mysql/message/server/EOFMessage.scala | 20 +++ .../mysql/message/server/ServerMessage.scala | 1 + .../async/db/mysql/MySQLConnectionSpec.scala | 48 ++++++- 13 files changed, 302 insertions(+), 123 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnectionHandler.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLHandlerDelegate.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/EOFMessage.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala index 73072047..f387de59 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -94,7 +94,7 @@ object ChannelUtils { return result } - def read4BytesInt( b : ChannelBuffer ) : Int = { + def read3BytesInt( b : ChannelBuffer ) : Int = { (b.readByte() & 0xff) | ((b.readByte() & 0xff) << 8) | ((b.readByte() & 0xff) << 16) } diff --git a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java index 3197e8f3..c53ce4eb 100644 --- a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java +++ b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java @@ -19,6 +19,8 @@ public static final String dumpAsHex(ChannelBuffer buffer, int length) { for (int i = 0; (i < rows) && (p < length); i++) { int ptemp = p; + outputBuf.append(i + ": "); + for (int j = 0; j < 8; j++) { String hexVal = Integer.toHexString(byteBuffer[ptemp] & 0xff); @@ -47,6 +49,8 @@ public static final String dumpAsHex(ChannelBuffer buffer, int length) { outputBuf.append("\n"); //$NON-NLS-1$ } + outputBuf.append(rows + ": "); + int n = 0; for (int i = p; i < length; i++) { @@ -77,6 +81,7 @@ public static final String dumpAsHex(ChannelBuffer buffer, int length) { } outputBuf.append("\n"); //$NON-NLS-1$ + outputBuf.append("Total " + byteBuffer.length + " bytes read\n"); return outputBuf.toString(); } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index d41d2a17..96ae18a7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -17,18 +17,14 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.Configuration -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory -import org.jboss.netty.bootstrap.ClientBootstrap -import scala.concurrent.{ExecutionContext, Promise, Future} -import org.jboss.netty.channel._ -import java.net.InetSocketAddress -import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.mysql.message.server.{OkMessage, ErrorMessage, HandshakeMessage, ServerMessage} -import scala.annotation.switch -import com.github.mauricio.async.db.mysql.message.client.{QuitMessage, ClientMessage, HandshakeResponseMessage} -import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.mysql.exceptions.MySQLException +import com.github.mauricio.async.db.mysql.message.client.{QuitMessage, HandshakeResponseMessage} +import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} +import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture +import com.github.mauricio.async.db.util.Log +import org.jboss.netty.channel._ +import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.{Failure, Success} object MySQLConnection { @@ -38,8 +34,7 @@ object MySQLConnection { class MySQLConnection( configuration : Configuration, charsetMapper : CharsetMapper = CharsetMapper.Instance ) - extends SimpleChannelHandler - with LifeCycleAwareChannelHandler + extends MySQLHandlerDelegate { import MySQLConnection.log @@ -48,33 +43,16 @@ class MySQLConnection( charsetMapper.toInt(configuration.charset) private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) - private val factory = new NioClientSocketChannelFactory( - configuration.bossPool, - configuration.workerPool) - private val bootstrap = new ClientBootstrap(this.factory) - private val connectionPromise = Promise[MySQLConnection]() + private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this) + + private final val connectionPromise = Promise[MySQLConnection]() + private final val disconnectionPromise = Promise[MySQLConnection]() private var connected = false - private var currentContext : ChannelHandlerContext = null def connect: Future[MySQLConnection] = { - - this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - override def getPipeline(): ChannelPipeline = { - Channels.pipeline( - new MySQLFrameDecoder(configuration.charset), - new MySQLOneToOneEncoder(configuration.charset, charsetMapper), - MySQLConnection.this) - } - - }) - - this.bootstrap.setOption("child.tcpNoDelay", true) - this.bootstrap.setOption("child.keepAlive", true) - - this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { - case exception => this.connectionPromise.failure(exception) + this.connectionHandler.connect.onFailure { + case e => this.connectionPromise.tryFailure(e) } this.connectionPromise.future @@ -82,65 +60,46 @@ class MySQLConnection( def close : Future[MySQLConnection] = { - if ( this.currentContext.getChannel.isConnected ) { - val promise = Promise[MySQLConnection] - - this.write( QuitMessage ).onComplete { + if ( !this.disconnectionPromise.isCompleted ) { + this.connectionHandler.write( QuitMessage ).onComplete { case Success( channelFuture ) => { - promise.success(this) - if ( this.currentContext.getChannel.isConnected ) { - this.currentContext.getChannel.close() + this.connectionHandler.disconnect.onComplete { + case Success( closeFuture ) => this.disconnectionPromise.trySuccess(this) + case Failure(e) => this.disconnectionPromise.tryFailure(e) } } - case Failure(exception) => promise.failure(exception) + case Failure(exception) => this.disconnectionPromise.tryFailure(exception) } - - promise.future - } else { - Promise.successful(this).future } + this.disconnectionPromise.future } - override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { + override def connected( ctx : ChannelHandlerContext ) { log.debug("Connected to {}", ctx.getChannel.getRemoteAddress) this.connected = true } - override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { - log.error("Transport failure", e.getCause) + override def exceptionCaught(throwable : Throwable) { + log.error("Transport failure", throwable) if ( !this.connectionPromise.isCompleted ) { - this.connectionPromise.failure(e.getCause) + this.connectionPromise.failure(throwable) } } - override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) { - - e.getMessage match { - case m : ServerMessage => { - (m.kind : @switch) match { - case ServerMessage.ServerProtocolVersion => { - this.onHandshake( m.asInstanceOf[HandshakeMessage] ) - } - case ServerMessage.Ok => this.onOk(m.asInstanceOf[OkMessage]) - case ServerMessage.Error => this.onError(m.asInstanceOf[ErrorMessage]) - } - } - } - + override def onOk( message : OkMessage ) { + log.debug("Received OK {}", message) + this.connectionPromise.trySuccess(this) } - private def onOk( message : OkMessage ) { - log.debug("Received OK {}", message) - if ( !this.connectionPromise.isCompleted ) { - this.connectionPromise.success(this) - } + def onEOF(message: EOFMessage) { + log.debug("Received EOF message - {}", message) } - private def onHandshake( message : HandshakeMessage ) { + override def onHandshake( message : HandshakeMessage ) { log.debug("Received handshake message - {}", message) - this.write(new HandshakeResponseMessage( + this.connectionHandler.write(new HandshakeResponseMessage( configuration.username, configuration.charset, message.seed, @@ -150,28 +109,10 @@ class MySQLConnection( )) } - private def onError( message : ErrorMessage ) { + override def onError( message : ErrorMessage ) { val exception = new MySQLException(message) exception.fillInStackTrace() - if ( !this.connectionPromise.isCompleted ) { - this.connectionPromise.failure(exception) - } - } - - def beforeAdd(ctx: ChannelHandlerContext) {} - - def beforeRemove(ctx: ChannelHandlerContext) {} - - def afterAdd(ctx: ChannelHandlerContext) { - this.currentContext = ctx - } - - def afterRemove(ctx: ChannelHandlerContext) { - this.currentContext = null - } - - private def write( message : ClientMessage ) : ChannelFuture = { - this.currentContext.getChannel.write(message) + this.connectionPromise.tryFailure(exception) } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnectionHandler.scala new file mode 100644 index 00000000..6716a281 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnectionHandler.scala @@ -0,0 +1,126 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.mysql.message.client.ClientMessage +import com.github.mauricio.async.db.mysql.util.CharsetMapper +import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture +import java.net.InetSocketAddress +import org.jboss.netty.bootstrap.ClientBootstrap +import org.jboss.netty.channel._ +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory +import scala.concurrent.{ExecutionContext, Promise, Future} +import com.github.mauricio.async.db.mysql.message.server.{ErrorMessage, OkMessage, HandshakeMessage, ServerMessage} +import scala.annotation.switch +import com.github.mauricio.async.db.util.Log + +object MySQLConnectionHandler { + val log = Log.get[MySQLConnectionHandler] +} + +class MySQLConnectionHandler( + configuration : Configuration, + charsetMapper : CharsetMapper, + handlerDelegate : MySQLHandlerDelegate + ) + extends SimpleChannelHandler + with LifeCycleAwareChannelHandler { + + import MySQLConnectionHandler.log + + private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) + + private final val factory = new NioClientSocketChannelFactory( + configuration.bossPool, + configuration.workerPool) + + private final val bootstrap = new ClientBootstrap(this.factory) + private final val connectionPromise = Promise[MySQLConnectionHandler] + private var currentContext : ChannelHandlerContext = null + + def connect: Future[MySQLConnectionHandler] = { + + this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + + override def getPipeline(): ChannelPipeline = { + Channels.pipeline( + new MySQLFrameDecoder(configuration.charset), + new MySQLOneToOneEncoder(configuration.charset, charsetMapper), + MySQLConnectionHandler.this) + } + + }) + + this.bootstrap.setOption("child.tcpNoDelay", true) + this.bootstrap.setOption("child.keepAlive", true) + + this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { + case exception => this.connectionPromise.failure(exception) + } + + this.connectionPromise.future + } + + override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) { + + e.getMessage match { + case m : ServerMessage => { + (m.kind : @switch) match { + case ServerMessage.ServerProtocolVersion => { + handlerDelegate.onHandshake( m.asInstanceOf[HandshakeMessage] ) + } + case ServerMessage.Ok => handlerDelegate.onOk(m.asInstanceOf[OkMessage]) + case ServerMessage.Error => handlerDelegate.onError(m.asInstanceOf[ErrorMessage]) + } + } + } + + } + + override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { + log.debug("Connected to {}", ctx.getChannel.getRemoteAddress) + handlerDelegate.connected( ctx ) + } + + override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { + log.error("Transport failure", e.getCause) + if ( !this.connectionPromise.isCompleted ) { + this.connectionPromise.failure(e.getCause) + } + handlerDelegate.exceptionCaught( e.getCause ) + } + + def beforeAdd(ctx: ChannelHandlerContext) {} + + def beforeRemove(ctx: ChannelHandlerContext) {} + + def afterAdd(ctx: ChannelHandlerContext) { + this.currentContext = ctx + } + + def afterRemove(ctx: ChannelHandlerContext) {} + + def write( message : ClientMessage ) : ChannelFuture = { + this.currentContext.getChannel.write(message) + } + + def disconnect : ChannelFuture = { + this.currentContext.getChannel.close() + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala index 70ad644c..63f64104 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} -import com.github.mauricio.async.db.mysql.decoder.{OkDecoder, ErrorDecoder, HandshakeV10Decoder} +import com.github.mauricio.async.db.mysql.decoder.{EOFMessageDecoder, OkDecoder, ErrorDecoder, HandshakeV10Decoder} import com.github.mauricio.async.db.mysql.message.server.ServerMessage -import com.github.mauricio.async.db.util.ChannelUtils.read4BytesInt +import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer @@ -41,13 +41,13 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { if (buffer.readableBytes() > 4) { - log.debug("Dumping request \n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) - buffer.markReaderIndex() - val size = read4BytesInt(buffer) + val size = read3BytesInt(buffer) val sequence = buffer.readByte() + val requestDump = MySQLHelper.dumpAsHex(buffer, buffer.readableBytes()) + if (buffer.readableBytes() >= size) { val messageType = buffer.readByte() @@ -56,6 +56,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val decoder = messageType match { case ServerMessage.ServerProtocolVersion => this.handshakeDecoder case ServerMessage.Error => this.errorDecoder + case ServerMessage.EOF => EOFMessageDecoder case ServerMessage.Ok => this.okDecoder case _ => { throw new ParserNotAvailableException(messageType) @@ -64,6 +65,8 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val result = decoder.decode(slice) + log.debug(s"Server message is ${result.getClass.getName}\n${requestDump}") + if ( slice.readableBytes() != 0 ) { throw new BufferNotFullyConsumedException(slice) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLHandlerDelegate.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLHandlerDelegate.scala new file mode 100644 index 00000000..3cef4fe0 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLHandlerDelegate.scala @@ -0,0 +1,31 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} +import org.jboss.netty.channel.ChannelHandlerContext + +trait MySQLHandlerDelegate { + + def onHandshake( message : HandshakeMessage ) + def onError( message : ErrorMessage ) + def onOk( message : OkMessage ) + def onEOF( message : EOFMessage ) + def exceptionCaught( exception : Throwable ) + def connected( ctx : ChannelHandlerContext ) + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala index ffcb09fd..c2e9fb7a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala @@ -33,8 +33,6 @@ object MySQLOneToOneEncoder { class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) extends OneToOneEncoder { - import MySQLOneToOneEncoder.log - private val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private var sequence = 1 @@ -44,7 +42,10 @@ class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) e case message : ClientMessage => { val encoder = (message.kind : @switch) match { case ClientMessage.ClientProtocolVersion => this.handshakeResponseEncoder - case ClientMessage.Quit => QuitMessageEncoder + case ClientMessage.Quit => { + sequence = 0 + QuitMessageEncoder + } case _ => throw new EncoderNotAvailableException(message) } @@ -52,8 +53,6 @@ class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) e ChannelUtils.writePacketLength(result, sequence) - log.debug("Dumping response {}\n{}", result.readableBytes(), MySQLHelper.dumpAsHex(result, result.readableBytes())) - sequence += 1 result diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala new file mode 100644 index 00000000..14c93657 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala @@ -0,0 +1,30 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, ServerMessage} + +object EOFMessageDecoder extends MessageDecoder { + + def decode(buffer: ChannelBuffer): ServerMessage = { + new EOFMessage( + buffer.readUnsignedShort(), + buffer.readUnsignedShort() ) + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala index 0c5a16b6..fbf39872 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala @@ -40,7 +40,6 @@ class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { var seed = new Array[Byte]( SeedSize + SeedComplementSize ) buffer.readBytes(seed, 0, SeedSize) - // filler buffer.readByte() var serverCapabilityFlags: Int = buffer.readShort() @@ -54,24 +53,14 @@ class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { var authenticationMethod: Option[String] = None if (authPluginDataLength > 0) { - log.debug("Auth plugin data size {}", authPluginDataLength) buffer.readerIndex(buffer.readerIndex() + Padding) buffer.readBytes(seed, SeedSize, SeedComplementSize) - - var count = 0 - while ( count < seed.length ) { - if ( seed(count) == 0 ) { - log.debug("Index {} is zeroed", count) - } - count += 1 - } - buffer.readByte() authenticationMethod = Some(ChannelUtils.readUntilEOF(buffer, charset)) } log.debug("Full seed is {}", new String( seed )) - log.debug("Method is {}", authenticationMethod) + log.debug(s"Method is ${authenticationMethod} - readable bytes ${buffer.readableBytes()}") new HandshakeMessage( serverVersion, diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala index d62e8086..1d41c22b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -87,8 +87,6 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e if ( m.database.isDefined ) { ChannelUtils.writeCString( m.database.get, buffer, charset ) - } else { - buffer.writeByte(0) } if ( m.authenticationMethod.isDefined ) { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/EOFMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/EOFMessage.scala new file mode 100644 index 00000000..bd7d767c --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/EOFMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.server + +case class EOFMessage( warningCount : Int, flags : Int ) + extends ServerMessage( ServerMessage.EOF ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala index 406ab738..ec49f4e4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala @@ -23,6 +23,7 @@ object ServerMessage { final val ServerProtocolVersion = 0x0a final val Error = 0xffffffff final val Ok = 0 + final val EOF = 0xfffffffe } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala index 5529f3aa..00845b97 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala @@ -38,27 +38,63 @@ class MySQLConnectionSpec extends Specification { database = Some("mysql_async_tests") ) + val configurationWithoutDatabase = new Configuration( + "root", + "localhost", + port = 3306, + password = None, + database = Some("mysql_async_tests") + ) + + val configurationWithPasswordWithoutDatabase = new Configuration( + "mysql_async", + "localhost", + port = 3306, + password = Some("root"), + database = None + ) + "connection" should { "connect to a MySQL instance with a password" in { - val connection = new MySQLConnection(configuration) - await(connection.connect) === connection + + withNonConnectedConnection { + connection => + await(connection.connect) === connection + }(configuration) + } "connect to a MySQL instance without password" in { - val connection = new MySQLConnection(rootConfiguration) - await(connection.connect) === connection + withNonConnectedConnection({ + connection => + await(connection.connect) === connection + }) (rootConfiguration) + } + + "connect to a MySQL instance without a database" in { + withNonConnectedConnection({ + connection => + await(connection.connect) === connection + }) (configurationWithoutDatabase) + } + + "connect to a MySQL instance without database with password" in { + withNonConnectedConnection({ + connection => + await(connection.connect) === connection + }) (configurationWithPasswordWithoutDatabase) } } - def withConnection[T]( fn : (MySQLConnection) => T )( cfg : Configuration = configuration ) : T = { + def withNonConnectedConnection[T](fn: (MySQLConnection) => T)(cfg: Configuration = configuration): T = { val connection = new MySQLConnection(cfg) try { fn(connection) } finally { - await( connection.close ) + await(connection.close) } From 49058c28723963fa2d3f6a3e8835cae679bfa982 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 8 May 2013 13:06:34 -0300 Subject: [PATCH 057/357] Fixing issue with bad encoding being set on MySQL --- .../mauricio/async/db/QueryResult.scala | 2 +- .../async/db/util/ChannelWrapper.scala | 5 + .../async/db/mysql/MySQLConnection.scala | 72 ++++++++- .../async/db/mysql/MySQLFrameDecoder.scala | 85 ---------- .../async/db/mysql/MySQLQueryResult.scala | 27 ++++ .../{ => codec}/MySQLConnectionHandler.scala | 58 +++++-- .../db/mysql/codec/MySQLFrameDecoder.scala | 150 ++++++++++++++++++ .../{ => codec}/MySQLHandlerDelegate.scala | 2 +- .../{ => codec}/MySQLOneToOneEncoder.scala | 15 +- .../decoder/ColumnDefinitionDecoder.scala | 60 +++++++ .../ColumnProcessingFinishedDecoder.scala | 28 ++++ .../db/mysql/decoder/EOFMessageDecoder.scala | 2 +- .../mysql/decoder/HandshakeV10Decoder.scala | 11 +- .../async/db/mysql/decoder/OkDecoder.scala | 13 -- .../mysql/decoder/ResultSetRowDecoder.scala | 47 ++++++ .../mysql/encoder/QueryMessageEncoder.scala | 37 +++++ .../db/mysql/exceptions/MySQLException.scala | 2 +- .../mysql/message/client/ClientMessage.scala | 1 + .../mysql/message/client/QueryMessage.scala | 19 +++ .../server/ColumnDefinitionMessage.scala | 32 ++++ .../ColumnProcessingFinishedMessage.scala | 19 +++ .../message/server/ResultSetRowMessage.scala | 61 +++++++ .../mysql/message/server/ServerMessage.scala | 13 +- .../async/db/mysql/util/CharsetMapper.scala | 11 +- mysql-async/src/test/resources/logback.xml | 4 +- .../async/db/mysql/ConnectionHelper.scala | 49 ++++++ .../github/mauricio/async/db/mysql/Main.scala | 17 ++ .../mauricio/async/db/mysql/QuerySpec.scala | 70 ++++++++ 28 files changed, 773 insertions(+), 139 deletions(-) delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala rename mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/{ => codec}/MySQLConnectionHandler.scala (64%) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala rename mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/{ => codec}/MySQLHandlerDelegate.scala (95%) rename mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/{ => codec}/MySQLOneToOneEncoder.scala (77%) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QueryMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnProcessingFinishedMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/QueryResult.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/QueryResult.scala index 89c9dca9..ed84f56d 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/QueryResult.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/QueryResult.scala @@ -25,7 +25,7 @@ package com.github.mauricio.async.db * @param rows */ -class QueryResult(val rowsAffected: Int, val statusMessage: String, val rows: Option[ResultSet]) { +class QueryResult(val rowsAffected: Long, val statusMessage: String, val rows: Option[ResultSet] = None) { override def toString: String = { "QueryResult{rows -> %s,status -> %s}".format(this.rowsAffected, this.statusMessage) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index 46fa1db0..d9d414f6 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -54,6 +54,11 @@ class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { i } + def readLengthEncodedString( charset : Charset ) : String = { + val length = readBinaryLength + readFixedString(length.asInstanceOf[Int], charset) + } + def readBinaryLength : Long = { val firstByte = buffer.readUnsignedByte() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 96ae18a7..603b6347 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.mysql -import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.{QueryResult, Configuration} import com.github.mauricio.async.db.mysql.exceptions.MySQLException -import com.github.mauricio.async.db.mysql.message.client.{QuitMessage, HandshakeResponseMessage} +import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage, QuitMessage, HandshakeResponseMessage} import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture @@ -26,6 +26,7 @@ import com.github.mauricio.async.db.util.Log import org.jboss.netty.channel._ import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.{Failure, Success} +import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} object MySQLConnection { val log = Log.get[MySQLConnection] @@ -48,6 +49,7 @@ class MySQLConnection( private final val connectionPromise = Promise[MySQLConnection]() private final val disconnectionPromise = Promise[MySQLConnection]() + private var queryPromise : Promise[QueryResult] = null private var connected = false def connect: Future[MySQLConnection] = { @@ -82,18 +84,42 @@ class MySQLConnection( override def exceptionCaught(throwable : Throwable) { log.error("Transport failure", throwable) - if ( !this.connectionPromise.isCompleted ) { - this.connectionPromise.failure(throwable) - } + + this.connectionPromise.tryFailure(throwable) + this.failQueryPromise(throwable) } override def onOk( message : OkMessage ) { log.debug("Received OK {}", message) this.connectionPromise.trySuccess(this) + + if ( this.isQuerying ) { + this.succeedQueryPromise( + new MySQLQueryResult( + message.affectedRows, + message.message, + message.lastInsertId, + message.statusFlags, + message.warnings + ) + ) + } + } def onEOF(message: EOFMessage) { log.debug("Received EOF message - {}", message) + if ( this.isQuerying ) { + this.succeedQueryPromise( + new MySQLQueryResult( + 0, + null, + -1, + message.flags, + message.warningCount + ) + ) + } } override def onHandshake( message : HandshakeMessage ) { @@ -110,9 +136,45 @@ class MySQLConnection( } override def onError( message : ErrorMessage ) { + log.error("Received an error message -> {}", message) val exception = new MySQLException(message) exception.fillInStackTrace() this.connectionPromise.tryFailure(exception) + this.failQueryPromise(exception) } + def sendQuery(query: String): Future[QueryResult] = { + this.queryPromise = Promise[QueryResult] + this.write( new QueryMessage(query) ) + this.queryPromise.future + } + + private def write( message : ClientMessage ) : ChannelFuture = { + this.connectionHandler.write(message) + } + + private def failQueryPromise( t : Throwable ) { + + if ( this.isQuerying ) { + val promise = this.queryPromise + this.queryPromise = null + + promise.tryFailure(t) + } + + } + + private def succeedQueryPromise( queryResult : QueryResult ) { + + if ( this.isQuerying ) { + val promise = this.queryPromise + this.queryPromise = null + + promise.trySuccess(queryResult) + } + + } + + private def isQuerying : Boolean = this.queryPromise != null + } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala deleted file mode 100644 index 63f64104..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLFrameDecoder.scala +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.mysql - -import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} -import com.github.mauricio.async.db.mysql.decoder.{EOFMessageDecoder, OkDecoder, ErrorDecoder, HandshakeV10Decoder} -import com.github.mauricio.async.db.mysql.message.server.ServerMessage -import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt -import com.github.mauricio.async.db.util.Log -import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer -import org.jboss.netty.channel.{Channel, ChannelHandlerContext} -import org.jboss.netty.handler.codec.frame.FrameDecoder - -object MySQLFrameDecoder { - val log = Log.get[MySQLFrameDecoder] -} - -class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { - - import MySQLFrameDecoder.log - - private final val handshakeDecoder = new HandshakeV10Decoder(charset) - private final val errorDecoder = new ErrorDecoder(charset) - private final val okDecoder = new OkDecoder(charset) - - def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { - if (buffer.readableBytes() > 4) { - - buffer.markReaderIndex() - - val size = read3BytesInt(buffer) - val sequence = buffer.readByte() - - val requestDump = MySQLHelper.dumpAsHex(buffer, buffer.readableBytes()) - - if (buffer.readableBytes() >= size) { - val messageType = buffer.readByte() - - val slice = buffer.readSlice(size - 1) - - val decoder = messageType match { - case ServerMessage.ServerProtocolVersion => this.handshakeDecoder - case ServerMessage.Error => this.errorDecoder - case ServerMessage.EOF => EOFMessageDecoder - case ServerMessage.Ok => this.okDecoder - case _ => { - throw new ParserNotAvailableException(messageType) - } - } - - val result = decoder.decode(slice) - - log.debug(s"Server message is ${result.getClass.getName}\n${requestDump}") - - if ( slice.readableBytes() != 0 ) { - throw new BufferNotFullyConsumedException(slice) - } - - return result - - } else { - buffer.resetReaderIndex() - } - - } - - return null - } - -} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala new file mode 100644 index 00000000..7b9cfe57 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala @@ -0,0 +1,27 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.{ResultSet, QueryResult} + +class MySQLQueryResult( + rowsAffected: Long, + message: String, + lastInsertId: Long, + statusFlags: Int, + warnings: Int, + rows: Option[ResultSet] = None) extends QueryResult(rowsAffected, message, rows) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala similarity index 64% rename from mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnectionHandler.scala rename to mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 6716a281..a821e460 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -14,20 +14,25 @@ * under the License. */ -package com.github.mauricio.async.db.mysql +package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.mysql.message.client.ClientMessage +import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage} +import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture +import com.github.mauricio.async.db.util.Log import java.net.InetSocketAddress import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.channel._ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory -import scala.concurrent.{ExecutionContext, Promise, Future} -import com.github.mauricio.async.db.mysql.message.server.{ErrorMessage, OkMessage, HandshakeMessage, ServerMessage} import scala.annotation.switch -import com.github.mauricio.async.db.util.Log +import scala.concurrent.{ExecutionContext, Promise, Future} +import scala.collection.mutable.ArrayBuffer +import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage +import com.github.mauricio.async.db.mysql.message.server.ErrorMessage +import com.github.mauricio.async.db.mysql.message.client.QueryMessage +import com.github.mauricio.async.db.mysql.message.server.OkMessage object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -51,6 +56,10 @@ class MySQLConnectionHandler( private final val bootstrap = new ClientBootstrap(this.factory) private final val connectionPromise = Promise[MySQLConnectionHandler] + private final val decoder = new MySQLFrameDecoder(configuration.charset) + private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) + private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() + private final val currentRows = new ArrayBuffer[ResultSetRowMessage]() private var currentContext : ChannelHandlerContext = null def connect: Future[MySQLConnectionHandler] = { @@ -59,8 +68,8 @@ class MySQLConnectionHandler( override def getPipeline(): ChannelPipeline = { Channels.pipeline( - new MySQLFrameDecoder(configuration.charset), - new MySQLOneToOneEncoder(configuration.charset, charsetMapper), + decoder, + encoder, MySQLConnectionHandler.this) } @@ -84,8 +93,29 @@ class MySQLConnectionHandler( case ServerMessage.ServerProtocolVersion => { handlerDelegate.onHandshake( m.asInstanceOf[HandshakeMessage] ) } - case ServerMessage.Ok => handlerDelegate.onOk(m.asInstanceOf[OkMessage]) - case ServerMessage.Error => handlerDelegate.onError(m.asInstanceOf[ErrorMessage]) + case ServerMessage.Ok => { + this.clearQueryState + handlerDelegate.onOk(m.asInstanceOf[OkMessage]) + } + case ServerMessage.Error => { + this.clearQueryState + handlerDelegate.onError(m.asInstanceOf[ErrorMessage]) + } + case ServerMessage.EOF => { + this.clearQueryState + handlerDelegate.onEOF(m.asInstanceOf[EOFMessage]) + } + case ServerMessage.ColumnDefinition => { + log.debug("Received column definition - {}", m) + this.currentColumns += m.asInstanceOf[ColumnDefinitionMessage] + } + case ServerMessage.ColumnDefinitionFinished => { + log.debug("Column processing finished, waiting for rows now -> {}", m) + } + case ServerMessage.Row => { + log.debug("Received row - {}", m) + this.currentRows += m.asInstanceOf[ResultSetRowMessage] + } } } } @@ -93,12 +123,10 @@ class MySQLConnectionHandler( } override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { - log.debug("Connected to {}", ctx.getChannel.getRemoteAddress) handlerDelegate.connected( ctx ) } override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { - log.error("Transport failure", e.getCause) if ( !this.connectionPromise.isCompleted ) { this.connectionPromise.failure(e.getCause) } @@ -116,6 +144,9 @@ class MySQLConnectionHandler( def afterRemove(ctx: ChannelHandlerContext) {} def write( message : ClientMessage ) : ChannelFuture = { + if ( message.kind == ClientMessage.Query ) { + this.decoder.queryProcessStarted() + } this.currentContext.getChannel.write(message) } @@ -123,4 +154,9 @@ class MySQLConnectionHandler( this.currentContext.getChannel.close() } + private def clearQueryState { + this.currentColumns.clear() + this.currentRows.clear() + } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala new file mode 100644 index 00000000..1c1173dd --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -0,0 +1,150 @@ +/* + * 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.mysql.codec + +import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} +import com.github.mauricio.async.db.mysql.MySQLHelper +import com.github.mauricio.async.db.mysql.decoder._ +import com.github.mauricio.async.db.mysql.message.server.ServerMessage +import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt +import com.github.mauricio.async.db.util.Log +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer +import org.jboss.netty.channel.{Channel, ChannelHandlerContext} +import org.jboss.netty.handler.codec.frame.FrameDecoder +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper + +object MySQLFrameDecoder { + val log = Log.get[MySQLFrameDecoder] +} + +class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { + + import MySQLFrameDecoder.log + + private final val handshakeDecoder = new HandshakeV10Decoder(charset) + private final val errorDecoder = new ErrorDecoder(charset) + private final val okDecoder = new OkDecoder(charset) + private final val columnDecoder = new ColumnDefinitionDecoder(charset) + private final val rowDecoder = new ResultSetRowDecoder(charset) + + private var processingColumns = false + private var isInQuery = false + private var totalColumns: Long = 0 + private var processedColumns: Long = 0 + + def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { + if (buffer.readableBytes() > 4) { + + buffer.markReaderIndex() + + val size = read3BytesInt(buffer) + val sequence = buffer.readByte() + + if (buffer.readableBytes() >= size) { + + val messageType = buffer.getByte(buffer.readerIndex()) + + val slice = buffer.readSlice(size) + + val requestDump = MySQLHelper.dumpAsHex(slice, slice.readableBytes()) + log.debug(s"Server message is type ${"%02x".format(messageType)} ( $messageType - $size bytes)\n${requestDump}") + + // removing initial kind byte so that we can switch + // on known messages but add it back if this is a query process + slice.readByte() + + val decoder = messageType match { + case ServerMessage.ServerProtocolVersion => this.handshakeDecoder + case ServerMessage.Error => { + this.clear + this.errorDecoder + } + case ServerMessage.EOF => { + if ( this.processingColumns ) { + this.processingColumns = false + ColumnProcessingFinishedDecoder + } else { + log.debug("Got EOF {} - {}", totalColumns, processedColumns) + this.clear + EOFMessageDecoder + } + } + case ServerMessage.Ok => { + this.clear + this.okDecoder + } + case _ => { + + if ( this.isInQuery ) { + null + } else { + throw new ParserNotAvailableException(messageType) + } + + } + } + + if ( decoder == null ) { + slice.readerIndex( slice.readerIndex() - 1 ) + return decodeQueryResult(slice, buffer) + } else { + val result = decoder.decode(slice) + + if (slice.readableBytes() != 0) { + throw new BufferNotFullyConsumedException(slice) + } + + return result + } + + } else { + buffer.resetReaderIndex() + } + + } + + return null + } + + def queryProcessStarted() { + this.isInQuery = true + this.processingColumns = true + } + + private def decodeQueryResult( slice : ChannelBuffer, buffer : ChannelBuffer ) : AnyRef = { + if ( this.totalColumns == 0 ) { + this.totalColumns = slice.readBinaryLength + return null + } else { + if ( this.totalColumns == this.processedColumns ) { + this.rowDecoder.decode(slice) + } else { + this.processedColumns += 1 + this.columnDecoder.decode(slice) + } + } + } + + private def clear { + this.isInQuery = false + this.processingColumns = false + this.totalColumns = 0 + this.processedColumns = 0 + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLHandlerDelegate.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala similarity index 95% rename from mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLHandlerDelegate.scala rename to mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala index 3cef4fe0..2f7aba03 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLHandlerDelegate.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.mysql +package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} import org.jboss.netty.channel.ChannelHandlerContext diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala similarity index 77% rename from mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala rename to mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index c2e9fb7a..127535fb 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -14,10 +14,10 @@ * under the License. */ -package com.github.mauricio.async.db.mysql +package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException -import com.github.mauricio.async.db.mysql.encoder.{QuitMessageEncoder, HandshakeResponseEncoder} +import com.github.mauricio.async.db.mysql.encoder.{QueryMessageEncoder, QuitMessageEncoder, HandshakeResponseEncoder} import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.{ChannelUtils, Log} @@ -31,21 +31,26 @@ object MySQLOneToOneEncoder { val log = Log.get[MySQLOneToOneEncoder] } -class MySQLOneToOneEncoder( charset : Charset, charsetMapper : CharsetMapper ) extends OneToOneEncoder { +class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends OneToOneEncoder { private val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) + private final val queryEncoder = new QueryMessageEncoder(charset) private var sequence = 1 def encode(ctx: ChannelHandlerContext, channel: Channel, msg: Any): ChannelBuffer = { msg match { - case message : ClientMessage => { - val encoder = (message.kind : @switch) match { + case message: ClientMessage => { + val encoder = (message.kind: @switch) match { case ClientMessage.ClientProtocolVersion => this.handshakeResponseEncoder case ClientMessage.Quit => { sequence = 0 QuitMessageEncoder } + case ClientMessage.Query => { + sequence = 0 + this.queryEncoder + } case _ => throw new EncoderNotAvailableException(message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala new file mode 100644 index 00000000..dd12f56b --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala @@ -0,0 +1,60 @@ +/* + * 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.mysql.decoder + +import com.github.mauricio.async.db.mysql.message.server.{ColumnDefinitionMessage, ServerMessage} +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer + +class ColumnDefinitionDecoder(charset: Charset) extends MessageDecoder { + + override def decode(buffer: ChannelBuffer): ColumnDefinitionMessage = { + + val catalog = buffer.readLengthEncodedString(charset) + val schema = buffer.readLengthEncodedString(charset) + val table = buffer.readLengthEncodedString(charset) + val originalTable = buffer.readLengthEncodedString(charset) + val name = buffer.readLengthEncodedString(charset) + val originalName = buffer.readLengthEncodedString(charset) + + buffer.readBinaryLength + + val characterSet = buffer.readUnsignedShort() + val columnLength = buffer.readUnsignedInt() + val columnType = buffer.readByte() + val flags = buffer.readShort() + val decimals = buffer.readByte() + + buffer.readShort() + + new ColumnDefinitionMessage( + catalog, + schema, + table, + originalTable, + name, + originalName, + characterSet, + columnLength, + columnType, + flags, + decimals + ) + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala new file mode 100644 index 00000000..ad5f82ce --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.{ColumnProcessingFinishedMessage, ServerMessage} + +object ColumnProcessingFinishedDecoder extends MessageDecoder { + + def decode(buffer: ChannelBuffer): ServerMessage = { + new ColumnProcessingFinishedMessage( EOFMessageDecoder.decode(buffer) ) + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala index 14c93657..7407c4fe 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, ServerMess object EOFMessageDecoder extends MessageDecoder { - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ChannelBuffer): EOFMessage = { new EOFMessage( buffer.readUnsignedShort(), buffer.readUnsignedShort() ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala index fbf39872..e6a25fd0 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala @@ -22,10 +22,10 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer object HandshakeV10Decoder { - val log = Log.get[HandshakeV10Decoder] - val SeedSize = 8 - val SeedComplementSize = 12 - val Padding = 10 + final val log = Log.get[HandshakeV10Decoder] + final val SeedSize = 8 + final val SeedComplementSize = 12 + final val Padding = 10 } class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { @@ -59,9 +59,6 @@ class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { authenticationMethod = Some(ChannelUtils.readUntilEOF(buffer, charset)) } - log.debug("Full seed is {}", new String( seed )) - log.debug(s"Method is ${authenticationMethod} - readable bytes ${buffer.readableBytes()}") - new HandshakeMessage( serverVersion, connectionId, diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala index 841035de..c0d45bd5 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala @@ -23,19 +23,6 @@ import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper class OkDecoder( charset : Charset ) extends MessageDecoder { - /* - 1 [00] the OK header - lenenc-int affected rows - lenenc-int last-insert-id - if capabilities & CLIENT_PROTOCOL_41 { - 2 status_flags - 2 warnings - } elseif capabilities & CLIENT_TRANSACTIONS { - 2 status_flags - } - string[EOF] info - */ - def decode(buffer: ChannelBuffer): ServerMessage = { new OkMessage( diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala new file mode 100644 index 00000000..0ca4c179 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala @@ -0,0 +1,47 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.{ResultSetRowMessage, ServerMessage} +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import java.nio.charset.Charset + +object ResultSetRowDecoder { + + final val NULL = 0xfb + +} + +class ResultSetRowDecoder( charset : Charset ) extends MessageDecoder { + + import ResultSetRowDecoder.NULL + + def decode(buffer: ChannelBuffer): ServerMessage = { + val row = new ResultSetRowMessage() + + while ( buffer.readable() ) { + if ( buffer.getByte(buffer.readerIndex()) == NULL ) { + row += null + } else { + row += buffer.readLengthEncodedString(charset) + } + } + + row + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala new file mode 100644 index 00000000..5188b832 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala @@ -0,0 +1,37 @@ +/* + * 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.mysql.encoder + +import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage} +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelUtils +import java.nio.charset.Charset + +class QueryMessageEncoder( charset : Charset ) extends MessageEncoder { + + def encode(message: ClientMessage): ChannelBuffer = { + + val m = message.asInstanceOf[QueryMessage] + val encodedQuery = m.query.getBytes( charset ) + val buffer = ChannelUtils.packetBuffer(4 + 1 + encodedQuery.length ) + buffer.writeByte( ClientMessage.Query ) + buffer.writeBytes( encodedQuery ) + + buffer + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala index b6f079e4..c8209f17 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/exceptions/MySQLException.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.mysql.exceptions import com.github.mauricio.async.db.exceptions.DatabaseException import com.github.mauricio.async.db.mysql.message.server.ErrorMessage -class MySQLException( errorMessage : ErrorMessage ) +class MySQLException( val errorMessage : ErrorMessage ) extends DatabaseException("Error %d - %s - %s".format(errorMessage.errorCode, errorMessage.sqlState, errorMessage.errorMessage)) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala index d782ec8a..8272238f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala @@ -22,6 +22,7 @@ object ClientMessage { final val ClientProtocolVersion = 0x09 final val Quit = 0x01 + final val Query = 0x03 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QueryMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QueryMessage.scala new file mode 100644 index 00000000..ce8d3768 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QueryMessage.scala @@ -0,0 +1,19 @@ +/* + * 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.mysql.message.client + +case class QueryMessage( query : String ) extends ClientMessage( ClientMessage.Query ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala new file mode 100644 index 00000000..e95efaf4 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -0,0 +1,32 @@ +/* + * 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.mysql.message.server + +case class ColumnDefinitionMessage( + catalog: String, + schema: String, + table: String, + originalTable: String, + name: String, + originalName: String, + characterSet: Int, + columnLength: Long, + columnType: Byte, + flags: Short, + decimals: Byte + ) + extends ServerMessage(ServerMessage.ColumnDefinition) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnProcessingFinishedMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnProcessingFinishedMessage.scala new file mode 100644 index 00000000..df3e63e5 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnProcessingFinishedMessage.scala @@ -0,0 +1,19 @@ +/* + * 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.mysql.message.server + +case class ColumnProcessingFinishedMessage( eofMessage : EOFMessage ) extends ServerMessage( ServerMessage.ColumnDefinitionFinished ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala new file mode 100644 index 00000000..f9153500 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala @@ -0,0 +1,61 @@ +/* + * 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.mysql.message.server + +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable + +class ResultSetRowMessage + extends ServerMessage( ServerMessage.Row ) + with mutable.Buffer[Any] +{ + + private val buffer = new ArrayBuffer[Any]() + + def length: Int = buffer.length + + def apply(idx: Int): Any = buffer(idx) + + def update(n: Int, newelem: Any) { + buffer.update(n, newelem) + } + + def +=(elem: Any): this.type = { + this.buffer += elem + this + } + + def clear() { + this.buffer.clear() + } + + def +=:(elem: Any): this.type = { + this.buffer.+=:(elem) + this + } + + def insertAll(n: Int, elems: Traversable[Any]) { + this.buffer.insertAll(n, elems) + } + + def remove(n: Int): Any = { + this.buffer.remove(n) + } + + def iterator: Iterator[Any] = this.buffer.iterator + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala index ec49f4e4..6b35e51b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala @@ -12,6 +12,7 @@ * 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.message.server @@ -20,10 +21,16 @@ import com.github.mauricio.async.db.KindedMessage object ServerMessage { - final val ServerProtocolVersion = 0x0a - final val Error = 0xffffffff + final val ServerProtocolVersion = 10 + final val Error = -1 final val Ok = 0 - final val EOF = 0xfffffffe + final val EOF = -2 + + // these messages don't actually exist + // but we use them to simplify the switch statements + final val ColumnDefinition = 100 + final val ColumnDefinitionFinished = 101 + final val Row = 102 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala index 2e9404e1..416663a0 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala @@ -24,12 +24,15 @@ object CharsetMapper { val Instance = new CharsetMapper() } -class CharsetMapper { +class CharsetMapper( charsetsToIntComplement : Map[Charset,Int] = Map.empty[Charset,Int] ) { private var charsetsToInt = Map[Charset,Int]( - CharsetUtil.UTF_8 -> 3, - CharsetUtil.US_ASCII -> 1 - ) + CharsetUtil.UTF_8 -> 83, + CharsetUtil.US_ASCII -> 11, + CharsetUtil.US_ASCII -> 65, + CharsetUtil.ISO_8859_1 -> 3, + CharsetUtil.ISO_8859_1 -> 69 + ) ++ charsetsToIntComplement def toInt( charset : Charset ) : Int = { charsetsToInt.getOrElse(charset, { diff --git a/mysql-async/src/test/resources/logback.xml b/mysql-async/src/test/resources/logback.xml index 11413563..3d3f5dad 100644 --- a/mysql-async/src/test/resources/logback.xml +++ b/mysql-async/src/test/resources/logback.xml @@ -2,14 +2,14 @@ - [%level][%thread][%d] %msg%ex%n + [%level][%thread][%d][%c{5}] %msg%ex%n target/mysql-async-tests.log - [%level][%thread][%d] %msg%ex%n + [%level][%thread][%d][%c{5}] %msg%ex%n diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala new file mode 100644 index 00000000..47eeb0db --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -0,0 +1,49 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.{QueryResult, Configuration} +import com.github.mauricio.async.db.util.FutureUtils.await + +trait ConnectionHelper { + + def defaultConfiguration = new Configuration( + "mysql_async", + "localhost", + port = 3306, + password = Some("root"), + database = Some("mysql_async_tests") + ) + + def withConnection[T]( fn : (MySQLConnection) => T ) : T = { + + val connection = new MySQLConnection(this.defaultConfiguration) + + try { + await( connection.connect ) + fn(connection) + } finally { + await( connection.close ) + } + + } + + def executeQuery( connection : MySQLConnection, query : String ) : QueryResult = { + await( connection.sendQuery(query) ) + } + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala new file mode 100644 index 00000000..1b301a2b --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala @@ -0,0 +1,17 @@ +package com.github.mauricio.async.db.mysql + +import org.jboss.netty.util.CharsetUtil +import org.jboss.netty.buffer.ChannelBuffers +import java.nio.charset.Charset + +object Main { + + def main(args: Array[String]) { + + val name = "Maurício Aragão".getBytes(CharsetUtil.ISO_8859_1) + val result = MySQLHelper.dumpAsHex(ChannelBuffers.wrappedBuffer(name), name.length) + + println(result) + } + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala new file mode 100644 index 00000000..32fdf9c0 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -0,0 +1,70 @@ +/* + * 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.mysql + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.mysql.exceptions.MySQLException +import org.jboss.netty.util.CharsetUtil +import org.jboss.netty.buffer.ChannelBuffers + +class QuerySpec extends Specification with ConnectionHelper { + + final val createTable = """CREATE TEMPORARY TABLE users ( + id INT NOT NULL AUTO_INCREMENT , + name VARCHAR(255) CHARACTER SET 'utf8' NOT NULL , + PRIMARY KEY (id) );""" + final val insert = """INSERT INTO users (name) VALUES ('Maurício Aragão')""" + final val select = """SELECT * FROM users""" + + "connection" should { + + "be able to run a DML query" in { + + withConnection { + connection => + executeQuery( connection, this.createTable ).rowsAffected === 0 + } + + } + + "raise an exception upon a bad statement" in { + withConnection { + connection => + executeQuery(connection, "this is not SQL") must throwA[MySQLException].like { + case e => e.asInstanceOf[MySQLException].errorMessage.sqlState === "#42000" + } + } + } + + "be able to select from a table" in { + + withConnection { + connection => + executeQuery(connection, this.createTable).rowsAffected === 0 + executeQuery(connection, this.insert).rowsAffected === 1 + val result = executeQuery(connection, this.select).rows.get + + result(0)("id") === 1 + result(0)("name") === "Maurício Linhares" + + } + + } + + } + +} From cd30b278e7ad7d69cdc9e28c556aefcfcc666e95 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 8 May 2013 22:18:18 -0300 Subject: [PATCH 058/357] Sending common statements to MySQL \o/ --- .../db}/column/BigDecimalEncoderDecoder.scala | 4 +- .../db/column/BigIntegerEncoderDecoder.scala | 21 +++ .../async/db}/column/ColumnDecoder.scala | 2 +- .../db}/column/ColumnDecoderRegistry.scala | 2 +- .../async/db}/column/ColumnEncoder.scala | 4 +- .../db}/column/ColumnEncoderDecoder.scala | 2 +- .../db}/column/ColumnEncoderRegistry.scala | 2 +- .../async/db}/column/DateEncoderDecoder.scala | 8 +- .../db}/column/DoubleEncoderDecoder.scala | 4 +- .../db}/column/FloatEncoderDecoder.scala | 3 +- .../db}/column/IntegerEncoderDecoder.scala | 4 +- .../async/db}/column/LongEncoderDecoder.scala | 5 +- .../db}/column/ShortEncoderDecoder.scala | 3 +- .../db}/column/StringEncoderDecoder.scala | 4 +- .../async/db}/column/TimeEncoderDecoder.scala | 4 +- .../TimeWithTimezoneEncoderDecoder.scala | 4 +- .../db}/column/TimestampEncoderDecoder.scala | 6 +- .../TimestampWithTimezoneEncoderDecoder.scala | 4 +- .../DateEncoderNotAvailableException.scala | 20 +++ .../async/db}/general/ArrayRowData.scala | 2 +- .../async/db/general/ColumnData.scala | 21 +++ .../async/db}/general/MutableResultSet.scala | 29 +++- .../db/util/ChannelFutureTransformer.scala | 1 + .../async/db/util/ChannelWrapper.scala | 1 + .../async/db/mysql/MySQLConnection.scala | 72 +++++--- .../mysql/codec/MySQLConnectionHandler.scala | 54 ++++-- .../db/mysql/codec/MySQLHandlerDelegate.scala | 2 + .../async/db/mysql/column/ColumnTypes.scala | 76 +++++++++ .../column/MySQLColumnDecoderRegistry.scala | 56 +++++++ .../message/server/ResultSetRowMessage.scala | 18 +- .../mauricio/async/db/mysql/QuerySpec.scala | 2 +- .../DatabaseConnectionHandler.scala | 15 +- .../async/db/postgresql/MessageEncoder.scala | 2 +- .../db/postgresql/column/ArrayDecoder.scala | 1 + .../column/BooleanEncoderDecoder.scala | 4 +- .../column/CharEncoderDecoder.scala | 4 +- .../column/DefaultColumnEncoderRegistry.scala | 150 ----------------- ... => PostgreSQLColumnDecoderRegistry.scala} | 7 +- .../PostgreSQLColumnEncoderRegistry.scala | 156 ++++++++++++++++++ .../ExecutePreparedStatementEncoder.scala | 2 +- .../PreparedStatementOpeningEncoder.scala | 2 +- .../DateEncoderNotAvailableException.scala | 11 -- ...nData.scala => PostgreSQLColumnData.scala} | 10 +- .../backend/RowDescriptionMessage.scala | 2 +- .../PreparedStatementExecuteMessage.scala | 2 +- .../frontend/PreparedStatementMessage.scala | 2 +- .../PreparedStatementOpeningMessage.scala | 2 +- .../parsers/RowDescriptionParser.scala | 6 +- .../db/general/MutableResultSetSpec.scala | 12 +- .../async/db/postgresql/ArrayTypesSpec.scala | 2 +- .../DatabaseConnectionHandlerSpec.scala | 2 +- .../postgresql/column/ArrayDecoderSpec.scala | 1 + .../DefaultColumnEncoderRegistrySpec.scala | 2 +- 53 files changed, 535 insertions(+), 302 deletions(-) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/BigDecimalEncoderDecoder.scala (89%) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/column/BigIntegerEncoderDecoder.scala rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/ColumnDecoder.scala (92%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/ColumnDecoderRegistry.scala (92%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/ColumnEncoder.scala (90%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/ColumnEncoderDecoder.scala (92%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/ColumnEncoderRegistry.scala (93%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/DateEncoderDecoder.scala (82%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/DoubleEncoderDecoder.scala (89%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/FloatEncoderDecoder.scala (89%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/IntegerEncoderDecoder.scala (89%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/LongEncoderDecoder.scala (89%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/ShortEncoderDecoder.scala (89%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/StringEncoderDecoder.scala (89%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/TimeEncoderDecoder.scala (92%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/TimeWithTimezoneEncoderDecoder.scala (88%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/TimestampEncoderDecoder.scala (89%) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/column/TimestampWithTimezoneEncoderDecoder.scala (88%) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DateEncoderNotAvailableException.scala rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/general/ArrayRowData.scala (96%) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/general/MutableResultSet.scala (69%) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala delete mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/{DefaultColumnDecoderRegistry.scala => PostgreSQLColumnDecoderRegistry.scala} (93%) create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnEncoderRegistry.scala delete mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/{ColumnData.scala => PostgreSQLColumnData.scala} (79%) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/BigDecimalEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/BigDecimalEncoderDecoder.scala index 9af99424..110a2ba7 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BigDecimalEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/BigDecimalEncoderDecoder.scala @@ -14,12 +14,10 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column object BigDecimalEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = BigDecimal(value) - def kind = ColumnTypes.Numeric - } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/BigIntegerEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/BigIntegerEncoderDecoder.scala new file mode 100644 index 00000000..41a347cf --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/BigIntegerEncoderDecoder.scala @@ -0,0 +1,21 @@ +/* + * 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 + +object BigIntegerEncoderDecoder extends ColumnEncoderDecoder { + override def decode(value: String): Any = BigInt(value) +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala similarity index 92% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala index 58bddaf5..e1878882 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column trait ColumnDecoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala similarity index 92% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala index 801502b6..23b29823 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnDecoderRegistry.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column trait ColumnDecoderRegistry { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoder.scala similarity index 90% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoder.scala index ed782b9f..1f2e8bf8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoder.scala @@ -14,12 +14,10 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column trait ColumnEncoder { def encode(value: Any): String = value.toString - def kind : Int - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoderDecoder.scala similarity index 92% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoderDecoder.scala index d6616d95..520c931c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoderDecoder.scala @@ -14,6 +14,6 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column trait ColumnEncoderDecoder extends ColumnEncoder with ColumnDecoder \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoderRegistry.scala similarity index 93% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoderRegistry.scala index 54f8dd6a..3b540e80 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnEncoderRegistry.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnEncoderRegistry.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column trait ColumnEncoderRegistry { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala similarity index 82% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala index b07844ad..63c01c6e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DateEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala @@ -14,11 +14,11 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column -import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException import org.joda.time.format.DateTimeFormat -import org.joda.time.{ReadablePartial, ReadableInstant, LocalDate} +import org.joda.time.{ReadablePartial, LocalDate} +import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException object DateEncoderDecoder extends ColumnEncoderDecoder { @@ -36,6 +36,4 @@ object DateEncoderDecoder extends ColumnEncoderDecoder { } } - def kind = ColumnTypes.Date - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DoubleEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/DoubleEncoderDecoder.scala index b4df457f..70836d42 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DoubleEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DoubleEncoderDecoder.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column + object DoubleEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Double = value.toDouble - def kind = ColumnTypes.Double } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/FloatEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/FloatEncoderDecoder.scala index fa2c245d..bcae2049 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/FloatEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/FloatEncoderDecoder.scala @@ -14,9 +14,8 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column object FloatEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Float = value.toFloat - def kind = ColumnTypes.Real } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/IntegerEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/IntegerEncoderDecoder.scala index 14d7d3f1..ed8b4fa5 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/IntegerEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/IntegerEncoderDecoder.scala @@ -14,12 +14,10 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column object IntegerEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Int = value.toInt - def kind = ColumnTypes.Integer - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LongEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/LongEncoderDecoder.scala index e81f48a3..3f9bdef2 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/LongEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LongEncoderDecoder.scala @@ -14,9 +14,8 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column object LongEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Long = value.toLong - def kind = ColumnTypes.Bigserial -} +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ShortEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/ShortEncoderDecoder.scala index cca4bacb..77071746 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ShortEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ShortEncoderDecoder.scala @@ -14,11 +14,10 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column object ShortEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = value.toShort - def kind = ColumnTypes.Smallint } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/StringEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/StringEncoderDecoder.scala index 4d18f98e..18c2adb5 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/StringEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/StringEncoderDecoder.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column + object StringEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): String = value - def kind = ColumnTypes.Varchar } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala similarity index 92% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala index f80e85f1..d19f015b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column import org.joda.time.LocalTime import org.joda.time.format.DateTimeFormat @@ -37,6 +37,4 @@ class TimeEncoderDecoder extends ColumnEncoderDecoder { this.parser.print(value.asInstanceOf[LocalTime]) } - def kind = ColumnTypes.Time - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeWithTimezoneEncoderDecoder.scala similarity index 88% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeWithTimezoneEncoderDecoder.scala index 387b2a51..f4e9a18c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimeWithTimezoneEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeWithTimezoneEncoderDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column import org.joda.time.format.DateTimeFormat @@ -24,6 +24,4 @@ object TimeWithTimezoneEncoderDecoder extends TimeEncoderDecoder { override def formatter = format - override def kind = ColumnTypes.TimeWithTimezone - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala similarity index 89% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala index 90236ca1..3bb9e5d6 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala @@ -14,9 +14,9 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column -import com.github.mauricio.async.db.postgresql.exceptions.DateEncoderNotAvailableException +import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException import java.sql.Timestamp import java.util.{Calendar, Date} import org.joda.time.format.DateTimeFormat @@ -46,6 +46,4 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { } } - def kind = ColumnTypes.Timestamp - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampWithTimezoneEncoderDecoder.scala similarity index 88% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampWithTimezoneEncoderDecoder.scala index a36deba8..181c502e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/TimestampWithTimezoneEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampWithTimezoneEncoderDecoder.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.column +package com.github.mauricio.async.db.column import org.joda.time.format.DateTimeFormat @@ -24,6 +24,4 @@ object TimestampWithTimezoneEncoderDecoder extends TimestampEncoderDecoder { override def formatter = format - override def kind = ColumnTypes.TimestampWithTimezone - } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DateEncoderNotAvailableException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DateEncoderNotAvailableException.scala new file mode 100644 index 00000000..a30559b7 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DateEncoderNotAvailableException.scala @@ -0,0 +1,20 @@ +/* + * 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.exceptions + +class DateEncoderNotAvailableException(value: Any) + extends DatabaseException("There is no encoder for value [%s] of type %s".format(value, value.getClass.getCanonicalName)) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/ArrayRowData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala similarity index 96% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/ArrayRowData.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala index e5b89ac2..386a343c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/ArrayRowData.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.general +package com.github.mauricio.async.db.general import com.github.mauricio.async.db.RowData diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala new file mode 100644 index 00000000..1d67cc70 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala @@ -0,0 +1,21 @@ +/* + * 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.general + +class ColumnData( + val name: String, + val dataType: Int) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala similarity index 69% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/MutableResultSet.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index b61c2925..907aae14 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -14,21 +14,23 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.general +package com.github.mauricio.async.db.general import collection.mutable.ArrayBuffer -import com.github.mauricio.async.db.{RowData, ResultSet} -import com.github.mauricio.async.db.postgresql.column.ColumnDecoderRegistry -import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData +import com.github.mauricio.async.db.column.ColumnDecoderRegistry import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.{RowData, ResultSet} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer object MutableResultSet { - val log = Log.get[MutableResultSet] + val log = Log.get[MutableResultSet[ColumnData]] } -class MutableResultSet(val columnTypes: Array[ColumnData], charset: Charset, decoder : ColumnDecoderRegistry) extends ResultSet { +class MutableResultSet[T <: ColumnData]( + val columnTypes: IndexedSeq[T], + charset: Charset, + decoder : ColumnDecoderRegistry) extends ResultSet { private val rows = new ArrayBuffer[RowData]() private val columnMapping: Map[String, Int] = this.columnTypes.indices.map( @@ -57,4 +59,19 @@ class MutableResultSet(val columnTypes: Array[ColumnData], charset: Charset, dec this.rows += realRow } + def addRawRow( row : Seq[String] ) { + val realRow = new ArrayRowData(columnMapping.size, this.rows.size, this.columnMapping) + + realRow.indices.foreach { + index => + realRow(index) = if (row(index) == null) { + null + } else { + this.decoder.decode( this.columnTypes(index).dataType, row(index) ) + } + } + + this.rows += realRow + } + } \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala index cdd842db..a4d73c62 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.util import org.jboss.netty.channel.{ChannelFutureListener, ChannelFuture} import scala.concurrent.{Promise, Future} import com.github.mauricio.async.db.exceptions.CanceledChannelFutureException +import scala.language.implicitConversions object ChannelFutureTransformer { diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index d9d414f6..7d735ceb 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.util import com.github.mauricio.async.db.exceptions.UnknownLengthException import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import scala.language.implicitConversions object ChannelWrapper { implicit def bufferToWrapper( buffer : ChannelBuffer ) = new ChannelWrapper(buffer) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 603b6347..09350a56 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -16,27 +16,30 @@ package com.github.mauricio.async.db.mysql -import com.github.mauricio.async.db.{QueryResult, Configuration} +import com.github.mauricio.async.db.column.ColumnDecoderRegistry +import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage, QuitMessage, HandshakeResponseMessage} import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.{ResultSet, QueryResult, Configuration} import org.jboss.netty.channel._ import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.{Failure, Success} -import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} +import com.github.mauricio.async.db.mysql.column.MySQLColumnDecoderRegistry object MySQLConnection { val log = Log.get[MySQLConnection] } class MySQLConnection( - configuration : Configuration, - charsetMapper : CharsetMapper = CharsetMapper.Instance ) - extends MySQLHandlerDelegate -{ + configuration: Configuration, + charsetMapper: CharsetMapper = CharsetMapper.Instance, + columnDecoderRegistry: ColumnDecoderRegistry = MySQLColumnDecoderRegistry.Instance + ) + extends MySQLHandlerDelegate { import MySQLConnection.log @@ -45,11 +48,11 @@ class MySQLConnection( private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) - private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this) + private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, columnDecoderRegistry) private final val connectionPromise = Promise[MySQLConnection]() private final val disconnectionPromise = Promise[MySQLConnection]() - private var queryPromise : Promise[QueryResult] = null + private var queryPromise: Promise[QueryResult] = null private var connected = false def connect: Future[MySQLConnection] = { @@ -60,13 +63,13 @@ class MySQLConnection( this.connectionPromise.future } - def close : Future[MySQLConnection] = { + def close: Future[MySQLConnection] = { - if ( !this.disconnectionPromise.isCompleted ) { - this.connectionHandler.write( QuitMessage ).onComplete { - case Success( channelFuture ) => { + if (!this.disconnectionPromise.isCompleted) { + this.connectionHandler.write(QuitMessage).onComplete { + case Success(channelFuture) => { this.connectionHandler.disconnect.onComplete { - case Success( closeFuture ) => this.disconnectionPromise.trySuccess(this) + case Success(closeFuture) => this.disconnectionPromise.trySuccess(this) case Failure(e) => this.disconnectionPromise.tryFailure(e) } } @@ -77,23 +80,23 @@ class MySQLConnection( this.disconnectionPromise.future } - override def connected( ctx : ChannelHandlerContext ) { + override def connected(ctx: ChannelHandlerContext) { log.debug("Connected to {}", ctx.getChannel.getRemoteAddress) this.connected = true } - override def exceptionCaught(throwable : Throwable) { + override def exceptionCaught(throwable: Throwable) { log.error("Transport failure", throwable) this.connectionPromise.tryFailure(throwable) this.failQueryPromise(throwable) } - override def onOk( message : OkMessage ) { + override def onOk(message: OkMessage) { log.debug("Received OK {}", message) this.connectionPromise.trySuccess(this) - if ( this.isQuerying ) { + if (this.isQuerying) { this.succeedQueryPromise( new MySQLQueryResult( message.affectedRows, @@ -109,7 +112,7 @@ class MySQLConnection( def onEOF(message: EOFMessage) { log.debug("Received EOF message - {}", message) - if ( this.isQuerying ) { + if (this.isQuerying) { this.succeedQueryPromise( new MySQLQueryResult( 0, @@ -122,7 +125,7 @@ class MySQLConnection( } } - override def onHandshake( message : HandshakeMessage ) { + override def onHandshake(message: HandshakeMessage) { log.debug("Received handshake message - {}", message) this.connectionHandler.write(new HandshakeResponseMessage( @@ -135,7 +138,7 @@ class MySQLConnection( )) } - override def onError( message : ErrorMessage ) { + override def onError(message: ErrorMessage) { log.error("Received an error message -> {}", message) val exception = new MySQLException(message) exception.fillInStackTrace() @@ -145,17 +148,17 @@ class MySQLConnection( def sendQuery(query: String): Future[QueryResult] = { this.queryPromise = Promise[QueryResult] - this.write( new QueryMessage(query) ) + this.write(new QueryMessage(query)) this.queryPromise.future } - private def write( message : ClientMessage ) : ChannelFuture = { + private def write(message: ClientMessage): ChannelFuture = { this.connectionHandler.write(message) } - private def failQueryPromise( t : Throwable ) { + private def failQueryPromise(t: Throwable) { - if ( this.isQuerying ) { + if (this.isQuerying) { val promise = this.queryPromise this.queryPromise = null @@ -164,9 +167,9 @@ class MySQLConnection( } - private def succeedQueryPromise( queryResult : QueryResult ) { + private def succeedQueryPromise(queryResult: QueryResult) { - if ( this.isQuerying ) { + if (this.isQuerying) { val promise = this.queryPromise this.queryPromise = null @@ -175,6 +178,21 @@ class MySQLConnection( } - private def isQuerying : Boolean = this.queryPromise != null + private def isQuerying: Boolean = this.queryPromise != null + + def onResultSet(resultSet: ResultSet, message: EOFMessage) { + if (this.isQuerying) { + this.succeedQueryPromise( + new MySQLQueryResult( + 0, + null, + -1, + message.flags, + message.warningCount, + Some(resultSet) + ) + ) + } + } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index a821e460..79ef393c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -33,15 +33,18 @@ import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage import com.github.mauricio.async.db.mysql.message.server.ErrorMessage import com.github.mauricio.async.db.mysql.message.client.QueryMessage import com.github.mauricio.async.db.mysql.message.server.OkMessage +import com.github.mauricio.async.db.column.ColumnDecoderRegistry +import com.github.mauricio.async.db.general.{ColumnData, MutableResultSet} object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] } class MySQLConnectionHandler( - configuration : Configuration, - charsetMapper : CharsetMapper, - handlerDelegate : MySQLHandlerDelegate + configuration: Configuration, + charsetMapper: CharsetMapper, + handlerDelegate: MySQLHandlerDelegate, + columnDecoderRegistry: ColumnDecoderRegistry ) extends SimpleChannelHandler with LifeCycleAwareChannelHandler { @@ -59,8 +62,8 @@ class MySQLConnectionHandler( private final val decoder = new MySQLFrameDecoder(configuration.charset) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() - private final val currentRows = new ArrayBuffer[ResultSetRowMessage]() - private var currentContext : ChannelHandlerContext = null + private var currentQuery : MutableResultSet[ColumnData] = null + private var currentContext: ChannelHandlerContext = null def connect: Future[MySQLConnectionHandler] = { @@ -88,22 +91,30 @@ class MySQLConnectionHandler( override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) { e.getMessage match { - case m : ServerMessage => { - (m.kind : @switch) match { + case m: ServerMessage => { + (m.kind: @switch) match { case ServerMessage.ServerProtocolVersion => { - handlerDelegate.onHandshake( m.asInstanceOf[HandshakeMessage] ) + handlerDelegate.onHandshake(m.asInstanceOf[HandshakeMessage]) } case ServerMessage.Ok => { this.clearQueryState handlerDelegate.onOk(m.asInstanceOf[OkMessage]) } - case ServerMessage.Error => { + case ServerMessage.Error => { this.clearQueryState handlerDelegate.onError(m.asInstanceOf[ErrorMessage]) } case ServerMessage.EOF => { + + val resultSet = this.currentQuery this.clearQueryState - handlerDelegate.onEOF(m.asInstanceOf[EOFMessage]) + + if ( resultSet != null ) { + handlerDelegate.onResultSet( resultSet, m.asInstanceOf[EOFMessage] ) + } else { + handlerDelegate.onEOF(m.asInstanceOf[EOFMessage]) + } + } case ServerMessage.ColumnDefinition => { log.debug("Received column definition - {}", m) @@ -111,10 +122,17 @@ class MySQLConnectionHandler( } case ServerMessage.ColumnDefinitionFinished => { log.debug("Column processing finished, waiting for rows now -> {}", m) + + this.currentQuery = new MutableResultSet[ColumnData]( + this.currentColumns.map( c => new ColumnData(c.name, c.columnType) ), + configuration.charset, + columnDecoderRegistry + ) + } case ServerMessage.Row => { log.debug("Received row - {}", m) - this.currentRows += m.asInstanceOf[ResultSetRowMessage] + this.currentQuery.addRawRow(m.asInstanceOf[ResultSetRowMessage]) } } } @@ -123,14 +141,14 @@ class MySQLConnectionHandler( } override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { - handlerDelegate.connected( ctx ) + handlerDelegate.connected(ctx) } override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { - if ( !this.connectionPromise.isCompleted ) { + if (!this.connectionPromise.isCompleted) { this.connectionPromise.failure(e.getCause) } - handlerDelegate.exceptionCaught( e.getCause ) + handlerDelegate.exceptionCaught(e.getCause) } def beforeAdd(ctx: ChannelHandlerContext) {} @@ -143,20 +161,20 @@ class MySQLConnectionHandler( def afterRemove(ctx: ChannelHandlerContext) {} - def write( message : ClientMessage ) : ChannelFuture = { - if ( message.kind == ClientMessage.Query ) { + def write(message: ClientMessage): ChannelFuture = { + if (message.kind == ClientMessage.Query) { this.decoder.queryProcessStarted() } this.currentContext.getChannel.write(message) } - def disconnect : ChannelFuture = { + def disconnect: ChannelFuture = { this.currentContext.getChannel.close() } private def clearQueryState { this.currentColumns.clear() - this.currentRows.clear() + this.currentQuery = null } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala index 2f7aba03..05729c75 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} import org.jboss.netty.channel.ChannelHandlerContext +import com.github.mauricio.async.db.ResultSet trait MySQLHandlerDelegate { @@ -27,5 +28,6 @@ trait MySQLHandlerDelegate { def onEOF( message : EOFMessage ) def exceptionCaught( exception : Throwable ) def connected( ctx : ChannelHandlerContext ) + def onResultSet( resultSet : ResultSet, message : EOFMessage ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala new file mode 100644 index 00000000..80151b66 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala @@ -0,0 +1,76 @@ +/* + * 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.mysql.column + +object ColumnTypes { + + final val FIELD_TYPE_BIT = 16 + + final val FIELD_TYPE_BLOB = 252 + + final val FIELD_TYPE_DATE = 10 + + final val FIELD_TYPE_DATETIME = 12 + + final val FIELD_TYPE_DECIMAL = 0 + + final val FIELD_TYPE_DOUBLE = 5 + + final val FIELD_TYPE_ENUM = 247 + + final val FIELD_TYPE_FLOAT = 4 + + final val FIELD_TYPE_GEOMETRY = 255 + + final val FIELD_TYPE_INT24 = 9 + + final val FIELD_TYPE_LONG = 3 + + final val FIELD_TYPE_LONG_BLOB = 251 + + final val FIELD_TYPE_LONGLONG = 8 + + final val FIELD_TYPE_MEDIUM_BLOB = 250 + + final val FIELD_TYPE_NEW_DECIMAL = 246 + + final val FIELD_TYPE_NEWDATE = 14 + + final val FIELD_TYPE_NULL = 6 + + final val FIELD_TYPE_SET = 248 + + final val FIELD_TYPE_SHORT = 2 + + final val FIELD_TYPE_STRING = 254 + + final val FIELD_TYPE_TIME = 11 + + final val FIELD_TYPE_TIMESTAMP = 7 + + final val FIELD_TYPE_TINY = 1 + + final val FIELD_TYPE_TINY_BLOB = 249 + + final val FIELD_TYPE_VAR_STRING = 253 + + final val FIELD_TYPE_VARCHAR = 15 + + final val FIELD_TYPE_YEAR = 13 + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala new file mode 100644 index 00000000..6f26a493 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -0,0 +1,56 @@ +/* + * 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.mysql.column + +import com.github.mauricio.async.db.column._ +import scala.annotation.switch + +object MySQLColumnDecoderRegistry { + final val Instance = new MySQLColumnDecoderRegistry() +} + +class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { + + def decode(kind: Int, value: String): Any = { + this.decoderFor(kind).decode(value) + } + + def decoderFor( kind : Int ) : ColumnDecoder = { + (kind : @switch) match { + case ColumnTypes.FIELD_TYPE_DATE => DateEncoderDecoder + case ColumnTypes.FIELD_TYPE_DATETIME => TimestampEncoderDecoder.Instance + case ColumnTypes.FIELD_TYPE_DECIMAL => BigDecimalEncoderDecoder + case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleEncoderDecoder + case ColumnTypes.FIELD_TYPE_FLOAT => FloatEncoderDecoder + case ColumnTypes.FIELD_TYPE_INT24 => IntegerEncoderDecoder + case ColumnTypes.FIELD_TYPE_LONG => LongEncoderDecoder + case ColumnTypes.FIELD_TYPE_LONGLONG => BigIntegerEncoderDecoder + case ColumnTypes.FIELD_TYPE_NEW_DECIMAL => BigDecimalEncoderDecoder + case ColumnTypes.FIELD_TYPE_NEWDATE => DateEncoderDecoder + case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder + case ColumnTypes.FIELD_TYPE_STRING => StringEncoderDecoder + case ColumnTypes.FIELD_TYPE_TIME => TimeEncoderDecoder.Instance + case ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampEncoderDecoder.Instance + case ColumnTypes.FIELD_TYPE_TINY => ShortEncoderDecoder + case ColumnTypes.FIELD_TYPE_VAR_STRING => StringEncoderDecoder + case ColumnTypes.FIELD_TYPE_VARCHAR => StringEncoderDecoder + case ColumnTypes.FIELD_TYPE_YEAR => IntegerEncoderDecoder + case _ => StringEncoderDecoder + } + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala index f9153500..2d001f24 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala @@ -21,20 +21,20 @@ import scala.collection.mutable class ResultSetRowMessage extends ServerMessage( ServerMessage.Row ) - with mutable.Buffer[Any] + with mutable.Buffer[String] { - private val buffer = new ArrayBuffer[Any]() + private val buffer = new ArrayBuffer[String]() def length: Int = buffer.length - def apply(idx: Int): Any = buffer(idx) + def apply(idx: Int): String = buffer(idx) - def update(n: Int, newelem: Any) { + def update(n: Int, newelem: String) { buffer.update(n, newelem) } - def +=(elem: Any): this.type = { + def +=(elem: String): this.type = { this.buffer += elem this } @@ -43,19 +43,19 @@ class ResultSetRowMessage this.buffer.clear() } - def +=:(elem: Any): this.type = { + def +=:(elem: String): this.type = { this.buffer.+=:(elem) this } - def insertAll(n: Int, elems: Traversable[Any]) { + def insertAll(n: Int, elems: Traversable[String]) { this.buffer.insertAll(n, elems) } - def remove(n: Int): Any = { + def remove(n: Int): String = { this.buffer.remove(n) } - def iterator: Iterator[Any] = this.buffer.iterator + override def iterator: Iterator[String] = this.buffer.iterator } \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 32fdf9c0..8d2443e5 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -59,7 +59,7 @@ class QuerySpec extends Specification with ConnectionHelper { val result = executeQuery(connection, this.select).rows.get result(0)("id") === 1 - result(0)("name") === "Maurício Linhares" + result(0)("name") === "Maurício Aragão" } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index 786fa24e..de0b5c5e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnDecoderRegistry, DefaultColumnEncoderRegistry, ColumnEncoderRegistry} +import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, PostgreSQLColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} +import com.github.mauricio.async.db.{ResultSet, Configuration, QueryResult, Connection} import concurrent.{Future, Promise} import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap @@ -33,7 +33,8 @@ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ import scala.annotation.switch -import com.github.mauricio.async.db.postgresql.general.MutableResultSet +import com.github.mauricio.async.db.column.{ColumnEncoderRegistry, ColumnDecoderRegistry} +import com.github.mauricio.async.db.general.MutableResultSet object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -45,8 +46,8 @@ object DatabaseConnectionHandler { class DatabaseConnectionHandler ( configuration: Configuration = Configuration.Default, - encoderRegistry: ColumnEncoderRegistry = DefaultColumnEncoderRegistry.Instance, - decoderRegistry: ColumnDecoderRegistry = DefaultColumnDecoderRegistry.Instance + encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, + decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance ) extends SimpleChannelHandler with Connection { import DatabaseConnectionHandler._ @@ -62,7 +63,7 @@ class DatabaseConnectionHandler private var readyForQuery = false private val parameterStatus = new ConcurrentHashMap[String, String]() - private val parsedStatements = new ConcurrentHashMap[String, Array[ColumnData]]() + private val parsedStatements = new ConcurrentHashMap[String, Array[PostgreSQLColumnData]]() private var _processData: Option[ProcessData] = None private var authenticated = false @@ -76,7 +77,7 @@ class DatabaseConnectionHandler private var connected = false private var recentError = false private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None) - private var currentQuery: Option[MutableResultSet] = None + private var currentQuery: Option[MutableResultSet[PostgreSQLColumnData]] = None private var currentPreparedStatement: Option[String] = None private var _currentChannel: Option[Channel] = None diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala index 01566f10..c87bcc55 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala @@ -16,7 +16,6 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.encoders._ import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend._ @@ -27,6 +26,7 @@ import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import scala.annotation.switch import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException +import com.github.mauricio.async.db.column.ColumnEncoderRegistry object MessageEncoder { val log = Log.get[MessageEncoder] 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 05970c28..c871b632 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,6 +19,7 @@ package com.github.mauricio.async.db.postgresql.column import scala.collection.IndexedSeq import scala.collection.mutable.{ArrayBuffer, Stack} import com.github.mauricio.async.db.postgresql.util.{ArrayStreamingParserDelegate, ArrayStreamingParser} +import com.github.mauricio.async.db.column.ColumnDecoder class ArrayDecoder(private val encoder: ColumnDecoder) extends ColumnDecoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala index 0a8a10f2..d11e6942 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/BooleanEncoderDecoder.scala @@ -16,6 +16,8 @@ package com.github.mauricio.async.db.postgresql.column +import com.github.mauricio.async.db.column.ColumnEncoderDecoder + object BooleanEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = { @@ -36,6 +38,4 @@ object BooleanEncoderDecoder extends ColumnEncoderDecoder { } } - def kind = ColumnTypes.Boolean - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala index cbf2c4e7..8dc30a67 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/CharEncoderDecoder.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.postgresql.column +import com.github.mauricio.async.db.column.ColumnEncoderDecoder + object CharEncoderDecoder extends ColumnEncoderDecoder { override def decode(value: String): Any = value.charAt(0) - def kind = ColumnTypes.Char - } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala deleted file mode 100644 index b2d89b62..00000000 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistry.scala +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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.column - -import org.joda.time._ -import scala.collection.JavaConversions._ - -object DefaultColumnEncoderRegistry { - val Instance = new DefaultColumnEncoderRegistry() -} - -class DefaultColumnEncoderRegistry extends ColumnEncoderRegistry { - - private val classesSequence = List( - classOf[Int] -> IntegerEncoderDecoder, - classOf[java.lang.Integer] -> IntegerEncoderDecoder, - - classOf[java.lang.Short] -> IntegerEncoderDecoder, - classOf[Short] -> ShortEncoderDecoder, - - classOf[Long] -> LongEncoderDecoder, - classOf[java.lang.Long] -> LongEncoderDecoder, - - classOf[String] -> StringEncoderDecoder, - classOf[java.lang.String] -> StringEncoderDecoder, - - classOf[Float] -> FloatEncoderDecoder, - classOf[java.lang.Float] -> FloatEncoderDecoder, - - classOf[Double] -> DoubleEncoderDecoder, - classOf[java.lang.Double] -> DoubleEncoderDecoder, - - classOf[BigDecimal] -> BigDecimalEncoderDecoder, - classOf[java.math.BigDecimal] -> BigDecimalEncoderDecoder, - - classOf[LocalDate] -> DateEncoderDecoder, - classOf[LocalTime] -> TimeEncoderDecoder.Instance, - classOf[DateTime] -> TimestampWithTimezoneEncoderDecoder, - classOf[ReadablePartial] -> TimeEncoderDecoder.Instance, - classOf[ReadableDateTime] -> TimestampWithTimezoneEncoderDecoder, - classOf[ReadableInstant] -> DateEncoderDecoder, - - classOf[java.util.Date] -> TimestampWithTimezoneEncoderDecoder, - classOf[java.sql.Date] -> DateEncoderDecoder, - classOf[java.sql.Time] -> TimeEncoderDecoder.Instance, - classOf[java.sql.Timestamp] -> TimestampWithTimezoneEncoderDecoder, - classOf[java.util.Calendar] -> TimestampWithTimezoneEncoderDecoder, - classOf[java.util.GregorianCalendar] -> TimestampWithTimezoneEncoderDecoder) - - private val classes: Map[Class[_], ColumnEncoder] = classesSequence.toMap - - def encode(value: Any): String = { - - if (value == null) { - return null - } - - val encoder = this.classes.get(value.getClass) - - if (encoder.isDefined) { - encoder.get.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 => { - this.classesSequence.find(entry => entry._1.isAssignableFrom(value.getClass)) match { - case Some(parent) => parent._2.encode(value) - case None => value.toString - } - } - } - - } - - } - - def encodeArray(collection: Traversable[_]): String = { - val builder = new StringBuilder() - - builder.append('{') - - val result = collection.map { - item => - - if (item == null) { - "NULL" - } else { - if (this.shouldQuote(item)) { - "\"" + this.encode(item).replaceAllLiterally("\"", """\"""") + "\"" - } else { - this.encode(item) - } - } - - }.mkString(",") - - builder.append(result) - builder.append('}') - - builder.toString() - } - - def shouldQuote(value: Any): Boolean = { - value match { - case n: java.lang.Number => false - case n: Int => false - case n: Short => false - case n: Long => false - case n: Float => false - case n: Double => false - case n: java.lang.Iterable[_] => false - case n: Traversable[_] => false - case n: Array[_] => false - case _ => true - } - } - - def kindOf(value: Any): Int = { - if ( value == null ) { - 0 - } else { - this.classes.get(value.getClass) match { - case Some( encoder ) => encoder.kind - case None => 0 - } - } - } -} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnDecoderRegistry.scala similarity index 93% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnDecoderRegistry.scala index 5c3d8d42..ea91de77 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnDecoderRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnDecoderRegistry.scala @@ -18,12 +18,13 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.postgresql.column.ColumnTypes._ import scala.annotation.switch +import com.github.mauricio.async.db.column._ -object DefaultColumnDecoderRegistry { - val Instance = new DefaultColumnDecoderRegistry() +object PostgreSQLColumnDecoderRegistry { + val Instance = new PostgreSQLColumnDecoderRegistry() } -class DefaultColumnDecoderRegistry extends ColumnDecoderRegistry { +class PostgreSQLColumnDecoderRegistry extends ColumnDecoderRegistry { def decode(kind: Int, value: String) : Any = decoderFor(kind).decode(value) 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 new file mode 100644 index 00000000..f1140908 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnEncoderRegistry.scala @@ -0,0 +1,156 @@ +/* + * 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.column + +import org.joda.time._ +import scala.collection.JavaConversions._ +import com.github.mauricio.async.db.column._ +import java.sql.{Date, Time, Timestamp} +import java.util.GregorianCalendar +import java.{lang, math} +import scala.Some + +object PostgreSQLColumnEncoderRegistry { + val Instance = new PostgreSQLColumnEncoderRegistry() +} + +class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { + + private val classesSequence: List[(Class[_], (ColumnEncoderDecoder, Int))] = List( + classOf[Int] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), + classOf[java.lang.Integer] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), + + classOf[java.lang.Short] -> (ShortEncoderDecoder -> ColumnTypes.Smallint), + classOf[Short] -> (ShortEncoderDecoder -> ColumnTypes.Smallint), + + classOf[Long] -> (LongEncoderDecoder -> ColumnTypes.Bigserial), + classOf[java.lang.Long] -> (LongEncoderDecoder -> ColumnTypes.Bigserial), + + classOf[String] -> (StringEncoderDecoder -> ColumnTypes.Varchar), + classOf[java.lang.String] -> (StringEncoderDecoder -> ColumnTypes.Varchar), + + classOf[Float] -> (FloatEncoderDecoder -> ColumnTypes.Real), + classOf[java.lang.Float] -> (FloatEncoderDecoder -> ColumnTypes.Real), + + classOf[Double] -> (DoubleEncoderDecoder -> ColumnTypes.Double), + classOf[java.lang.Double] -> (DoubleEncoderDecoder -> ColumnTypes.Double), + + classOf[BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), + classOf[java.math.BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), + + classOf[LocalDate] -> ( DateEncoderDecoder -> ColumnTypes.Date ), + classOf[LocalTime] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time), + classOf[DateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), + classOf[ReadablePartial] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time), + classOf[ReadableDateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), + classOf[ReadableInstant] -> (DateEncoderDecoder -> ColumnTypes.Date), + + classOf[java.util.Date] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), + classOf[java.sql.Date] -> ( DateEncoderDecoder -> ColumnTypes.Date ), + classOf[java.sql.Time] -> ( TimeEncoderDecoder.Instance -> ColumnTypes.Time ), + classOf[java.sql.Timestamp] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), + classOf[java.util.Calendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), + classOf[java.util.GregorianCalendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone) + ) + + private final val classes = classesSequence.toMap + + def encode(value: Any): String = { + + if (value == null) { + return null + } + + val encoder = this.classes.get(value.getClass) + + 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 => { + this.classesSequence.find(entry => entry._1.isAssignableFrom(value.getClass)) match { + case Some(parent) => parent._2._1.encode(value) + case None => value.toString + } + } + } + + } + + } + + def encodeArray(collection: Traversable[_]): String = { + val builder = new StringBuilder() + + builder.append('{') + + val result = collection.map { + item => + + if (item == null) { + "NULL" + } else { + if (this.shouldQuote(item)) { + "\"" + this.encode(item).replaceAllLiterally("\"", """\"""") + "\"" + } else { + this.encode(item) + } + } + + }.mkString(",") + + builder.append(result) + builder.append('}') + + builder.toString() + } + + def shouldQuote(value: Any): Boolean = { + value match { + case n: java.lang.Number => false + case n: Int => false + case n: Short => false + case n: Long => false + case n: Float => false + case n: Double => false + case n: java.lang.Iterable[_] => false + case n: Traversable[_] => false + case n: Array[_] => false + case _ => true + } + } + + def kindOf(value: Any): Int = { + if ( value == null ) { + 0 + } else { + this.classes.get(value.getClass) match { + case Some( entry ) => entry._2 + case None => 0 + } + } + } +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 8c05a043..ffb6de55 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.column.{ColumnEncoderRegistry, ColumnEncoderDecoder} import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.column.ColumnEncoderRegistry class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 5925751a..e58fd5de 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.column.{ColumnEncoderRegistry, ColumnEncoderDecoder} import com.github.mauricio.async.db.postgresql.messages.backend.Message import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala deleted file mode 100644 index 81a596e4..00000000 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/DateEncoderNotAvailableException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mauricio.async.db.postgresql.exceptions - -import com.github.mauricio.async.db.exceptions.DatabaseException - -/** - * User: mauricio - * Date: 4/4/13 - * Time: 12:36 AM - */ -class DateEncoderNotAvailableException(value: Any) - extends DatabaseException("There is no encoder for value [%s] of type %s".format(value, value.getClass.getCanonicalName)) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala similarity index 79% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala index ffff5651..73a8b16a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ColumnData.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala @@ -16,11 +16,13 @@ package com.github.mauricio.async.db.postgresql.messages.backend -class ColumnData( - val name: String, +import com.github.mauricio.async.db.general.ColumnData + +class PostgreSQLColumnData( + name: String, val tableObjectId: Int, val columnNumber: Int, - val dataType: Int, + dataType: Int, val dataTypeSize: Int, val dataTypeModifier: Int, - val fieldFormat: Int) \ No newline at end of file + val fieldFormat: Int) extends ColumnData( name, dataType ) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala index 06594ba6..d4da6edb 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala @@ -16,5 +16,5 @@ package com.github.mauricio.async.db.postgresql.messages.backend -case class RowDescriptionMessage(val columnDatas: Array[ColumnData]) +case class RowDescriptionMessage(val columnDatas: Array[PostgreSQLColumnData]) extends Message(Message.RowDescription) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index 76c413e6..889d501a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.postgresql.messages.backend.Message -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry +import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementExecuteMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) extends PreparedStatementMessage(Message.Execute, query, values, encoderRegistry) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index 4a106f4e..180c5953 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry +import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementMessage( kind: Byte, diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index 9d38fa35..f2e9781a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.postgresql.messages.backend.Message -import com.github.mauricio.async.db.postgresql.column.ColumnEncoderRegistry +import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementOpeningMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) extends PreparedStatementMessage(Message.Parse, query, values, encoderRegistry) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index fa6f7939..931e8569 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, ColumnData, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, PostgreSQLColumnData, Message} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils @@ -64,11 +64,11 @@ class RowDescriptionParser(charset: Charset) extends MessageParser { override def parseMessage(b: ChannelBuffer): Message = { val columnsCount = b.readShort() - val columns = new Array[ColumnData](columnsCount) + val columns = new Array[PostgreSQLColumnData](columnsCount) 0.until(columnsCount).foreach { index => - columns(index) = new ColumnData( + columns(index) = new PostgreSQLColumnData( name = ChannelUtils.readCString(b, charset), tableObjectId = b.readInt(), columnNumber = b.readShort(), diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala index f1806cb8..03e37fba 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala @@ -16,24 +16,24 @@ package com.github.mauricio.async.db.general -import com.github.mauricio.async.db.postgresql.column.{DefaultColumnDecoderRegistry, ColumnTypes} -import com.github.mauricio.async.db.postgresql.messages.backend.ColumnData +import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, ColumnTypes} +import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumnData import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification -import com.github.mauricio.async.db.postgresql.general.MutableResultSet +import com.github.mauricio.async.db.general.MutableResultSet class MutableResultSetSpec extends Specification { val charset = CharsetUtil.UTF_8 - val decoder = new DefaultColumnDecoderRegistry + val decoder = new PostgreSQLColumnDecoderRegistry "result set" should { "correctly map column data to fields" in { val columns = Array( - new ColumnData( + new PostgreSQLColumnData( name = "id", tableObjectId = 0, columnNumber = 0, @@ -42,7 +42,7 @@ class MutableResultSetSpec extends Specification { dataTypeModifier = 0, fieldFormat = 0 ), - new ColumnData( + new PostgreSQLColumnData( name = "name", tableObjectId = 0, columnNumber = 5, 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 374ec73a..f56948cd 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 @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification -import com.github.mauricio.async.db.postgresql.column.TimestampWithTimezoneEncoderDecoder +import com.github.mauricio.async.db.column.TimestampWithTimezoneEncoderDecoder class ArrayTypesSpec extends Specification with DatabaseTestHelper { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 841f8a0d..8cdacb91 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -16,7 +16,6 @@ package com.github.mauricio.postgresql -import com.github.mauricio.async.db.postgresql.column.{TimeEncoderDecoder, DateEncoderDecoder, TimestampEncoderDecoder} import com.github.mauricio.async.db.postgresql.exceptions.{InsufficientParametersException, QueryMustNotBeNullOrEmptyException, GenericDatabaseException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} @@ -26,6 +25,7 @@ import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException +import com.github.mauricio.async.db.column.{TimestampEncoderDecoder, TimeEncoderDecoder, DateEncoderDecoder} class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala index f248d6b9..7fcfc973 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.column import org.specs2.mutable.Specification +import com.github.mauricio.async.db.column.IntegerEncoderDecoder class ArrayDecoderSpec extends Specification { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala index d1daa410..88965d49 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala @@ -20,7 +20,7 @@ import org.specs2.mutable.Specification class DefaultColumnEncoderRegistrySpec extends Specification { - val registry = new DefaultColumnEncoderRegistry() + val registry = new PostgreSQLColumnEncoderRegistry() "registry" should { From 77106678b2508a01abb614979eb40608f02f9e72 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 8 May 2013 22:51:13 -0300 Subject: [PATCH 059/357] MySQLConnection now implements the Connection trait --- .../github/mauricio/async/db/Connection.scala | 2 +- .../async/db/pool/ConnectionPool.scala | 10 ++++++---- .../async/db/mysql/MySQLConnection.scala | 19 +++++++++++++------ .../mysql/codec/MySQLConnectionHandler.scala | 8 ++++++++ .../DatabaseConnectionHandler.scala | 14 +++++++------- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala index cab63b93..62084e14 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala @@ -62,7 +62,7 @@ trait Connection { * @return */ - def connect: Future[Map[String, String]] + def connect: Future[Connection] /** * diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala index 3b1cf218..9e79c98c 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala @@ -51,7 +51,11 @@ class ConnectionPool[T <: Connection]( * @return */ - def disconnect: Future[Connection] = this.close.map(item => this)(executionContext) + def disconnect: Future[Connection] = if ( this.isConnected ) { + this.close.map(item => this)(executionContext) + } else { + Future.successful(this) + } /** * @@ -60,9 +64,7 @@ class ConnectionPool[T <: Connection]( * @return */ - def connect: Future[Map[String, String]] = Future( { - Map[String, String]() - })(executionContext) + def connect: Future[Connection] = Future.successful(this) def isConnected: Boolean = !this.isClosed diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 09350a56..1be54d9e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -24,7 +24,7 @@ import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.{ResultSet, QueryResult, Configuration} +import com.github.mauricio.async.db.{Connection, ResultSet, QueryResult, Configuration} import org.jboss.netty.channel._ import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.{Failure, Success} @@ -39,7 +39,9 @@ class MySQLConnection( charsetMapper: CharsetMapper = CharsetMapper.Instance, columnDecoderRegistry: ColumnDecoderRegistry = MySQLColumnDecoderRegistry.Instance ) - extends MySQLHandlerDelegate { + extends MySQLHandlerDelegate + with Connection +{ import MySQLConnection.log @@ -50,12 +52,12 @@ class MySQLConnection( private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, columnDecoderRegistry) - private final val connectionPromise = Promise[MySQLConnection]() - private final val disconnectionPromise = Promise[MySQLConnection]() + private final val connectionPromise = Promise[Connection]() + private final val disconnectionPromise = Promise[Connection]() private var queryPromise: Promise[QueryResult] = null private var connected = false - def connect: Future[MySQLConnection] = { + def connect: Future[Connection] = { this.connectionHandler.connect.onFailure { case e => this.connectionPromise.tryFailure(e) } @@ -63,7 +65,7 @@ class MySQLConnection( this.connectionPromise.future } - def close: Future[MySQLConnection] = { + def close: Future[Connection] = { if (!this.disconnectionPromise.isCompleted) { this.connectionHandler.write(QuitMessage).onComplete { @@ -195,4 +197,9 @@ class MySQLConnection( } } + def disconnect: Future[Connection] = this.close + + def isConnected: Boolean = this.connectionHandler.isConnected + + def sendPreparedStatement(query: String, values: Seq[Any]): Future[QueryResult] = ??? } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 79ef393c..b57170d8 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -177,4 +177,12 @@ class MySQLConnectionHandler( this.currentQuery = null } + def isConnected : Boolean = { + if ( this.currentContext != null ) { + this.currentContext.getChannel.isConnected + } else { + false + } + } + } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala index de0b5c5e..cfd31ac3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala @@ -16,10 +16,12 @@ package com.github.mauricio.async.db.postgresql +import com.github.mauricio.async.db.column.{ColumnEncoderRegistry, ColumnDecoderRegistry} +import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, PostgreSQLColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.{ResultSet, Configuration, QueryResult, Connection} +import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import concurrent.{Future, Promise} import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap @@ -31,10 +33,8 @@ import org.jboss.netty.channel._ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some -import scala.collection.JavaConversions._ import scala.annotation.switch -import com.github.mauricio.async.db.column.{ColumnEncoderRegistry, ColumnDecoderRegistry} -import com.github.mauricio.async.db.general.MutableResultSet +import scala.collection.JavaConversions._ object DatabaseConnectionHandler { val log = Log.get[DatabaseConnectionHandler] @@ -72,7 +72,7 @@ class DatabaseConnectionHandler configuration.workerPool) private val bootstrap = new ClientBootstrap(this.factory) - private val connectionFuture = Promise[Map[String, String]]() + private val connectionFuture = Promise[Connection]() private var connected = false private var recentError = false @@ -83,7 +83,7 @@ class DatabaseConnectionHandler def isReadyForQuery: Boolean = this.readyForQuery - def connect: Future[Map[String, String]] = { + def connect: Future[Connection] = { this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @@ -311,7 +311,7 @@ class DatabaseConnectionHandler this.clearQueryPromise if (!this.connectionFuture.isCompleted) { - this.connectionFuture.success(this.parameterStatus.toMap) + this.connectionFuture.success(this) } } From 7535e8c2db32d7e4312ff782373516eb0934647a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 11:45:03 -0300 Subject: [PATCH 060/357] Date, time, timestamps and year support for MySQL --- .../async/db/column/TimeEncoderDecoder.scala | 16 +++-- .../db/column/TimestampEncoderDecoder.scala | 10 ++- .../mauricio/async/db/mysql/QuerySpec.scala | 66 ++++++++++++++++++- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala index d19f015b..bcb3cafd 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.column import org.joda.time.LocalTime -import org.joda.time.format.DateTimeFormat +import org.joda.time.format.{DateTimeFormatterBuilder, DateTimeFormat} object TimeEncoderDecoder { val Instance = new TimeEncoderDecoder() @@ -25,16 +25,22 @@ object TimeEncoderDecoder { class TimeEncoderDecoder extends ColumnEncoderDecoder { - private val parser = DateTimeFormat.forPattern("HH:mm:ss.SSSSSS") + final private val optional = new DateTimeFormatterBuilder() + .appendPattern(".SSSSSS").toParser - def formatter = parser + final private val format = new DateTimeFormatterBuilder() + .appendPattern("HH:mm:ss") + .appendOptional(optional) + .toFormatter + + def formatter = format override def decode(value: String): LocalTime = { - parser.parseLocalTime(value) + format.parseLocalTime(value) } override def encode(value: Any): String = { - this.parser.print(value.asInstanceOf[LocalTime]) + this.format.print(value.asInstanceOf[LocalTime]) } } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala index 3bb9e5d6..669bee40 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.column import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException import java.sql.Timestamp import java.util.{Calendar, Date} -import org.joda.time.format.DateTimeFormat +import org.joda.time.format.{DateTimeFormatter, DateTimeFormatterBuilder, DateTimeFormat} import org.joda.time.{ReadableDateTime, DateTime} object TimestampEncoderDecoder { @@ -28,7 +28,13 @@ object TimestampEncoderDecoder { class TimestampEncoderDecoder extends ColumnEncoderDecoder { - private val format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") + private val optional = new DateTimeFormatterBuilder() + .appendPattern(".SSSSSS").toParser + + private val format = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss") + .appendOptional(optional) + .toFormatter def formatter = format diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 8d2443e5..99ce6d3f 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -20,6 +20,7 @@ import org.specs2.mutable.Specification import com.github.mauricio.async.db.mysql.exceptions.MySQLException import org.jboss.netty.util.CharsetUtil import org.jboss.netty.buffer.ChannelBuffers +import org.joda.time.{ReadableDateTime, LocalTime, LocalDate, ReadablePartial} class QuerySpec extends Specification with ConnectionHelper { @@ -35,8 +36,8 @@ class QuerySpec extends Specification with ConnectionHelper { "be able to run a DML query" in { withConnection { - connection => - executeQuery( connection, this.createTable ).rowsAffected === 0 + connection => + executeQuery(connection, this.createTable).rowsAffected === 0 } } @@ -60,6 +61,67 @@ class QuerySpec extends Specification with ConnectionHelper { result(0)("id") === 1 result(0)("name") === "Maurício Aragão" + } + + } + + val createTableTimeColumns = + """CREATE TEMPORARY TABLE posts ( + id INT NOT NULL AUTO_INCREMENT, + created_at_date DATE not null, + created_at_datetime DATETIME not null, + created_at_timestamp TIMESTAMP not null, + created_at_time TIME not null, + created_at_year YEAR not null, + primary key (id) + )""" + + val insertTableTimeColumns = + """ + |insert into posts (created_at_date, created_at_datetime, created_at_timestamp, created_at_time, created_at_year) + |values ( '2038-01-19', '2013-01-19 03:14:07', '2020-01-19 03:14:07', '03:14:07', '1999' ) + """.stripMargin + + "be able to select from a table with timestamps" in { + + withConnection { + connection => + executeQuery(connection, createTableTimeColumns) + executeQuery(connection, insertTableTimeColumns) + val result = executeQuery(connection, "SELECT * FROM posts").rows.get(0) + + val date = result("created_at_date").asInstanceOf[LocalDate] + + date.getYear === 2038 + date.getMonthOfYear === 1 + date.getDayOfMonth === 19 + + val dateTime = result("created_at_datetime").asInstanceOf[ReadableDateTime] + dateTime.getYear === 2013 + dateTime.getMonthOfYear === 1 + dateTime.getDayOfMonth === 19 + dateTime.getHourOfDay === 3 + dateTime.getMinuteOfHour === 14 + dateTime.getSecondOfMinute === 7 + + val timestamp = result("created_at_timestamp").asInstanceOf[ReadableDateTime] + timestamp.getYear === 2020 + timestamp.getMonthOfYear === 1 + timestamp.getDayOfMonth === 19 + timestamp.getHourOfDay === 3 + timestamp.getMinuteOfHour === 14 + timestamp.getSecondOfMinute === 7 + + + val time = result("created_at_time").asInstanceOf[LocalTime] + time.getHourOfDay === 3 + time.getMinuteOfHour === 14 + time.getSecondOfMinute === 7 + + val year = result("created_at_year").asInstanceOf[Int] + + year === 1999 + } From 59f1e49e01d1720cb0f8a1748611144eab9ddbc5 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 12:24:25 -0300 Subject: [PATCH 061/357] Processing numbers as well --- .../async/db/column/ByteDecoder.scala | 21 ++++++ .../db/mysql/codec/MySQLFrameDecoder.scala | 5 +- .../async/db/mysql/column/ColumnTypes.scala | 2 + .../column/MySQLColumnDecoderRegistry.scala | 7 +- .../decoder/ColumnDefinitionDecoder.scala | 5 ++ .../mauricio/async/db/mysql/QuerySpec.scala | 71 ++++++++++++++++--- 6 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/column/ByteDecoder.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ByteDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ByteDecoder.scala new file mode 100644 index 00000000..ea5b3f05 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ByteDecoder.scala @@ -0,0 +1,21 @@ +/* + * 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 + +object ByteDecoder extends ColumnDecoder { + override def decode(value: String): Any = value.toByte +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 1c1173dd..29ceccff 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -61,8 +61,8 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val slice = buffer.readSlice(size) - val requestDump = MySQLHelper.dumpAsHex(slice, slice.readableBytes()) - log.debug(s"Server message is type ${"%02x".format(messageType)} ( $messageType - $size bytes)\n${requestDump}") + //val requestDump = MySQLHelper.dumpAsHex(slice, slice.readableBytes()) + //log.debug(s"Server message is type ${"%02x".format(messageType)} ( $messageType - $size bytes)\n${requestDump}") // removing initial kind byte so that we can switch // on known messages but add it back if this is a query process @@ -79,7 +79,6 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { this.processingColumns = false ColumnProcessingFinishedDecoder } else { - log.debug("Got EOF {} - {}", totalColumns, processedColumns) this.clear EOFMessageDecoder } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala index 80151b66..89d5b3e5 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala @@ -29,6 +29,8 @@ object ColumnTypes { final val FIELD_TYPE_DECIMAL = 0 + final val FIELD_TYPE_NUMERIC = -10 + final val FIELD_TYPE_DOUBLE = 5 final val FIELD_TYPE_ENUM = 247 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala index 6f26a493..17c1dcf3 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -37,15 +37,16 @@ class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleEncoderDecoder case ColumnTypes.FIELD_TYPE_FLOAT => FloatEncoderDecoder case ColumnTypes.FIELD_TYPE_INT24 => IntegerEncoderDecoder - case ColumnTypes.FIELD_TYPE_LONG => LongEncoderDecoder - case ColumnTypes.FIELD_TYPE_LONGLONG => BigIntegerEncoderDecoder + case ColumnTypes.FIELD_TYPE_LONG => IntegerEncoderDecoder + case ColumnTypes.FIELD_TYPE_LONGLONG => LongEncoderDecoder case ColumnTypes.FIELD_TYPE_NEW_DECIMAL => BigDecimalEncoderDecoder + case ColumnTypes.FIELD_TYPE_NUMERIC => BigDecimalEncoderDecoder case ColumnTypes.FIELD_TYPE_NEWDATE => DateEncoderDecoder case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder case ColumnTypes.FIELD_TYPE_STRING => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_TIME => TimeEncoderDecoder.Instance case ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampEncoderDecoder.Instance - case ColumnTypes.FIELD_TYPE_TINY => ShortEncoderDecoder + case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder case ColumnTypes.FIELD_TYPE_VAR_STRING => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_VARCHAR => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_YEAR => IntegerEncoderDecoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala index dd12f56b..1cb19273 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala @@ -20,6 +20,11 @@ import com.github.mauricio.async.db.mysql.message.server.{ColumnDefinitionMessag import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.Log + +object ColumnDefinitionDecoder { + final val log = Log.get[ColumnDefinitionDecoder] +} class ColumnDefinitionDecoder(charset: Charset) extends MessageDecoder { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 99ce6d3f..67f2d2d8 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -65,8 +65,10 @@ class QuerySpec extends Specification with ConnectionHelper { } - val createTableTimeColumns = - """CREATE TEMPORARY TABLE posts ( + "be able to select from a table with timestamps" in { + + val createTableTimeColumns = + """CREATE TEMPORARY TABLE posts ( id INT NOT NULL AUTO_INCREMENT, created_at_date DATE not null, created_at_datetime DATETIME not null, @@ -76,13 +78,11 @@ class QuerySpec extends Specification with ConnectionHelper { primary key (id) )""" - val insertTableTimeColumns = - """ - |insert into posts (created_at_date, created_at_datetime, created_at_timestamp, created_at_time, created_at_year) - |values ( '2038-01-19', '2013-01-19 03:14:07', '2020-01-19 03:14:07', '03:14:07', '1999' ) - """.stripMargin - - "be able to select from a table with timestamps" in { + val insertTableTimeColumns = + """ + |insert into posts (created_at_date, created_at_datetime, created_at_timestamp, created_at_time, created_at_year) + |values ( '2038-01-19', '2013-01-19 03:14:07', '2020-01-19 03:14:07', '03:14:07', '1999' ) + """.stripMargin withConnection { connection => @@ -127,6 +127,59 @@ class QuerySpec extends Specification with ConnectionHelper { } + "be able to select from a table with the various numeric types" in { + + val createTableNumericColumns = + """ + |create temporary table numbers ( + |id int auto_increment not null, + |number_tinyint tinyint not null, + |number_smallint smallint not null, + |number_mediumint mediumint not null, + |number_int int not null, + |number_bigint bigint not null, + |number_decimal decimal(9,6), + |number_float float, + |number_double double, + |primary key (id) + |) + """.stripMargin + + val insertTableNumericColumns = + """ + |insert into numbers ( + |number_tinyint, + |number_smallint, + |number_mediumint, + |number_int, + |number_bigint, + |number_decimal, + |number_float, + |number_double + |) values + |(-100, 32766, 8388607, 2147483647, 9223372036854775807, 450.764491, 14.7, 87650.9876) + """.stripMargin + + withConnection { + connection => + executeQuery(connection, createTableNumericColumns) + executeQuery(connection, insertTableNumericColumns) + val result = executeQuery(connection, "SELECT * FROM numbers").rows.get(0) + + result("number_tinyint").asInstanceOf[Byte] === -100 + result("number_smallint").asInstanceOf[Short] === 32766 + result("number_mediumint").asInstanceOf[Int] === 8388607 + result("number_int").asInstanceOf[Int] === 2147483647 + result("number_bigint").asInstanceOf[Long] === 9223372036854775807L + result("number_decimal") === BigDecimal(450.764491) + result("number_float") === 14.7F + result("number_double") === 87650.9876 + + + } + + } + } } From 62df542bae89282a4b48ceee6d63c0c7088ca1c4 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 16:51:18 -0300 Subject: [PATCH 062/357] Extracting the handler piece of DatabaseConnectionHandler and renaming it PostgreSQLConnection --- .../mauricio/async/db/Configuration.scala | 4 +- .../async/db/pool/ConnectionPool.scala | 2 +- .../async/db/util/ExecutorServiceUtils.scala | 4 +- .../async/db/mysql/MySQLConnection.scala | 19 +- .../mysql/codec/MySQLConnectionHandler.scala | 6 +- .../async/db/mysql/ConnectionHelper.scala | 3 + .../github/mauricio/async/db/mysql/Main.scala | 17 -- ...ndler.scala => PostgreSQLConnection.scala} | 226 +++--------------- .../{ => codec}/MessageDecoder.scala | 8 +- .../{ => codec}/MessageEncoder.scala | 22 +- .../codec/PostgreSQLConnectionDelegate.scala | 34 +++ .../codec/PostgreSQLConnectionHandler.scala | 209 ++++++++++++++++ .../encoders/CloseMessageEncoder.scala | 4 +- .../encoders/CredentialEncoder.scala | 8 +- .../db/postgresql/encoders/Encoder.scala | 4 +- .../ExecutePreparedStatementEncoder.scala | 14 +- .../PreparedStatementOpeningEncoder.scala | 18 +- .../encoders/QueryMessageEncoder.scala | 8 +- .../encoders/StartupMessageEncoder.scala | 4 +- .../backend/AuthenticationMessage.scala | 2 +- .../messages/backend/BindComplete.scala | 2 +- .../messages/backend/CloseComplete.scala | 2 +- .../backend/CommandCompleteMessage.scala | 2 +- .../messages/backend/DataRowMessage.scala | 2 +- .../messages/backend/EmptyQueryString.scala | 2 +- .../messages/backend/ErrorMessage.scala | 2 +- .../messages/backend/InformationMessage.scala | 2 +- .../postgresql/messages/backend/NoData.scala | 2 +- .../messages/backend/NoticeMessage.scala | 2 +- .../backend/ParameterStatusMessage.scala | 2 +- .../messages/backend/ParseComplete.scala | 2 +- .../messages/backend/ProcessData.scala | 2 +- .../backend/ReadyForQueryMessage.scala | 2 +- .../backend/RowDescriptionMessage.scala | 2 +- .../{Message.scala => ServerMessage.scala} | 6 +- ...ntendMessage.scala => ClientMessage.scala} | 2 +- .../messages/frontend/CloseMessage.scala | 4 +- .../messages/frontend/CredentialMessage.scala | 4 +- .../PreparedStatementExecuteMessage.scala | 4 +- .../frontend/PreparedStatementMessage.scala | 2 +- .../PreparedStatementOpeningMessage.scala | 4 +- .../messages/frontend/QueryMessage.scala | 4 +- .../messages/frontend/StartupMessage.scala | 4 +- .../parsers/AuthenticationStartupParser.scala | 4 +- .../parsers/BackendKeyDataParser.scala | 4 +- .../parsers/CommandCompleteParser.scala | 4 +- .../db/postgresql/parsers/DataRowParser.scala | 4 +- .../db/postgresql/parsers/ErrorParser.scala | 4 +- .../parsers/InformationParser.scala | 6 +- .../db/postgresql/parsers/MessageParser.scala | 4 +- .../parsers/MessageParsersRegistry.scala | 30 +-- .../db/postgresql/parsers/NoticeParser.scala | 4 +- .../parsers/ParameterStatusParser.scala | 4 +- .../parsers/ReadyForQueryParser.scala | 4 +- .../parsers/ReturningMessageParser.scala | 4 +- .../parsers/RowDescriptionParser.scala | 4 +- .../pool/ConnectionObjectFactory.scala | 16 +- .../async/db/examples/BasicExample.scala | 6 +- .../db/general/MutableResultSetSpec.scala | 1 - .../DatabaseConnectionHandlerSpec.scala | 4 +- .../db/postgresql/DatabaseTestHelper.scala | 6 +- .../db/postgresql/MessageDecoderSpec.scala | 10 +- .../db/postgresql/parsers/ParserESpec.scala | 4 +- .../db/postgresql/parsers/ParserKSpec.scala | 4 +- .../db/postgresql/parsers/ParserSSpec.scala | 4 +- .../postgresql/pool/ConnectionPoolSpec.scala | 4 +- .../SingleThreadedAsyncObjectPoolSpec.scala | 10 +- 67 files changed, 454 insertions(+), 373 deletions(-) delete mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/{DatabaseConnectionHandler.scala => PostgreSQLConnection.scala} (52%) rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/{ => codec}/MessageDecoder.scala (92%) rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/{ => codec}/MessageEncoder.scala (82%) create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/{Message.scala => ServerMessage.scala} (91%) rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/{FrontendMessage.scala => ClientMessage.scala} (92%) 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 65aeaef7..9b54b835 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 @@ -53,8 +53,8 @@ case class Configuration(val username: String, val port: Int = 5432, val password: Option[String] = None, val database: Option[String] = None, - val bossPool: ExecutorService = ExecutorServiceUtils.FixedThreadPool, - val workerPool: ExecutorService = ExecutorServiceUtils.FixedThreadPool, + val bossPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, + val workerPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, val charset: Charset = Configuration.DefaultCharset, val maximumMessageSize: Int = 16777216 ) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala index 9e79c98c..4f55fb28 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala @@ -39,7 +39,7 @@ import scala.concurrent.{ExecutionContext, Future} class ConnectionPool[T <: Connection]( factory: ObjectFactory[T], configuration: PoolConfiguration, - executionContext: ExecutionContext = ExecutorServiceUtils.FixedExecutionContext + executionContext: ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends SingleThreadedAsyncObjectPool[T](factory, configuration) with Connection { diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala index 755879d7..44ba2c83 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -20,8 +20,8 @@ import java.util.concurrent.{ExecutorService, Executors} import scala.concurrent.ExecutionContext object ExecutorServiceUtils { - implicit val FixedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) - implicit val FixedExecutionContext = ExecutionContext.fromExecutor( FixedThreadPool ) + implicit val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) + implicit val CachedExecutionContext = ExecutionContext.fromExecutor( CachedThreadPool ) def newFixedPool( count : Int ) : ExecutorService = { Executors.newFixedThreadPool( count, DaemonThreadsFactory ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 1be54d9e..c7aa1d23 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -67,15 +67,17 @@ class MySQLConnection( def close: Future[Connection] = { - if (!this.disconnectionPromise.isCompleted) { - this.connectionHandler.write(QuitMessage).onComplete { - case Success(channelFuture) => { - this.connectionHandler.disconnect.onComplete { - case Success(closeFuture) => this.disconnectionPromise.trySuccess(this) - case Failure(e) => this.disconnectionPromise.tryFailure(e) + if ( this.isConnected ) { + if (!this.disconnectionPromise.isCompleted) { + this.connectionHandler.write(QuitMessage).onComplete { + case Success(channelFuture) => { + this.connectionHandler.disconnect.onComplete { + case Success(closeFuture) => this.disconnectionPromise.trySuccess(this) + case Failure(e) => this.disconnectionPromise.tryFailure(e) + } } + case Failure(exception) => this.disconnectionPromise.tryFailure(exception) } - case Failure(exception) => this.disconnectionPromise.tryFailure(exception) } } @@ -174,8 +176,7 @@ class MySQLConnection( if (this.isQuerying) { val promise = this.queryPromise this.queryPromise = null - - promise.trySuccess(queryResult) + promise.success(queryResult) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index b57170d8..26b90046 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -55,7 +55,8 @@ class MySQLConnectionHandler( private final val factory = new NioClientSocketChannelFactory( configuration.bossPool, - configuration.workerPool) + configuration.workerPool, + 1) private final val bootstrap = new ClientBootstrap(this.factory) private final val connectionPromise = Promise[MySQLConnectionHandler] @@ -169,7 +170,8 @@ class MySQLConnectionHandler( } def disconnect: ChannelFuture = { - this.currentContext.getChannel.close() + val future = this.currentContext.getChannel.close() + future } private def clearQueryState { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index 47eeb0db..4686a12a 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -18,6 +18,9 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.{QueryResult, Configuration} import com.github.mauricio.async.db.util.FutureUtils.await +import scala.concurrent.Future +import scala.util.{Failure, Success} +import com.github.mauricio.async.db.util.ExecutorServiceUtils trait ConnectionHelper { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala deleted file mode 100644 index 1b301a2b..00000000 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/Main.scala +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.mauricio.async.db.mysql - -import org.jboss.netty.util.CharsetUtil -import org.jboss.netty.buffer.ChannelBuffers -import java.nio.charset.Charset - -object Main { - - def main(args: Array[String]) { - - val name = "Maurício Aragão".getBytes(CharsetUtil.ISO_8859_1) - val result = MySQLHelper.dumpAsHex(ChannelBuffers.wrappedBuffer(name), name.length) - - println(result) - } - -} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala similarity index 52% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala index cfd31ac3..b4b1bbd4 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala @@ -22,7 +22,7 @@ import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRe import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} -import concurrent.{Future, Promise} +import scala.concurrent.{ExecutionContext, Future, Promise} import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.{AtomicReference, AtomicLong} @@ -35,199 +35,58 @@ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.annotation.switch import scala.collection.JavaConversions._ +import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler, MessageEncoder, MessageDecoder} -object DatabaseConnectionHandler { - val log = Log.get[DatabaseConnectionHandler] - val Name = "Netty-PostgreSQL-driver-0.1.0" +object PostgreSQLConnection { + val log = Log.get[PostgreSQLConnection] + val Name = "Netty-PostgreSQL-driver-0.1.2" InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) val Counter = new AtomicLong() } -class DatabaseConnectionHandler +class PostgreSQLConnection ( configuration: Configuration = Configuration.Default, encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance - ) extends SimpleChannelHandler with Connection { + ) + extends PostgreSQLConnectionDelegate + with Connection { - import DatabaseConnectionHandler._ + import PostgreSQLConnection._ - private val currentCount = Counter.incrementAndGet() - private val properties = List( - "user" -> configuration.username, - "database" -> configuration.database, - "application_name" -> DatabaseConnectionHandler.Name, - "client_encoding" -> configuration.charset.name(), - "DateStyle" -> "ISO", - "extra_float_digits" -> "2") + private final val connectionHandler = new PostgreSQLConnectionHandler( configuration, encoderRegistry, decoderRegistry, this ) + private final val currentCount = Counter.incrementAndGet() + private final implicit val executionContext = ExecutionContext.fromExecutorService(configuration.workerPool) private var readyForQuery = false private val parameterStatus = new ConcurrentHashMap[String, String]() private val parsedStatements = new ConcurrentHashMap[String, Array[PostgreSQLColumnData]]() - private var _processData: Option[ProcessData] = None private var authenticated = false - private val factory = new NioClientSocketChannelFactory( - configuration.bossPool, - configuration.workerPool) - - private val bootstrap = new ClientBootstrap(this.factory) private val connectionFuture = Promise[Connection]() - private var connected = false private var recentError = false private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None) private var currentQuery: Option[MutableResultSet[PostgreSQLColumnData]] = None private var currentPreparedStatement: Option[String] = None - private var _currentChannel: Option[Channel] = None def isReadyForQuery: Boolean = this.readyForQuery def connect: Future[Connection] = { - - this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - override def getPipeline(): ChannelPipeline = { - Channels.pipeline( - new MessageDecoder(configuration.charset, configuration.maximumMessageSize), - new MessageEncoder(configuration.charset, encoderRegistry), - DatabaseConnectionHandler.this) - } - - }) - - this.bootstrap.setOption("child.tcpNoDelay", true) - this.bootstrap.setOption("child.keepAlive", true) - - this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { - - if (future.isSuccess) { - connected = true - _currentChannel = Some(future.getChannel) - } else { - connectionFuture.failure(future.getCause) - } - - } - }) + this.connectionHandler.connect.onFailure { + case e => this.connectionFuture.tryFailure(e) + } this.connectionFuture.future } - override def disconnect: Future[Connection] = { - val closingPromise = Promise[Connection]() - - if (this.currentChannel.isConnected) { - this.currentChannel.write(CloseMessage).addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { - - if (future.getCause != null) { - closingPromise.failure(future.getCause) - } else { - if (future.getChannel.isOpen) { - future.getChannel.close().addListener(new ChannelFutureListener { - def operationComplete(internalFuture: ChannelFuture) { - if (internalFuture.isSuccess) { - closingPromise.success(DatabaseConnectionHandler.this) - } else { - closingPromise.failure(internalFuture.getCause) - } - } - }) - } else { - closingPromise.success(DatabaseConnectionHandler.this) - } - } - } - }) - - } else { - closingPromise.success(this) - } - - closingPromise.future - } + override def disconnect: Future[Connection] = this.connectionHandler.disconnect.map( c => this ) - override def isConnected: Boolean = { - if (this.currentChannel != null) { - this.currentChannel.isConnected - } else { - this.connected - } - } + override def isConnected: Boolean = this.connectionHandler.isConnected def parameterStatuses: scala.collection.immutable.Map[String, String] = this.parameterStatus.toMap - def processData: Option[ProcessData] = { - _processData - } - - override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { - this.connected = true - e.getChannel().write(new StartupMessage(this.properties)) - } - - override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { - - e.getMessage() match { - - case m: Message => { - - (m.name : @switch) match { - case Message.BackendKeyData => { - this._processData = Some(m.asInstanceOf[ProcessData]) - } - case Message.BindComplete => { - } - case Message.Authentication => { - this.onAuthenticationResponse(ctx.getChannel, m.asInstanceOf[AuthenticationMessage]) - } - case Message.CommandComplete => { - this.onCommandComplete(m.asInstanceOf[CommandCompleteMessage]) - } - case Message.CloseComplete => { - } - case Message.DataRow => { - this.onDataRow(m.asInstanceOf[DataRowMessage]) - } - case Message.Error => { - this.onError(m.asInstanceOf[ErrorMessage]) - } - case Message.EmptyQueryString => { - val exception = new QueryMustNotBeNullOrEmptyException(null) - this.setErrorOnFutures(exception) - } - case Message.NoData => { - } - case Message.Notice => { - } - case Message.ParameterStatus => { - this.onParameterStatus(m.asInstanceOf[ParameterStatusMessage]) - } - case Message.ParseComplete => { - } - case Message.ReadyForQuery => { - this.onReadyForQuery - } - case Message.RowDescription => { - this.onRowDescription(m.asInstanceOf[RowDescriptionMessage]) - } - case _ => { - throw new IllegalStateException("Handler not implemented for message %s".format(m.name)) - } - } - - } - case _ => { - log.error("[{}] - Unknown message type {}", this.currentCount, e.getMessage) - throw new IllegalArgumentException("Unknown message type - %s".format(e.getMessage())) - } - - } - - } - override def sendQuery(query: String): Future[QueryResult] = { validateQuery(query) this.readyForQuery = false @@ -235,7 +94,7 @@ class DatabaseConnectionHandler val promise = Promise[QueryResult]() this.setQueryPromise(promise) - this.currentChannel.write(new QueryMessage(query)) + write(new QueryMessage(query)) promise.future } @@ -270,17 +129,17 @@ class DatabaseConnectionHandler this.currentPreparedStatement = Some(realQuery) if (!this.isParsed(realQuery)) { - this.currentChannel.write(new PreparedStatementOpeningMessage(realQuery, values, this.encoderRegistry)) + write(new PreparedStatementOpeningMessage(realQuery, values, this.encoderRegistry)) } else { this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(realQuery), configuration.charset, this.decoderRegistry)) - this.currentChannel.write(new PreparedStatementExecuteMessage(realQuery, values, this.encoderRegistry)) + write(new PreparedStatementExecuteMessage(realQuery, values, this.encoderRegistry)) } promise.future } - override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { - this.setErrorOnFutures(e.getCause) + override def onError( exception : Throwable ) { + this.setErrorOnFutures(exception) } def hasRecentError: Boolean = this.recentError @@ -300,22 +159,15 @@ class DatabaseConnectionHandler this.currentPreparedStatement = None } - override def channelDisconnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { - log.info("[{}] - Connection disconnected - {}", this.currentCount, ctx.getChannel.getRemoteAddress) - this.connected = false - } - - private def onReadyForQuery { + override def onReadyForQuery() { this.recentError = false this.readyForQuery = true this.clearQueryPromise - if (!this.connectionFuture.isCompleted) { - this.connectionFuture.success(this) - } + this.connectionFuture.trySuccess(this) } - private def onError(m: ErrorMessage) { + override def onError(m: ErrorMessage) { log.error("[%s] - Error with message -> {}".format(currentCount), m) val error = new GenericDatabaseException(m) @@ -324,20 +176,20 @@ class DatabaseConnectionHandler this.setErrorOnFutures(error) } - private def onCommandComplete(m: CommandCompleteMessage) { + override def onCommandComplete(m: CommandCompleteMessage) { this.currentPreparedStatement = None this.succeedQueryPromise(new QueryResult(m.rowsAffected, m.statusMessage, this.currentQuery)) } - private def onParameterStatus(m: ParameterStatusMessage) { + override def onParameterStatus(m: ParameterStatusMessage) { this.parameterStatus.put(m.key, m.value) } - private def onDataRow(m: DataRowMessage) { + override def onDataRow(m: DataRowMessage) { this.currentQuery.get.addRawRow(m.values) } - private def onRowDescription(m: RowDescriptionMessage) { + override def onRowDescription(m: RowDescriptionMessage) { this.currentQuery = Option(new MutableResultSet(m.columnDatas, configuration.charset, this.decoderRegistry)) if (this.currentPreparedStatement.isDefined) { @@ -349,7 +201,7 @@ class DatabaseConnectionHandler this.parsedStatements.containsKey(query) } - private def onAuthenticationResponse(channel: Channel, message: AuthenticationMessage) { + override def onAuthenticationResponse(message: AuthenticationMessage) { message match { case m: AuthenticationOkMessage => { @@ -357,23 +209,15 @@ class DatabaseConnectionHandler this.authenticated = true } case m: AuthenticationChallengeCleartextMessage => { - channel.write(this.credential(m)) + write(this.credential(m)) } case m: AuthenticationChallengeMD5 => { - channel.write(this.credential(m)) + write(this.credential(m)) } } } - private def currentChannel: Channel = { - if (this._currentChannel.isDefined) { - return this._currentChannel.get - } else { - throw new NotConnectedException("This object is not connected") - } - } - private def credential(authenticationMessage: AuthenticationChallengeMessage): CredentialMessage = { if (configuration.username != null && configuration.password.isDefined) { new CredentialMessage( @@ -433,6 +277,10 @@ class DatabaseConnectionHandler } } + def write( message : ClientMessage ) { + this.connectionHandler.write(message) + } + override def toString: String = { "%s{counter=%s}".format(this.getClass.getSimpleName, this.currentCount) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala similarity index 92% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala index 79faecc7..e14f54d8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala @@ -14,16 +14,16 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql +package com.github.mauricio.async.db.postgresql.codec +import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParsersRegistry} import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import messages.backend.Message import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{ChannelHandlerContext, Channel} import org.jboss.netty.handler.codec.frame.FrameDecoder -import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} object MessageDecoder { val log = Log.get[MessageDecoder] @@ -54,7 +54,7 @@ class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder if (b.readableBytes() >= length) { code match { - case Message.Authentication => { + case ServerMessage.Authentication => { AuthenticationStartupParser.parseMessage(b) } case _ => { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala similarity index 82% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala index c87bcc55..2a122f4b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala @@ -14,10 +14,12 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql +package com.github.mauricio.async.db.postgresql.codec +import com.github.mauricio.async.db.column.ColumnEncoderRegistry +import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException import com.github.mauricio.async.db.postgresql.encoders._ -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend._ import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset @@ -25,8 +27,6 @@ import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import scala.annotation.switch -import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException -import com.github.mauricio.async.db.column.ColumnEncoderRegistry object MessageEncoder { val log = Log.get[MessageEncoder] @@ -43,14 +43,14 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e override def encode(ctx: ChannelHandlerContext, channel: Channel, msg: AnyRef): ChannelBuffer = { val buffer = msg match { - case message: FrontendMessage => { + case message: ClientMessage => { val encoder = (message.kind : @switch) match { - case Message.Close => CloseMessageEncoder - case Message.Execute => this.executeEncoder - case Message.Parse => this.openEncoder - case Message.Startup => this.startupEncoder - case Message.Query => this.queryEncoder - case Message.PasswordMessage => this.credentialEncoder + case ServerMessage.Close => CloseMessageEncoder + case ServerMessage.Execute => this.executeEncoder + case ServerMessage.Parse => this.openEncoder + case ServerMessage.Startup => this.startupEncoder + case ServerMessage.Query => this.queryEncoder + case ServerMessage.PasswordMessage => this.credentialEncoder case _ => throw new EncoderNotAvailableException(message) } encoder.encode(message) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala new file mode 100644 index 00000000..11fd9bf9 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala @@ -0,0 +1,34 @@ +/* + * 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.codec + +import com.github.mauricio.async.db.postgresql.messages.backend._ +import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage +import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage + +trait PostgreSQLConnectionDelegate { + + def onAuthenticationResponse(message: AuthenticationMessage) + def onCommandComplete( message : CommandCompleteMessage ) + def onDataRow( message : DataRowMessage ) + def onError( message : ErrorMessage ) + def onError( throwable : Throwable ) + def onParameterStatus( message : ParameterStatusMessage ) + def onReadyForQuery() + def onRowDescription(message : RowDescriptionMessage) + +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala new file mode 100644 index 00000000..ffd111a6 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -0,0 +1,209 @@ +/* + * 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.codec + +import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.column.{ColumnDecoderRegistry, ColumnEncoderRegistry} +import com.github.mauricio.async.db.postgresql.exceptions._ +import com.github.mauricio.async.db.postgresql.messages.backend._ +import com.github.mauricio.async.db.postgresql.messages.frontend._ +import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture +import com.github.mauricio.async.db.util.Log +import java.net.InetSocketAddress +import org.jboss.netty.bootstrap.ClientBootstrap +import org.jboss.netty.channel._ +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory +import scala.annotation.switch +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.util.{Failure, Success} + +object PostgreSQLConnectionHandler { + final val log = Log.get[PostgreSQLConnectionHandler] +} + +class PostgreSQLConnectionHandler +( + configuration: Configuration, + encoderRegistry: ColumnEncoderRegistry, + decoderRegistry: ColumnDecoderRegistry, + connectionDelegate : PostgreSQLConnectionDelegate + ) + extends SimpleChannelHandler + with LifeCycleAwareChannelHandler +{ + + import PostgreSQLConnectionHandler.log + + private val properties = List( + "user" -> configuration.username, + "database" -> configuration.database, + "application_name" -> "Netty-PostgreSQL-driver-0.1.0", + "client_encoding" -> configuration.charset.name(), + "DateStyle" -> "ISO", + "extra_float_digits" -> "2") + + private final val factory = new NioClientSocketChannelFactory( + configuration.bossPool, + configuration.workerPool, + 1) + + private final implicit val executionContext = ExecutionContext.fromExecutorService(configuration.workerPool) + private final val bootstrap = new ClientBootstrap(this.factory) + private final val connectionFuture = Promise[PostgreSQLConnectionHandler]() + private final val disconnectionPromise = Promise[PostgreSQLConnectionHandler]() + private var processData : ProcessData = null + + private var currentContext : ChannelHandlerContext = null + + def connect: Future[PostgreSQLConnectionHandler] = { + + this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + + override def getPipeline(): ChannelPipeline = { + Channels.pipeline( + new MessageDecoder(configuration.charset, configuration.maximumMessageSize), + new MessageEncoder(configuration.charset, encoderRegistry), + PostgreSQLConnectionHandler.this) + } + + }) + + this.bootstrap.setOption("child.tcpNoDelay", true) + this.bootstrap.setOption("child.keepAlive", true) + + this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { + case e => connectionFuture.tryFailure(e) + } + + this.connectionFuture.future + } + + def disconnect: Future[PostgreSQLConnectionHandler] = { + + if ( this.isConnected ) { + this.currentContext.getChannel.write(CloseMessage).onComplete { + case Success(writeFuture) => writeFuture.getChannel.close().onComplete { + case Success(closeFuture) => this.disconnectionPromise.trySuccess(this) + case Failure(e) => this.disconnectionPromise.tryFailure(e) + } + case Failure(e) => this.disconnectionPromise.tryFailure(e) + } + } + + this.disconnectionPromise.future + } + + def isConnected: Boolean = { + if (this.currentContext != null) { + this.currentContext.getChannel.isConnected + } else { + false + } + } + + override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { + e.getChannel().write(new StartupMessage(this.properties)) + } + + override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { + + e.getMessage() match { + + case m: ServerMessage => { + + (m.kind : @switch) match { + case ServerMessage.BackendKeyData => { + this.processData = m.asInstanceOf[ProcessData] + } + case ServerMessage.BindComplete => { + } + case ServerMessage.Authentication => { + connectionDelegate.onAuthenticationResponse(m.asInstanceOf[AuthenticationMessage]) + } + case ServerMessage.CommandComplete => { + connectionDelegate.onCommandComplete(m.asInstanceOf[CommandCompleteMessage]) + } + case ServerMessage.CloseComplete => { + } + case ServerMessage.DataRow => { + connectionDelegate.onDataRow(m.asInstanceOf[DataRowMessage]) + } + case ServerMessage.Error => { + connectionDelegate.onError(m.asInstanceOf[ErrorMessage]) + } + case ServerMessage.EmptyQueryString => { + val exception = new QueryMustNotBeNullOrEmptyException(null) + exception.fillInStackTrace() + connectionDelegate.onError(exception) + } + case ServerMessage.NoData => { + } + case ServerMessage.Notice => { + } + case ServerMessage.ParameterStatus => { + connectionDelegate.onParameterStatus(m.asInstanceOf[ParameterStatusMessage]) + } + case ServerMessage.ParseComplete => { + } + case ServerMessage.ReadyForQuery => { + connectionDelegate.onReadyForQuery() + } + case ServerMessage.RowDescription => { + connectionDelegate.onRowDescription(m.asInstanceOf[RowDescriptionMessage]) + } + case _ => { + val exception = new IllegalStateException("Handler not implemented for message %s".format(m.kind)) + exception.fillInStackTrace() + connectionDelegate.onError(exception) + } + } + + } + case _ => { + log.error("Unknown message type - {}", e.getMessage) + val exception = new IllegalArgumentException("Unknown message type - %s".format(e.getMessage())) + exception.fillInStackTrace() + connectionDelegate.onError(exception) + } + + } + + } + + override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { + connectionDelegate.onError(e.getCause) + } + + override def channelDisconnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { + log.info("Connection disconnected - {}", ctx.getChannel.getRemoteAddress) + } + + def beforeAdd(ctx: ChannelHandlerContext) { + this.currentContext = ctx + } + + def afterAdd(ctx: ChannelHandlerContext) {} + + def beforeRemove(ctx: ChannelHandlerContext) {} + + def afterRemove(ctx: ChannelHandlerContext) {} + + def write( message : ClientMessage ) { + this.currentContext.getChannel.write(message) + } + +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala index c261f792..87fac1b0 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.messages.frontend.FrontendMessage +import com.github.mauricio.async.db.postgresql.messages.frontend.ClientMessage import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} object CloseMessageEncoder extends Encoder { - override def encode(message: FrontendMessage): ChannelBuffer = { + override def encode(message: ClientMessage): ChannelBuffer = { val buffer = ChannelBuffers.buffer(5) buffer.writeByte('X') buffer.writeInt(4) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index 91ea27d3..b680cac5 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.messages.backend.{Message, AuthenticationResponseType} -import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, FrontendMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, AuthenticationResponseType} +import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, ClientMessage} import com.github.mauricio.async.db.postgresql.util.{PostgreSQLMD5Digest} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} @@ -25,7 +25,7 @@ import com.github.mauricio.async.db.util.ChannelUtils class CredentialEncoder(charset: Charset) extends Encoder { - def encode(message: FrontendMessage): ChannelBuffer = { + def encode(message: ClientMessage): ChannelBuffer = { val credentialMessage = message.asInstanceOf[CredentialMessage] @@ -43,7 +43,7 @@ class CredentialEncoder(charset: Charset) extends Encoder { } val buffer = ChannelBuffers.dynamicBuffer(1 + 4 + password.size + 1) - buffer.writeByte(Message.PasswordMessage) + buffer.writeByte(ServerMessage.PasswordMessage) buffer.writeInt(0) buffer.writeBytes(password) buffer.writeByte(0) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala index a35d0dcc..9f948e64 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.messages.frontend.FrontendMessage +import com.github.mauricio.async.db.postgresql.messages.frontend.ClientMessage import org.jboss.netty.buffer.ChannelBuffer trait Encoder { - def encode(message: FrontendMessage): ChannelBuffer + def encode(message: ClientMessage): ChannelBuffer } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index ffb6de55..8d8403f7 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.messages.backend.Message -import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementExecuteMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage +import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementExecuteMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util.ChannelUtils @@ -25,7 +25,7 @@ import com.github.mauricio.async.db.column.ColumnEncoderRegistry class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { - def encode(message: FrontendMessage): ChannelBuffer = { + def encode(message: ClientMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] @@ -33,7 +33,7 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR val bindBuffer = ChannelBuffers.dynamicBuffer(1024) - bindBuffer.writeByte(Message.Bind) + bindBuffer.writeByte(ServerMessage.Bind) bindBuffer.writeInt(0) bindBuffer.writeBytes(queryBytes) @@ -61,7 +61,7 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR val executeLength = 1 + 4 + queryBytes.length + 1 + 4 val executeBuffer = ChannelBuffers.buffer(executeLength) - executeBuffer.writeByte(Message.Execute) + executeBuffer.writeByte(ServerMessage.Execute) executeBuffer.writeInt(executeLength - 1) executeBuffer.writeBytes(queryBytes) @@ -71,7 +71,7 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR val closeLength = 1 + 4 + 1 + queryBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) - closeBuffer.writeByte(Message.CloseStatementOrPortal) + closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') @@ -79,7 +79,7 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR closeBuffer.writeByte(0) val syncBuffer = ChannelBuffers.buffer(5) - syncBuffer.writeByte(Message.Sync) + syncBuffer.writeByte(ServerMessage.Sync) syncBuffer.writeInt(4) ChannelBuffers.wrappedBuffer(bindBuffer, executeBuffer, syncBuffer, closeBuffer) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index e58fd5de..41fc892d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.messages.backend.Message -import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, PreparedStatementOpeningMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage +import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementOpeningMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util.ChannelUtils @@ -25,7 +25,7 @@ import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { - override def encode(message: FrontendMessage): ChannelBuffer = { + override def encode(message: ClientMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementOpeningMessage] @@ -34,7 +34,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val parseBuffer = ChannelBuffers.dynamicBuffer(1024) - parseBuffer.writeByte(Message.Parse) + parseBuffer.writeByte(ServerMessage.Parse) parseBuffer.writeInt(0) parseBuffer.writeBytes(queryBytes) @@ -52,7 +52,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val bindBuffer = ChannelBuffers.dynamicBuffer(1024) - bindBuffer.writeByte(Message.Bind) + bindBuffer.writeByte(ServerMessage.Bind) bindBuffer.writeInt(0) bindBuffer.writeBytes(queryBytes) @@ -80,7 +80,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val describeLength = 1 + 4 + 1 + queryBytes.length + 1 val describeBuffer = ChannelBuffers.buffer(describeLength) - describeBuffer.writeByte(Message.Describe) + describeBuffer.writeByte(ServerMessage.Describe) describeBuffer.writeInt(describeLength - 1) describeBuffer.writeByte('P') @@ -90,7 +90,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val executeLength = 1 + 4 + queryBytes.length + 1 + 4 val executeBuffer = ChannelBuffers.buffer(executeLength) - executeBuffer.writeByte(Message.Execute) + executeBuffer.writeByte(ServerMessage.Execute) executeBuffer.writeInt(executeLength - 1) executeBuffer.writeBytes(queryBytes) @@ -100,7 +100,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val closeLength = 1 + 4 + 1 + queryBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) - closeBuffer.writeByte(Message.CloseStatementOrPortal) + closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') @@ -108,7 +108,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR closeBuffer.writeByte(0) val syncBuffer = ChannelBuffers.buffer(5) - syncBuffer.writeByte(Message.Sync) + syncBuffer.writeByte(ServerMessage.Sync) syncBuffer.writeInt(4) ChannelBuffers.wrappedBuffer(parseBuffer, bindBuffer, describeBuffer, executeBuffer, closeBuffer, syncBuffer) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index 6e62a74b..3bfa23ab 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -16,20 +16,20 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.messages.backend.Message -import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, FrontendMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage +import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, ClientMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util.ChannelUtils class QueryMessageEncoder(charset: Charset) extends Encoder { - override def encode(message: FrontendMessage): ChannelBuffer = { + override def encode(message: ClientMessage): ChannelBuffer = { val m = message.asInstanceOf[QueryMessage] val buffer = ChannelBuffers.dynamicBuffer() - buffer.writeByte(Message.Query) + buffer.writeByte(ServerMessage.Query) buffer.writeInt(0) ChannelUtils.writeCString(m.query, buffer, charset) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index 46c3c0a1..332331d8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.encoders -import com.github.mauricio.async.db.postgresql.messages.frontend.{FrontendMessage, StartupMessage} +import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, StartupMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util.ChannelUtils @@ -25,7 +25,7 @@ class StartupMessageEncoder(charset: Charset) extends Encoder { //private val log = Log.getByName("StartupMessageEncoder") - override def encode(message: FrontendMessage): ChannelBuffer = { + override def encode(message: ClientMessage): ChannelBuffer = { val startup = message.asInstanceOf[StartupMessage] diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala index 860caf5b..576773db 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/AuthenticationMessage.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -abstract class AuthenticationMessage extends Message(Message.Authentication) \ No newline at end of file +abstract class AuthenticationMessage extends ServerMessage(ServerMessage.Authentication) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala index 90cd8595..56f71931 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/BindComplete.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object BindComplete extends Message(Message.BindComplete) +object BindComplete extends ServerMessage(ServerMessage.BindComplete) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala index cb1bf6a4..173bc4ba 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CloseComplete.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object CloseComplete extends Message(Message.CloseComplete) \ No newline at end of file +object CloseComplete extends ServerMessage(ServerMessage.CloseComplete) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala index d1504a13..2f689b07 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/CommandCompleteMessage.scala @@ -17,4 +17,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend case class CommandCompleteMessage(val rowsAffected: Int, val statusMessage: String) - extends Message(Message.CommandComplete) \ No newline at end of file + extends ServerMessage(ServerMessage.CommandComplete) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala index 32a592ec..3b73f78d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala @@ -18,4 +18,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend import org.jboss.netty.buffer.ChannelBuffer -case class DataRowMessage(val values: Array[ChannelBuffer]) extends Message(Message.DataRow) \ No newline at end of file +case class DataRowMessage(val values: Array[ChannelBuffer]) extends ServerMessage(ServerMessage.DataRow) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala index 09e7498e..693d1e19 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/EmptyQueryString.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object EmptyQueryString extends Message( Message.EmptyQueryString ) \ No newline at end of file +object EmptyQueryString extends ServerMessage( ServerMessage.EmptyQueryString ) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala index 6389dea2..1531e541 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ErrorMessage.scala @@ -17,4 +17,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend class ErrorMessage(fields: Map[Char, String]) - extends InformationMessage(Message.Error, fields) + extends InformationMessage(ServerMessage.Error, fields) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala index b1eeed29..14dfdc6d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/InformationMessage.scala @@ -51,7 +51,7 @@ object InformationMessage { } abstract class InformationMessage(messageType: Byte, val fields: Map[Char, String]) - extends Message(messageType) { + extends ServerMessage(messageType) { def message: String = this.fields('M') diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala index 13eb8b9d..4feac705 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoData.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object NoData extends Message( Message.NoData ) \ No newline at end of file +object NoData extends ServerMessage( ServerMessage.NoData ) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala index b2c78e4a..0a5c165e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NoticeMessage.scala @@ -17,4 +17,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend class NoticeMessage(fields: Map[Char, String]) - extends InformationMessage(Message.Notice, fields) + extends InformationMessage(ServerMessage.Notice, fields) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala index 18633c01..29b1953f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParameterStatusMessage.scala @@ -17,4 +17,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend case class ParameterStatusMessage(val key: String, val value: String) - extends Message(Message.ParameterStatus) \ No newline at end of file + extends ServerMessage(ServerMessage.ParameterStatus) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala index 1100765a..f96d3c24 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ParseComplete.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object ParseComplete extends Message(Message.ParseComplete) +object ParseComplete extends ServerMessage(ServerMessage.ParseComplete) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala index 98b51a4f..dd38a586 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ProcessData.scala @@ -17,4 +17,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend case class ProcessData(val processId: Int, val secretKey: Int) - extends Message(Message.BackendKeyData) + extends ServerMessage(ServerMessage.BackendKeyData) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala index 41594d7d..bd353dc5 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ReadyForQueryMessage.scala @@ -16,4 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend -class ReadyForQueryMessage(transactionStatus: Char) extends Message(Message.ReadyForQuery) +class ReadyForQueryMessage(transactionStatus: Char) extends ServerMessage(ServerMessage.ReadyForQuery) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala index d4da6edb..f4c58a1f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/RowDescriptionMessage.scala @@ -17,4 +17,4 @@ package com.github.mauricio.async.db.postgresql.messages.backend case class RowDescriptionMessage(val columnDatas: Array[PostgreSQLColumnData]) - extends Message(Message.RowDescription) \ No newline at end of file + extends ServerMessage(ServerMessage.RowDescription) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala similarity index 91% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala index d5cb5973..0ef530d0 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/Message.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala @@ -16,7 +16,9 @@ package com.github.mauricio.async.db.postgresql.messages.backend -object Message { +import com.github.mauricio.async.db.KindedMessage + +object ServerMessage { final val Authentication = 'R' final val BackendKeyData = 'K' final val Bind = 'B' @@ -45,4 +47,4 @@ object Message { final val Sync = 'S' } -class Message(val name: Byte) \ No newline at end of file +class ServerMessage(val kind: Int) extends KindedMessage \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/ClientMessage.scala similarity index 92% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/ClientMessage.scala index a84ff4be..5a9ea914 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/FrontendMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/ClientMessage.scala @@ -18,4 +18,4 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.KindedMessage -class FrontendMessage(val kind: Int) extends KindedMessage +class ClientMessage(val kind: Int) extends KindedMessage diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala index fd85007d..4b1f2df9 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CloseMessage.scala @@ -16,6 +16,6 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -object CloseMessage extends FrontendMessage(Message.Close) +object CloseMessage extends ClientMessage(ServerMessage.Close) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala index 1e96ab74..ae8a8908 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/CredentialMessage.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.{Message, AuthenticationResponseType} +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, AuthenticationResponseType} class CredentialMessage( @@ -25,4 +25,4 @@ class CredentialMessage( val authenticationType: AuthenticationResponseType.AuthenticationResponseType, val salt: Option[Array[Byte]] ) - extends FrontendMessage(Message.PasswordMessage) \ No newline at end of file + extends ClientMessage(ServerMessage.PasswordMessage) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index 889d501a..5862102d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementExecuteMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(Message.Execute, query, values, encoderRegistry) + extends PreparedStatementMessage(ServerMessage.Execute, query, values, encoderRegistry) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index 180c5953..240ccf70 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -24,7 +24,7 @@ class PreparedStatementMessage( val values: Seq[Any], encoderRegistry : ColumnEncoderRegistry ) - extends FrontendMessage(kind) { + extends ClientMessage(kind) { val valueTypes: Seq[Int] = values.map { value => diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index f2e9781a..13791a5a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementOpeningMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(Message.Parse, query, values, encoderRegistry) \ No newline at end of file + extends PreparedStatementMessage(ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala index 797b520c..d1dd6b1a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/QueryMessage.scala @@ -16,6 +16,6 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class QueryMessage(val query: String) extends FrontendMessage(Message.Query) \ No newline at end of file +class QueryMessage(val query: String) extends ClientMessage(ServerMessage.Query) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala index d62d25e9..e4bb34c4 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala @@ -16,6 +16,6 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class StartupMessage(val parameters: List[(String, Any)]) extends FrontendMessage(Message.Startup) \ No newline at end of file +class StartupMessage(val parameters: List[(String, Any)]) extends ClientMessage(ServerMessage.Startup) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index 10e24dc9..75547d2c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{AuthenticationChallengeMD5, AuthenticationChallengeCleartextMessage, AuthenticationOkMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{AuthenticationChallengeMD5, AuthenticationChallengeCleartextMessage, AuthenticationOkMessage, ServerMessage} import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException @@ -31,7 +31,7 @@ object AuthenticationStartupParser extends MessageParser { val AuthenticationGSSContinue = 8 val AuthenticationSSPI = 9 - override def parseMessage(b: ChannelBuffer): Message = { + override def parseMessage(b: ChannelBuffer): ServerMessage = { val authenticationType = b.readInt() diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala index c8a8bcbc..d57366dc 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{ProcessData, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ProcessData, ServerMessage} import org.jboss.netty.buffer.ChannelBuffer object BackendKeyDataParser extends MessageParser { - override def parseMessage(b: ChannelBuffer): Message = { + override def parseMessage(b: ChannelBuffer): ServerMessage = { new ProcessData(b.readInt(), b.readInt()) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 923b9d63..c80bb90f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, ServerMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils class CommandCompleteParser(charset: Charset) extends MessageParser { - override def parseMessage(b: ChannelBuffer): Message = { + override def parseMessage(b: ChannelBuffer): ServerMessage = { val result = ChannelUtils.readCString(b, charset) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala index e9d9e79e..4d8fa592 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{DataRowMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{DataRowMessage, ServerMessage} import org.jboss.netty.buffer.ChannelBuffer object DataRowParser extends MessageParser { - def parseMessage(buffer: ChannelBuffer): Message = { + def parseMessage(buffer: ChannelBuffer): ServerMessage = { val row = new Array[ChannelBuffer](buffer.readShort()) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala index ca080bd5..8c3655d6 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ErrorParser.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{ErrorMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ErrorMessage, ServerMessage} import java.nio.charset.Charset class ErrorParser(charset: Charset) extends InformationParser(charset) { - def createMessage(fields: Map[Char, String]): Message = new ErrorMessage(fields) + def createMessage(fields: Map[Char, String]): ServerMessage = new ErrorMessage(fields) } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 9ae99e09..3ccfc1a0 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils abstract class InformationParser(charset: Charset) extends MessageParser { - override def parseMessage(b: ChannelBuffer): Message = { + override def parseMessage(b: ChannelBuffer): ServerMessage = { val fields = scala.collection.mutable.Map[Char, String]() @@ -42,6 +42,6 @@ abstract class InformationParser(charset: Charset) extends MessageParser { createMessage(fields.toMap) } - def createMessage(fields: Map[Char, String]): Message + def createMessage(fields: Map[Char, String]): ServerMessage } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala index 44a60e76..5791b3c3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.Message +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import org.jboss.netty.buffer.ChannelBuffer trait MessageParser { - def parseMessage(buffer: ChannelBuffer): Message + def parseMessage(buffer: ChannelBuffer): ServerMessage } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala index 297f2102..343262de 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -31,25 +31,25 @@ class MessageParsersRegistry(charset: Charset) { private def parserFor(t: Byte): MessageParser = { t match { - case Message.Authentication => AuthenticationStartupParser - case Message.BackendKeyData => BackendKeyDataParser - case Message.BindComplete => ReturningMessageParser.BindCompleteMessageParser - case Message.CloseComplete => ReturningMessageParser.CloseCompleteMessageParser - case Message.CommandComplete => this.commandCompleteParser - case Message.DataRow => DataRowParser - case Message.Error => this.errorParser - case Message.EmptyQueryString => ReturningMessageParser.EmptyQueryStringMessageParser - case Message.NoData => ReturningMessageParser.NoDataMessageParser - case Message.Notice => this.noticeParser - case Message.ParameterStatus => this.parameterStatusParser - case Message.ParseComplete => ReturningMessageParser.ParseCompleteMessageParser - case Message.RowDescription => this.rowDescriptionParser - case Message.ReadyForQuery => ReadyForQueryParser + case ServerMessage.Authentication => AuthenticationStartupParser + case ServerMessage.BackendKeyData => BackendKeyDataParser + case ServerMessage.BindComplete => ReturningMessageParser.BindCompleteMessageParser + case ServerMessage.CloseComplete => ReturningMessageParser.CloseCompleteMessageParser + case ServerMessage.CommandComplete => this.commandCompleteParser + case ServerMessage.DataRow => DataRowParser + case ServerMessage.Error => this.errorParser + case ServerMessage.EmptyQueryString => ReturningMessageParser.EmptyQueryStringMessageParser + case ServerMessage.NoData => ReturningMessageParser.NoDataMessageParser + case ServerMessage.Notice => this.noticeParser + case ServerMessage.ParameterStatus => this.parameterStatusParser + case ServerMessage.ParseComplete => ReturningMessageParser.ParseCompleteMessageParser + case ServerMessage.RowDescription => this.rowDescriptionParser + case ServerMessage.ReadyForQuery => ReadyForQueryParser case _ => throw new ParserNotAvailableException(t) } } - def parse(t: Byte, b: ChannelBuffer): Message = { + def parse(t: Byte, b: ChannelBuffer): ServerMessage = { this.parserFor(t).parseMessage(b) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala index a8aeb11a..d0bf2b46 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NoticeParser.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{Message, NoticeMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, NoticeMessage} import java.nio.charset.Charset class NoticeParser(charset: Charset) extends InformationParser(charset) { - def createMessage(fields: Map[Char, String]): Message = new NoticeMessage(fields) + def createMessage(fields: Map[Char, String]): ServerMessage = new NoticeMessage(fields) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index 4b9ccbb0..374b8026 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, ServerMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils @@ -25,7 +25,7 @@ class ParameterStatusParser(charset: Charset) extends MessageParser { import ChannelUtils._ - override def parseMessage(b: ChannelBuffer): Message = { + override def parseMessage(b: ChannelBuffer): ServerMessage = { new ParameterStatusMessage(readCString(b, charset), readCString(b, charset)) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala index bc7201f2..7c553c67 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{ReadyForQueryMessage, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{ReadyForQueryMessage, ServerMessage} import org.jboss.netty.buffer.ChannelBuffer object ReadyForQueryParser extends MessageParser { - override def parseMessage(b: ChannelBuffer): Message = { + override def parseMessage(b: ChannelBuffer): ServerMessage = { new ReadyForQueryMessage(b.readByte().asInstanceOf[Char]) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala index 466355c7..f75baafa 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala @@ -29,8 +29,8 @@ object ReturningMessageParser { } -class ReturningMessageParser(val message: Message) extends MessageParser { +class ReturningMessageParser(val message: ServerMessage) extends MessageParser { - def parseMessage(buffer: ChannelBuffer): Message = this.message + def parseMessage(buffer: ChannelBuffer): ServerMessage = this.message } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 931e8569..4cd6b2ce 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, PostgreSQLColumnData, Message} +import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, PostgreSQLColumnData, ServerMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils @@ -61,7 +61,7 @@ The format code being used for the field. Currently will be zero (text) or one ( class RowDescriptionParser(charset: Charset) extends MessageParser { - override def parseMessage(b: ChannelBuffer): Message = { + override def parseMessage(b: ChannelBuffer): ServerMessage = { val columnsCount = b.readShort() val columns = new Array[PostgreSQLColumnData](columnsCount) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index 636a4274..17c3432e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler +import com.github.mauricio.async.db.postgresql.PostgreSQLConnection import scala.concurrent.duration._ import scala.language.postfixOps import scala.concurrent.Await @@ -37,18 +37,18 @@ object ConnectionObjectFactory { * @param configuration */ -class ConnectionObjectFactory( val configuration : Configuration ) extends ObjectFactory[DatabaseConnectionHandler] { +class ConnectionObjectFactory( val configuration : Configuration ) extends ObjectFactory[PostgreSQLConnection] { import ConnectionObjectFactory.log - def create: DatabaseConnectionHandler = { - val connection = new DatabaseConnectionHandler(configuration) + def create: PostgreSQLConnection = { + val connection = new PostgreSQLConnection(configuration) Await.result(connection.connect, 5.seconds) connection } - def destroy(item: DatabaseConnectionHandler) { + def destroy(item: PostgreSQLConnection) { item.disconnect } @@ -60,7 +60,7 @@ class ConnectionObjectFactory( val configuration : Configuration ) extends Objec * @return */ - def validate( item : DatabaseConnectionHandler ) : Try[DatabaseConnectionHandler] = { + def validate( item : PostgreSQLConnection ) : Try[PostgreSQLConnection] = { Try { if ( item.isConnected && !item.hasRecentError ) { item @@ -78,8 +78,8 @@ class ConnectionObjectFactory( val configuration : Configuration ) extends Objec * @return */ - override def test(item: DatabaseConnectionHandler): Try[DatabaseConnectionHandler] = { - val result : Try[DatabaseConnectionHandler] = Try({ + override def test(item: PostgreSQLConnection): Try[PostgreSQLConnection] = { + val result : Try[PostgreSQLConnection] = Try({ Await.result( item.sendQuery("SELECT 0"), 5.seconds ) item }) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala index 1bafad03..18edcd9c 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.examples -import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler -import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext +import com.github.mauricio.async.db.postgresql.PostgreSQLConnection +import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext import com.github.mauricio.async.db.{RowData, QueryResult, Connection} import scala.concurrent.duration._ import scala.concurrent.{Await, Future} @@ -29,7 +29,7 @@ object BasicExample { def main(args: Array[String]) { val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?username=postgres&password=somepassword") - val connection: Connection = new DatabaseConnectionHandler(configuration) + val connection: Connection = new PostgreSQLConnection(configuration) Await.result(connection.connect, 5 seconds) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala index 03e37fba..083db18f 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala @@ -21,7 +21,6 @@ import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumn import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification -import com.github.mauricio.async.db.general.MutableResultSet class MutableResultSetSpec extends Specification { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 8cdacb91..7556421c 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -18,7 +18,7 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.postgresql.exceptions.{InsufficientParametersException, QueryMustNotBeNullOrEmptyException, GenericDatabaseException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage -import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} +import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import concurrent.{Future, Await} import org.specs2.mutable.Specification @@ -282,7 +282,7 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe "transaction and flatmap example" in { - val handler: Connection = new DatabaseConnectionHandler(defaultConfiguration) + val handler: Connection = new PostgreSQLConnection(defaultConfiguration) val result: Future[QueryResult] = handler.connect .map(parameters => handler) .flatMap(connection => connection.sendQuery("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ")) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index 396db5ef..bc2f8bf6 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -40,13 +40,13 @@ trait DatabaseTestHelper { username = "postgres", database = databaseName) - def withHandler[T](fn: (DatabaseConnectionHandler) => T): T = { + def withHandler[T](fn: (PostgreSQLConnection) => T): T = { withHandler(this.defaultConfiguration, fn) } - def withHandler[T](configuration: Configuration, fn: (DatabaseConnectionHandler) => T): T = { + def withHandler[T](configuration: Configuration, fn: (PostgreSQLConnection) => T): T = { - val handler = new DatabaseConnectionHandler(configuration) + val handler = new PostgreSQLConnection(configuration) try { Await.result(handler.connect, Duration(5, SECONDS)) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index ef653cad..86f69da3 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -16,12 +16,12 @@ package com.github.mauricio.postgresql -import com.github.mauricio.async.db.postgresql.MessageDecoder -import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ErrorMessage} +import com.github.mauricio.async.db.postgresql.codec.MessageDecoder +import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ErrorMessage} import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification -import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} class MessageDecoderSpec extends Specification { @@ -74,7 +74,7 @@ class MessageDecoderSpec extends Specification { "should raise an exception if the length is negative" in { val buffer = ChannelBuffers.dynamicBuffer() - buffer.writeByte( Message.Close ) + buffer.writeByte( ServerMessage.Close ) buffer.writeInt( 2 ) this.decoder.decode(null, null, buffer) must throwA[NegativeMessageSizeException] @@ -83,7 +83,7 @@ class MessageDecoderSpec extends Specification { "should raise an exception if the length is too big" in { val buffer = ChannelBuffers.dynamicBuffer() - buffer.writeByte( Message.Close ) + buffer.writeByte( ServerMessage.Close ) buffer.writeInt( MessageDecoder.DefaultMaximumSize + 10 ) this.decoder.decode(null, null, buffer) must throwA[MessageTooLongException] diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala index e8cb95c1..d70aa94d 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ErrorMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ErrorMessage} import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification @@ -37,7 +37,7 @@ class ParserESpec extends Specification { val message = new ErrorParser(CharsetUtil.UTF_8).parseMessage(buffer).asInstanceOf[ErrorMessage] message.message === content - message.name === Message.Error + message.kind === ServerMessage.Error } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala index 46d410f8..326588a1 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ProcessData} +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ProcessData} import org.jboss.netty.buffer.ChannelBuffers import org.specs2.mutable.Specification @@ -35,7 +35,7 @@ class ParserKSpec extends Specification { val data = parser.parseMessage(buffer).asInstanceOf[ProcessData] List( - data.name === Message.BackendKeyData, + data.kind === ServerMessage.BackendKeyData, data.processId === 10, data.secretKey === 20 ) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala index bb3896cc..4ed277af 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.parsers -import com.github.mauricio.async.db.postgresql.messages.backend.{Message, ParameterStatusMessage} +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ParameterStatusMessage} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil @@ -44,7 +44,7 @@ class ParserSSpec extends Specification { content.key === key content.value === value - content.name === Message.ParameterStatus + content.kind === ServerMessage.ParameterStatus buffer.readerIndex() === buffer.writerIndex() } 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 5a757ac7..52f5dfbb 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 @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.pool -import com.github.mauricio.async.db.postgresql.{DatabaseConnectionHandler, DatabaseTestHelper} +import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} import org.specs2.mutable.Specification import com.github.mauricio.async.db.pool.{ConnectionPool, PoolConfiguration} @@ -51,7 +51,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { } - def withPool[R]( fn : (ConnectionPool[DatabaseConnectionHandler]) => R ) : R = { + def withPool[R]( fn : (ConnectionPool[PostgreSQLConnection]) => R ) : R = { val pool = new ConnectionPool( new ConnectionObjectFactory(defaultConfiguration), PoolConfiguration.Default ) try { 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 4b5e36e8..2c8b9b48 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,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.pool -import com.github.mauricio.async.db.postgresql.{DatabaseTestHelper, DatabaseConnectionHandler} +import com.github.mauricio.async.db.postgresql.{DatabaseTestHelper, PostgreSQLConnection} import java.nio.channels.ClosedChannelException import java.util.concurrent.TimeUnit import org.specs2.mutable.Specification @@ -126,7 +126,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH } def withPool[T]( - fn: (SingleThreadedAsyncObjectPool[DatabaseConnectionHandler]) => T, + fn: (SingleThreadedAsyncObjectPool[PostgreSQLConnection]) => T, maxObjects: Int = 5, maxQueueSize: Int = 5, validationInterval: Long = 3000 @@ -139,7 +139,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH validationInterval = validationInterval ) val factory = new ConnectionObjectFactory(this.defaultConfiguration) - val pool = new SingleThreadedAsyncObjectPool[DatabaseConnectionHandler](factory, poolConfiguration) + val pool = new SingleThreadedAsyncObjectPool[PostgreSQLConnection](factory, poolConfiguration) try { fn(pool) @@ -149,9 +149,9 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH } - def executeTest(connection: DatabaseConnectionHandler) = executeQuery(connection, "SELECT 0").rows.get(0)(0) === 0 + def executeTest(connection: PostgreSQLConnection) = executeQuery(connection, "SELECT 0").rows.get(0)(0) === 0 - def get(pool: SingleThreadedAsyncObjectPool[DatabaseConnectionHandler]): DatabaseConnectionHandler = { + def get(pool: SingleThreadedAsyncObjectPool[PostgreSQLConnection]): PostgreSQLConnection = { val future = pool.take Await.result(future, Duration(5, TimeUnit.SECONDS)) } From da5b56ae98ad6c781bc422a71cd5424f9e99797f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 17:01:36 -0300 Subject: [PATCH 063/357] Fixing broken spec and updating imports --- .../mauricio/async/db/mysql/MySQLConnection.scala | 2 +- .../db/mysql/codec/MySQLConnectionHandler.scala | 15 +++++++-------- .../async/db/mysql/codec/MySQLFrameDecoder.scala | 4 +--- .../db/mysql/codec/MySQLHandlerDelegate.scala | 2 +- .../mysql/decoder/ColumnDefinitionDecoder.scala | 4 ++-- .../decoder/ColumnProcessingFinishedDecoder.scala | 2 +- .../db/mysql/decoder/EOFMessageDecoder.scala | 2 +- .../async/db/mysql/decoder/ErrorDecoder.scala | 4 ++-- .../async/db/mysql/decoder/MessageDecoder.scala | 2 +- .../async/db/mysql/decoder/OkDecoder.scala | 4 ++-- .../db/mysql/decoder/ResultSetRowDecoder.scala | 2 +- .../mysql/encoder/HandshakeResponseEncoder.scala | 8 ++++---- .../db/mysql/encoder/QueryMessageEncoder.scala | 2 +- .../db/mysql/encoder/QuitMessageEncoder.scala | 2 +- .../auth/MySQLNativePasswordAuthentication.scala | 2 -- .../message/server/ResultSetRowMessage.scala | 2 +- .../async/db/mysql/ConnectionHelper.scala | 5 +---- .../async/db/mysql/MySQLConnectionSpec.scala | 2 +- .../mauricio/async/db/mysql/QuerySpec.scala | 6 ++---- .../db/postgresql/PostgreSQLConnection.scala | 9 ++------- .../codec/PostgreSQLConnectionDelegate.scala | 2 +- .../async/db/postgresql/column/ArrayDecoder.scala | 4 ++-- .../column/PostgreSQLColumnDecoderRegistry.scala | 2 +- .../column/PostgreSQLColumnEncoderRegistry.scala | 7 ++----- .../postgresql/encoders/CredentialEncoder.scala | 4 ++-- .../ExecutePreparedStatementEncoder.scala | 4 ++-- .../PreparedStatementOpeningEncoder.scala | 4 ++-- .../postgresql/encoders/QueryMessageEncoder.scala | 2 +- .../encoders/StartupMessageEncoder.scala | 2 +- .../exceptions/GenericDatabaseException.scala | 2 +- .../MissingCredentialInformationException.scala | 2 +- .../PreparedStatementExecuteMessage.scala | 2 +- .../PreparedStatementOpeningMessage.scala | 2 +- .../parsers/AuthenticationStartupParser.scala | 2 +- .../parsers/CommandCompleteParser.scala | 2 +- .../db/postgresql/parsers/InformationParser.scala | 2 +- .../parsers/MessageParsersRegistry.scala | 2 +- .../parsers/ParameterStatusParser.scala | 2 +- .../postgresql/parsers/RowDescriptionParser.scala | 2 +- .../postgresql/pool/ConnectionObjectFactory.scala | 8 ++++---- .../db/postgresql/util/ArrayStreamingParser.scala | 2 +- .../mauricio/async/db/examples/BasicExample.scala | 2 +- .../async/db/postgresql/ArrayTypesSpec.scala | 2 +- .../DatabaseConnectionHandlerSpec.scala | 4 ++-- .../async/db/postgresql/DatabaseTestHelper.scala | 3 +-- .../db/postgresql/column/ArrayDecoderSpec.scala | 2 +- .../db/postgresql/pool/ConnectionPoolSpec.scala | 4 ++-- .../pool/SingleThreadedAsyncObjectPoolSpec.scala | 2 +- 48 files changed, 71 insertions(+), 90 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index c7aa1d23..0d4389b1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.column.ColumnDecoderRegistry import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} +import com.github.mauricio.async.db.mysql.column.MySQLColumnDecoderRegistry import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage, QuitMessage, HandshakeResponseMessage} import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} @@ -28,7 +29,6 @@ import com.github.mauricio.async.db.{Connection, ResultSet, QueryResult, Configu import org.jboss.netty.channel._ import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.{Failure, Success} -import com.github.mauricio.async.db.mysql.column.MySQLColumnDecoderRegistry object MySQLConnection { val log = Log.get[MySQLConnection] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 26b90046..16daf418 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -17,7 +17,12 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage} +import com.github.mauricio.async.db.column.ColumnDecoderRegistry +import com.github.mauricio.async.db.general.{ColumnData, MutableResultSet} +import com.github.mauricio.async.db.mysql.message.client.ClientMessage +import com.github.mauricio.async.db.mysql.message.server.ErrorMessage +import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage +import com.github.mauricio.async.db.mysql.message.server.OkMessage import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture @@ -27,14 +32,8 @@ import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.channel._ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import scala.annotation.switch -import scala.concurrent.{ExecutionContext, Promise, Future} import scala.collection.mutable.ArrayBuffer -import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage -import com.github.mauricio.async.db.mysql.message.server.ErrorMessage -import com.github.mauricio.async.db.mysql.message.client.QueryMessage -import com.github.mauricio.async.db.mysql.message.server.OkMessage -import com.github.mauricio.async.db.column.ColumnDecoderRegistry -import com.github.mauricio.async.db.general.{ColumnData, MutableResultSet} +import scala.concurrent.{ExecutionContext, Promise, Future} object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 29ceccff..50ac2ece 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -17,16 +17,15 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} -import com.github.mauricio.async.db.mysql.MySQLHelper import com.github.mauricio.async.db.mysql.decoder._ import com.github.mauricio.async.db.mysql.message.server.ServerMessage import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.frame.FrameDecoder -import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] @@ -34,7 +33,6 @@ object MySQLFrameDecoder { class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { - import MySQLFrameDecoder.log private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala index 05729c75..6cab9419 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.mysql.codec +import com.github.mauricio.async.db.ResultSet import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} import org.jboss.netty.channel.ChannelHandlerContext -import com.github.mauricio.async.db.ResultSet trait MySQLHandlerDelegate { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala index 1cb19273..9fd04bce 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.decoder -import com.github.mauricio.async.db.mysql.message.server.{ColumnDefinitionMessage, ServerMessage} +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.util.Log object ColumnDefinitionDecoder { final val log = Log.get[ColumnDefinitionDecoder] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala index ad5f82ce..21b4174a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.decoder -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.message.server.{ColumnProcessingFinishedMessage, ServerMessage} +import org.jboss.netty.buffer.ChannelBuffer object ColumnProcessingFinishedDecoder extends MessageDecoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala index 7407c4fe..2c4f016c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.decoder +import com.github.mauricio.async.db.mysql.message.server.EOFMessage import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, ServerMessage} object EOFMessageDecoder extends MessageDecoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala index e778d615..922a2701 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.mysql.decoder -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.message.server.{ErrorMessage, ServerMessage} -import java.nio.charset.Charset import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer import scala.language.implicitConversions class ErrorDecoder( charset : Charset ) extends MessageDecoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala index 3a038869..136d7e41 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.decoder -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.message.server.ServerMessage +import org.jboss.netty.buffer.ChannelBuffer trait MessageDecoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala index c0d45bd5..37864e8a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.mysql.decoder -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.message.server.{OkMessage, ServerMessage} -import java.nio.charset.Charset import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer class OkDecoder( charset : Charset ) extends MessageDecoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala index 0ca4c179..a2b3ef7b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.mysql.decoder -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.message.server.{ResultSetRowMessage, ServerMessage} import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer object ResultSetRowDecoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala index 1d41c22b..2dd4032e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.encoder +import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException +import com.github.mauricio.async.db.mysql.encoder.auth.MySQLNativePasswordAuthentication import com.github.mauricio.async.db.mysql.message.client.{HandshakeResponseMessage, ClientMessage} -import org.jboss.netty.buffer.ChannelBuffer -import java.nio.charset.Charset import com.github.mauricio.async.db.mysql.util.CharsetMapper -import com.github.mauricio.async.db.mysql.encoder.auth.MySQLNativePasswordAuthentication import com.github.mauricio.async.db.util.{Log, ChannelUtils} -import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer object HandshakeResponseEncoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala index 5188b832..93401667 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.mysql.encoder import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage} -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer class QueryMessageEncoder( charset : Charset ) extends MessageEncoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala index f25a2764..7c090e26 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala @@ -17,8 +17,8 @@ package com.github.mauricio.async.db.mysql.encoder import com.github.mauricio.async.db.mysql.message.client.ClientMessage -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils +import org.jboss.netty.buffer.ChannelBuffer object QuitMessageEncoder extends MessageEncoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala index 7d21797d..d40adbc5 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala @@ -18,8 +18,6 @@ package com.github.mauricio.async.db.mysql.encoder.auth import java.nio.charset.Charset import java.security.MessageDigest -import org.jboss.netty.util.CharsetUtil -import com.github.mauricio.async.db.mysql.MySQLHelper object MySQLNativePasswordAuthentication { final val EmptyArray = Array.empty[Byte] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala index 2d001f24..5d3fec8b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.message.server -import scala.collection.mutable.ArrayBuffer import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer class ResultSetRowMessage extends ServerMessage( ServerMessage.Row ) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index 4686a12a..ecd36570 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -16,11 +16,8 @@ package com.github.mauricio.async.db.mysql -import com.github.mauricio.async.db.{QueryResult, Configuration} import com.github.mauricio.async.db.util.FutureUtils.await -import scala.concurrent.Future -import scala.util.{Failure, Success} -import com.github.mauricio.async.db.util.ExecutorServiceUtils +import com.github.mauricio.async.db.{QueryResult, Configuration} trait ConnectionHelper { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala index 00845b97..9dc02d56 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.mysql -import org.specs2.mutable.Specification import com.github.mauricio.async.db.Configuration import com.github.mauricio.async.db.util.FutureUtils.await +import org.specs2.mutable.Specification class MySQLConnectionSpec extends Specification { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 67f2d2d8..17069e5c 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -16,11 +16,9 @@ package com.github.mauricio.async.db.mysql -import org.specs2.mutable.Specification import com.github.mauricio.async.db.mysql.exceptions.MySQLException -import org.jboss.netty.util.CharsetUtil -import org.jboss.netty.buffer.ChannelBuffers -import org.joda.time.{ReadableDateTime, LocalTime, LocalDate, ReadablePartial} +import org.joda.time.{ReadableDateTime, LocalTime, LocalDate} +import org.specs2.mutable.Specification class QuerySpec extends Specification with ConnectionHelper { 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 b4b1bbd4..c423dfb9 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 @@ -18,24 +18,19 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.column.{ColumnEncoderRegistry, ColumnDecoderRegistry} import com.github.mauricio.async.db.general.MutableResultSet +import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler} import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, PostgreSQLColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} -import scala.concurrent.{ExecutionContext, Future, Promise} -import java.net.InetSocketAddress import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.{AtomicReference, AtomicLong} import messages.backend._ import messages.frontend._ -import org.jboss.netty.bootstrap.ClientBootstrap -import org.jboss.netty.channel._ -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some -import scala.annotation.switch import scala.collection.JavaConversions._ -import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler, MessageEncoder, MessageDecoder} +import scala.concurrent.{ExecutionContext, Future, Promise} object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala index 11fd9bf9..2ac7427e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.postgresql.codec -import com.github.mauricio.async.db.postgresql.messages.backend._ import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage +import com.github.mauricio.async.db.postgresql.messages.backend._ trait PostgreSQLConnectionDelegate { 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 c871b632..3e058c0f 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 @@ -16,10 +16,10 @@ 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 com.github.mauricio.async.db.postgresql.util.{ArrayStreamingParserDelegate, ArrayStreamingParser} -import com.github.mauricio.async.db.column.ColumnDecoder class ArrayDecoder(private val encoder: ColumnDecoder) extends ColumnDecoder { 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 ea91de77..e534eff3 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 @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.postgresql.column +import com.github.mauricio.async.db.column._ import com.github.mauricio.async.db.postgresql.column.ColumnTypes._ import scala.annotation.switch -import com.github.mauricio.async.db.column._ object PostgreSQLColumnDecoderRegistry { val Instance = new PostgreSQLColumnDecoderRegistry() 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 f1140908..440dd535 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 @@ -16,13 +16,10 @@ package com.github.mauricio.async.db.postgresql.column -import org.joda.time._ -import scala.collection.JavaConversions._ import com.github.mauricio.async.db.column._ -import java.sql.{Date, Time, Timestamp} -import java.util.GregorianCalendar -import java.{lang, math} +import org.joda.time._ import scala.Some +import scala.collection.JavaConversions._ object PostgreSQLColumnEncoderRegistry { val Instance = new PostgreSQLColumnEncoderRegistry() diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index b680cac5..80a89c4e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -18,10 +18,10 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, AuthenticationResponseType} import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, ClientMessage} -import com.github.mauricio.async.db.postgresql.util.{PostgreSQLMD5Digest} +import com.github.mauricio.async.db.postgresql.util.PostgreSQLMD5Digest +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.util.ChannelUtils class CredentialEncoder(charset: Charset) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 8d8403f7..2da65cbb 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.encoders +import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementExecuteMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.util.ChannelUtils -import com.github.mauricio.async.db.column.ColumnEncoderRegistry class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 41fc892d..a4a37806 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql.encoders +import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementOpeningMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.util.ChannelUtils -import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index 3bfa23ab..3d3b5db6 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -18,9 +18,9 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, ClientMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.util.ChannelUtils class QueryMessageEncoder(charset: Charset) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index 332331d8..eeb8be99 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, StartupMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.util.ChannelUtils class StartupMessageEncoder(charset: Charset) extends Encoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala index 801f4423..09d2efe2 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/GenericDatabaseException.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.exceptions -import com.github.mauricio.async.db.postgresql.messages.backend.ErrorMessage import com.github.mauricio.async.db.exceptions.DatabaseException +import com.github.mauricio.async.db.postgresql.messages.backend.ErrorMessage class GenericDatabaseException(val errorMessage: ErrorMessage) extends DatabaseException(errorMessage.toString) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala index 18f8361b..c5cdfb01 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/MissingCredentialInformationException.scala @@ -17,8 +17,8 @@ package com.github.mauricio.async.db.postgresql.exceptions -import com.github.mauricio.async.db.postgresql.messages.backend.AuthenticationResponseType import com.github.mauricio.async.db.exceptions.DatabaseException +import com.github.mauricio.async.db.postgresql.messages.backend.AuthenticationResponseType class MissingCredentialInformationException( val username: String, diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index 5862102d..4779573b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.column.ColumnEncoderRegistry +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage class PreparedStatementExecuteMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) extends PreparedStatementMessage(ServerMessage.Execute, query, values, encoderRegistry) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index 13791a5a..d77bb99f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.column.ColumnEncoderRegistry +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage class PreparedStatementOpeningMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) extends PreparedStatementMessage(ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index 75547d2c..88dd0e84 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.postgresql.parsers +import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.async.db.postgresql.messages.backend.{AuthenticationChallengeMD5, AuthenticationChallengeCleartextMessage, AuthenticationOkMessage, ServerMessage} import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException object AuthenticationStartupParser extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index c80bb90f..20ab7509 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, ServerMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.util.ChannelUtils class CommandCompleteParser(charset: Charset) extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 3ccfc1a0..0a9a645e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.util.ChannelUtils abstract class InformationParser(charset: Charset) extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala index 343262de..40e94f73 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.postgresql.parsers +import com.github.mauricio.async.db.exceptions.ParserNotAvailableException import com.github.mauricio.async.db.postgresql.messages.backend._ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.exceptions.ParserNotAvailableException class MessageParsersRegistry(charset: Charset) { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index 374b8026..74b57efb 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, ServerMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.util.ChannelUtils class ParameterStatusParser(charset: Charset) extends MessageParser { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 4cd6b2ce..9f2903d8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, PostgreSQLColumnData, ServerMessage} +import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.util.ChannelUtils /** diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index 17c3432e..cbbac280 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -17,14 +17,14 @@ package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.pool.ObjectFactory import com.github.mauricio.async.db.postgresql.PostgreSQLConnection +import com.github.mauricio.async.db.util.Log +import java.nio.channels.ClosedChannelException +import scala.concurrent.Await import scala.concurrent.duration._ import scala.language.postfixOps -import scala.concurrent.Await -import com.github.mauricio.async.db.util.Log import scala.util.{Success, Failure, Try} -import java.nio.channels.ClosedChannelException -import com.github.mauricio.async.db.pool.ObjectFactory object ConnectionObjectFactory { val log = Log.get[ConnectionObjectFactory] diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParser.scala index dca643b6..75166e80 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ArrayStreamingParser.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.postgresql.util import com.github.mauricio.async.db.postgresql.exceptions.InvalidArrayException +import com.github.mauricio.async.db.util.Log import scala.collection.mutable import scala.collection.mutable.StringBuilder -import com.github.mauricio.async.db.util.Log object ArrayStreamingParser { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala index 18edcd9c..5f31d677 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/examples/BasicExample.scala @@ -17,12 +17,12 @@ package com.github.mauricio.async.db.examples import com.github.mauricio.async.db.postgresql.PostgreSQLConnection +import com.github.mauricio.async.db.postgresql.util.URLParser import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext import com.github.mauricio.async.db.{RowData, QueryResult, Connection} import scala.concurrent.duration._ import scala.concurrent.{Await, Future} import scala.language.postfixOps -import com.github.mauricio.async.db.postgresql.util.URLParser object BasicExample { 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 f56948cd..7396aeb3 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,8 +16,8 @@ package com.github.mauricio.async.db.postgresql -import org.specs2.mutable.Specification import com.github.mauricio.async.db.column.TimestampWithTimezoneEncoderDecoder +import org.specs2.mutable.Specification class ArrayTypesSpec extends Specification with DatabaseTestHelper { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 7556421c..f20f9dfa 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -16,6 +16,8 @@ package com.github.mauricio.postgresql +import com.github.mauricio.async.db.column.{TimestampEncoderDecoder, TimeEncoderDecoder, DateEncoderDecoder} +import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.async.db.postgresql.exceptions.{InsufficientParametersException, QueryMustNotBeNullOrEmptyException, GenericDatabaseException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} @@ -24,8 +26,6 @@ import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ -import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException -import com.github.mauricio.async.db.column.{TimestampEncoderDecoder, TimeEncoderDecoder, DateEncoderDecoder} class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index bc2f8bf6..562cc3c2 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.postgresql +import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Connection, Configuration} import java.util.concurrent.{TimeoutException, TimeUnit} import scala.Some import scala.concurrent.duration._ import scala.concurrent.{Future, Await} -import com.github.mauricio.async.db.util.Log object DatabaseTestHelper { val log = Log.get[DatabaseTestHelper] @@ -29,7 +29,6 @@ object DatabaseTestHelper { trait DatabaseTestHelper { - import DatabaseTestHelper.log def databaseName = Some("netty_driver_test") diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala index 7fcfc973..75fdbb68 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.postgresql.column -import org.specs2.mutable.Specification import com.github.mauricio.async.db.column.IntegerEncoderDecoder +import org.specs2.mutable.Specification class ArrayDecoderSpec extends Specification { 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 52f5dfbb..e7f12443 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 @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.postgresql.pool +import com.github.mauricio.async.db.pool.{ConnectionPool, PoolConfiguration} import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} import org.specs2.mutable.Specification -import com.github.mauricio.async.db.pool.{ConnectionPool, PoolConfiguration} class ConnectionPoolSpec extends Specification with DatabaseTestHelper { @@ -45,7 +45,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { "return an empty map when connect is called" in { withPool { pool => - await(pool.connect) === Map[String,String]() + await(pool.connect) === pool } } 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 2c8b9b48..4abb7976 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,6 +16,7 @@ package com.github.mauricio.async.db.postgresql.pool +import com.github.mauricio.async.db.pool.{SingleThreadedAsyncObjectPool, PoolExhaustedException, PoolConfiguration} import com.github.mauricio.async.db.postgresql.{DatabaseTestHelper, PostgreSQLConnection} import java.nio.channels.ClosedChannelException import java.util.concurrent.TimeUnit @@ -23,7 +24,6 @@ import org.specs2.mutable.Specification import scala.concurrent.Await import scala.concurrent.duration._ import scala.language.postfixOps -import com.github.mauricio.async.db.pool.{SingleThreadedAsyncObjectPool, PoolExhaustedException, PoolConfiguration} class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { From 93d36d1d89ced7d18037c45fabed5c889445306a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 18:31:04 -0300 Subject: [PATCH 064/357] Removed Java code from PostgreSQL driver, fixes #8 --- README.markdown | 2 +- .../postgresql/util/PostgreSQLMD5Digest.java | 86 ------------------- .../encoders/CredentialEncoder.scala | 7 +- .../db/postgresql/util/PasswordHelper.scala | 75 ++++++++++++++++ .../postgresql/util/PasswordHelperSpec.scala | 34 ++++++++ 5 files changed, 115 insertions(+), 89 deletions(-) delete mode 100644 postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelper.scala create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala diff --git a/README.markdown b/README.markdown index 83238c94..c099b79b 100644 --- a/README.markdown +++ b/README.markdown @@ -23,7 +23,7 @@ Or Maven: ``` This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql.org/) driver under the -`com.github.mauricio.async.db.postgresql.util` package consisting of `PostgreSQLMD5Digest` and `ParseURL` classes. +`com.github.mauricio.async.db.postgresql.util` package consisting of the `ParseURL` class. ## What can it do now? diff --git a/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java b/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java deleted file mode 100644 index 5063b6ff..00000000 --- a/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/PostgreSQLMD5Digest.java +++ /dev/null @@ -1,86 +0,0 @@ -/*------------------------------------------------------------------------- -* -* Copyright (c) 2003-2011, PostgreSQL Global Development Group -* -* IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/util/MD5Digest.java,v 1.13 2011/08/02 13:50:29 davecramer Exp $ -* -*------------------------------------------------------------------------- -*/ -package com.github.mauricio.async.db.postgresql.util; - -import java.nio.charset.Charset; -import java.security.MessageDigest; - -/** - * MD5-based utility function to obfuscate passwords before network - * transmission. - * - * @author Jeremy Wohl - */ - -public class PostgreSQLMD5Digest { - private static final byte[] lookup = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f'}; - - private PostgreSQLMD5Digest() { - } - - /* - * Encodes user/password/salt information in the following way: - * MD5(MD5(password + user) + salt) - * - * @param user The connecting user. - * @param password The connecting user's password. - * @param salt A four-salt sent by the server. - * - * @return A 35-byte array, comprising the string "md5" and an MD5 digest. - */ - public static byte[] encode(String userStr, String passwordStr, byte[] salt, Charset charset) { - byte[] user = userStr.getBytes(charset); - byte[] password = passwordStr.getBytes(charset); - - MessageDigest md; - byte[] temp_digest, pass_digest; - byte[] hex_digest = new byte[35]; - - try { - md = MessageDigest.getInstance("MD5"); - - md.update(password); - md.update(user); - temp_digest = md.digest(); - - bytesToHex(temp_digest, hex_digest, 0); - md.update(hex_digest, 0, 32); - md.update(salt); - pass_digest = md.digest(); - - bytesToHex(pass_digest, hex_digest, 3); - hex_digest[0] = 'm'; - hex_digest[1] = 'd'; - hex_digest[2] = '5'; - } catch (Exception e) { - throw new IllegalStateException(e); - } - - return hex_digest; - } - - - /* - * Turn 16-byte stream into a human-readable 32-byte hex string - */ - private static void bytesToHex(byte[] bytes, byte[] hex, int offset) { - - int i, c, j, pos = offset; - - for (i = 0; i < 16; i++) { - c = bytes[i] & 0xFF; - j = c >> 4; - hex[pos++] = lookup[j]; - j = (c & 0xF); - hex[pos++] = lookup[j]; - } - } -} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index 80a89c4e..98819339 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, AuthenticationResponseType} import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, ClientMessage} -import com.github.mauricio.async.db.postgresql.util.PostgreSQLMD5Digest +import com.github.mauricio.async.db.postgresql.util.PasswordHelper import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} @@ -34,7 +34,10 @@ class CredentialEncoder(charset: Charset) extends Encoder { credentialMessage.password.getBytes(charset) } case AuthenticationResponseType.MD5 => { - PostgreSQLMD5Digest.encode( + + println("======> salt is Array[Byte](" + credentialMessage.salt.get.mkString(", ") + ")") + + PasswordHelper.encode( credentialMessage.username, credentialMessage.password, credentialMessage.salt.get, diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelper.scala new file mode 100644 index 00000000..d532b557 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelper.scala @@ -0,0 +1,75 @@ +/* + * 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 java.nio.charset.Charset +import java.security.MessageDigest + +object PasswordHelper { + + private final val Lookup = Array[Byte]('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f') + + private def bytesToHex( bytes : Array[Byte], hex : Array[Byte], offset : Int ) { + + var pos = offset + var i = 0 + + while ( i < 16 ) { + val c = bytes(i) & 0xff + var j = c >> 4 + hex(pos) = Lookup(j) + pos += 1 + j = (c & 0xf) + + hex(pos) = Lookup(j) + pos += 1 + + i += 1 + } + + } + + def encode( userText : String, passwordText : String, salt : Array[Byte], charset : Charset ) : Array[Byte] = { + val user = userText.getBytes(charset) + val password = passwordText.getBytes(charset) + + val md = MessageDigest.getInstance("MD5") + + md.update(password) + md.update(user) + + val tempDigest = md.digest() + + val hexDigest = new Array[Byte](35) + + bytesToHex(tempDigest, hexDigest, 0) + md.update(hexDigest, 0, 32) + md.update(salt) + + val passDigest = md.digest() + + bytesToHex(passDigest, hexDigest, 3) + + hexDigest(0) = 'm' + hexDigest(1) = 'd' + hexDigest(2) = '5' + + hexDigest + } + +} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala new file mode 100644 index 00000000..6b6de329 --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala @@ -0,0 +1,34 @@ +package com.github.mauricio.async.db.postgresql.util + +import org.specs2.mutable.Specification +import org.jboss.netty.util.CharsetUtil + +/** + * User: mauricio + * Date: 5/9/13 + * Time: 5:43 PM + */ +class PasswordHelperSpec extends Specification { + + val salt = Array[Byte](-31, 68, 99, 36) + val result = Array[Byte](109,100,53,54,102,57,55,57,98,99,51,101,100,100,54,101,56,52,57,49,100,52,101,99,49,55,100,57,97,51,102,97,97,55,56) + + def printArray( name : String, bytes : Array[Byte] ) { + printf("%s %s -> (%s)%n", name, bytes.length, bytes.mkString(",")) + } + + + "helper" should { + + "generate the same value as the PostgreSQL code" in { + + val username = "mauricio" + val password = "example" + + PasswordHelper.encode(username, password, salt, CharsetUtil.UTF_8) === result + + } + + } + +} From ae09cf05461a36ace93e723c1798619c44cf83c0 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 18:33:52 -0300 Subject: [PATCH 065/357] Updating changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae627ff8..52f85be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 0.1.2 (unreleased) * Optimize match/cases to `@switch` (#10) +* Reimplement the PostgreSQLMD5Digest.java in Scala - (#8) ## 0.1.1 - 2013-04-30 From 0b1b3b27176a1eba1f1fea9dcd53d6554d03a3fd Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 18:34:58 -0300 Subject: [PATCH 066/357] Yet another changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f85be6..6d94a78f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Optimize match/cases to `@switch` (#10) * Reimplement the PostgreSQLMD5Digest.java in Scala - (#8) +* Implement MySQL support, should be able to execute common statements and login with password (#9) ## 0.1.1 - 2013-04-30 From 8b5421b0f97bf8579bac9af7ccb5754e1a1e2383 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 9 May 2013 19:06:58 -0300 Subject: [PATCH 067/357] checking if github picks up an internal readme --- postgresql-async/README.md | 169 +++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 postgresql-async/README.md diff --git a/postgresql-async/README.md b/postgresql-async/README.md new file mode 100644 index 00000000..c099b79b --- /dev/null +++ b/postgresql-async/README.md @@ -0,0 +1,169 @@ +# postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10 + +The main goal of this project is to implement a performant and fully functional async PostgreSQL driver. This project +has no interest in JDBC, it's supposed to be a clean room implementation for people interested in talking directly +to PostgreSQL. + +[PostgreSQL protocol information and definition can be found here](http://www.postgresql.org/docs/devel/static/protocol.html) + +Include at your SBT project with: + +```scala +"com.github.mauricio" %% "postgresql-async" % "0.1.1" +``` + +Or Maven: + +```xml + + com.github.mauricio + postgresql-async_2.10 + 0.1.1 + +``` + +This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql.org/) driver under the +`com.github.mauricio.async.db.postgresql.util` package consisting of the `ParseURL` class. + +## What can it do now? + +- connect to a database with or without authentication (supports MD5 and cleartext authentication methods) +- receive database parameters +- receive database notices +- execute direct queries (without portals/prepared statements) +- portals/prepared statements +- parses most of the basic PostgreSQL types, other types are parsed as string +- date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects +- all work is done using the new `scala.concurrent.Future` and `scala.concurrent.Promise` objects + +## What is missing? + +- more authentication mechanisms +- benchmarks +- more tests (run the `jacoco:cover` sbt task and see where you can improve) +- timeout handler for initial handshare and queries +- implement byte array support + +## What are the design goals? + +- fast, fast and faster +- small memory footprint +- avoid copying data as much as possible (we're always trying to use wrap and slice on buffers) +- easy to use, call a method, get a future or a callback and be happy +- never, ever, block +- all features covered by tests +- next to zero dependencies (it currently depends on Netty and SFL4J only) + +## How can you help? + +- checkout the source code +- find bugs, find places where performance can be improved +- check the **What is missing** piece +- check the [issues page](https://github.com/mauricio/postgresql-async/issues) for bugs or new features +- send a pull request with specs + +## Main public interface + +### Connection + +Represents a connection to the database. This is the **root** object you will be using in your application. You will +find two classes that implement this trait, `DatabaseConnectionHandler` and `ConnectionPool`. The different between +them is that `DatabaseConnectionHandler` is a single connection and `ConnectionPool` represents a pool of connections. + +To create both you will need a `Configuration` object with your database details. You can create one manually or +create one from a JDBC or Heroku database URL using the `URLParser` object. + +### QueryResult + +It's the output of running a statement against the database (either using `sendQuery` or `sendPreparedStatement`). +This object will contain the amount of rows, status message and the possible `ResultSet` (Option\[ResultSet]) if the +query returns any rows. + +### ResultSet + +It's an IndexedSeq\[Array\[Any]], every item is a row returned by the database. The database types are returned as Scala +objects that fit the original type, so `smallint` becomes a `Short`, `numeric` becomes `BigDecimal`, `varchar` becomes +`String` and so on. You can find the whole default transformation list at the `DefaultColumnDecoderRegistry` class. + +### Prepared statements + +Databases support prepared or precompiled statements. These statements allow the database to precompile the query +on the first execution and reuse this compiled representation on future executions, this makes it faster and also allows +for safer data escaping when dealing with user provided data. + +To execute a prepared statement you grab a connection and: + +```scala +val connection : Connection = ... +val future = connection.sendPreparedStatement( "SELECT * FROM products WHERE products.name = ?", Array( "Dominion" ) ) +``` + +The `?` (question mark) in the query is a parameter placeholder, it allows you to set a value in that place in the +query without having to escape stuff yourself. The driver itself will make sure this parameter is delivered to the +database in a safe way so you don't have to worry about SQL injection attacks. + +The basic numbers, Joda Time date, time, timestamp objects, strings and arrays of these objects are all valid values +as prepared statement parameters and they will be encoded to their respective PostgreSQL types. + +Remember that parameters are positional the order they show up at query should be the same as the one in the array or +sequence given to the method call. + +## Example usage + +You can find a small Play 2 app using it [here](https://github.com/mauricio/postgresql-async-app) and a blog post about +it [here](http://mauricio.github.io/2013/04/29/async-database-access-with-postgresql-play-scala-and-heroku.html). + +In short, what you would usually do is: +```scala +import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler +import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext +import com.github.mauricio.async.db.util.URLParser +import com.github.mauricio.async.db.{RowData, QueryResult, Connection} +import scala.concurrent.duration._ +import scala.concurrent.{Await, Future} + +object BasicExample { + + def main(args: Array[String]) { + + val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?username=postgres&password=somepassword") + val connection: Connection = new DatabaseConnectionHandler(configuration) + + Await.result(connection.connect, 5 seconds) + + val future: Future[QueryResult] = connection.sendQuery("SELECT 0") + + val mapResult: Future[Any] = future.map(queryResult => queryResult.rows match { + case Some(resultSet) => { + val row : RowData = resultSet.head + row(0) + } + case None => -1 + } + ) + + val result = Await.result( mapResult, 5 seconds ) + + println(result) + + connection.disconnect + + } + +} +``` + +First, create a `DatabaseConnectionHandler`, connect it to the database, execute queries (this object is not thread +safe, so you must execute only one query at a time) and work with the futures it returns. Once you are done, call +disconnect and the connection is closed. + +You can also use the `ConnectionPool` provided by the driver to simplify working with database connections in your app. +Check the blog post above for more details and the project's ScalaDocs. + +## Supported Scala/Java types and their destination types on PostgreSQL + + + +## Licence + +This project is freely available under the Apache 2 licence, use it at your own risk. \ No newline at end of file From cdcdbfd489e44e21ef5a7acd8ef8dee2823b3fec Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 May 2013 10:49:27 -0300 Subject: [PATCH 068/357] Much more efficient bitmap implementation --- .../mauricio/async/db/util/BitMap.scala | 69 +++++++++++++++++++ .../github/mauricio/async/db/util/Flag.scala | 52 ++++++++++++++ .../mauricio/async/db/util/BitMapSpec.scala | 44 ++++++++++++ .../mauricio/async/db/util/FlagSpec.scala | 54 +++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala new file mode 100644 index 00000000..1b8a28ee --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala @@ -0,0 +1,69 @@ +/* + * 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.util + +object BitMap { + final val Bytes = Array( 128, 64, 32, 16, 8, 4, 2, 1 ) + + def apply( bytes : Seq[Byte] ) : BitMap = new BitMap(bytes : _*) + +} + +/** + * + * Implements a bit map where you can check which bits are set and which are not. + * + * @param bytes + */ + +class BitMap( bytes : Byte * ) extends IndexedSeq[(Int,Boolean)] { + + val length = bytes.length * 8 + + /** + * + * Returns true if the bit at the given index is set, false if it is not. + * + * @param index the bit position, starts at 0 + * @return + */ + + def isSet( index : Int ) : Boolean = { + val quotient = index / 8 + val remainder = index % 8 + val byte = bytes(quotient) + + (byte | BitMap.Bytes(remainder)) == byte + } + + + override def foreach[U](f: ((Int, Boolean)) => U) { + var currentIndex = 0 + for ( byte <- bytes ) { + var x = 0 + while ( x < BitMap.Bytes.length ) { + f( currentIndex, (byte | BitMap.Bytes(x)) == byte ) + x += 1 + currentIndex += 1 + } + } + } + + def apply(idx: Int): (Int, Boolean) = (idx,this.isSet(idx)) + + override def toString: String = this.map( entry => if ( entry._2 ) '1' else '0' ).mkString("") +} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala new file mode 100644 index 00000000..dc7fdd98 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala @@ -0,0 +1,52 @@ +/* + * 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.util + +/** + * + * Simplifies the represenation of a collection of flags. You should subclass this + * class and give it a default collection of flags. + * + * @param mask + * @param possibleValues + */ + +abstract class Flag[T](mask: Int, possibleValues: Map[String, Int]) { + + def +(flag: Int) : T = create(mask | flag) + + def -(flag: Int) : T = create(mask & ~flag) + + def has(flag: Int) : Boolean = hasAll(flag) + + protected def create( value : Int ) : T + + def hasAll(flags: Int*) : Boolean = flags.map { + f => (f & mask) > 0 + }.reduceLeft { + _ && _ + } + + override def toString() : String = { + val cs = possibleValues.filter { + case (key, value) => has(value) + }.map { + case (key,value) => key + }.mkString (", ") + s"${this.getClass.getSimpleName}(" + mask + ": " + cs + ")" + } +} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala new file mode 100644 index 00000000..f9d962c4 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala @@ -0,0 +1,44 @@ +/* + * 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.util + +import org.specs2.mutable.Specification + +class BitMapSpec extends Specification { + + "bitmap" should { + + "correctly set and unset bits" in { + + val bitMap = new BitMap(0x32, 121) + + bitMap.isSet(0) must beFalse + bitMap.isSet(1) must beFalse + bitMap.isSet(2) must beTrue + bitMap.isSet(3) must beTrue + bitMap.isSet(4) must beFalse + bitMap.isSet(5) must beFalse + bitMap.isSet(6) must beTrue + bitMap.isSet(7) must beFalse + + bitMap.toString === "0011001001111001" + + } + + } + +} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala new file mode 100644 index 00000000..6438c465 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala @@ -0,0 +1,54 @@ +/* + * 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.util + +import org.specs2.mutable.Specification + +class FlagSpec extends Specification { + + import SampleFlag._ + + "flag" should { + + "compose on flags correctly" in { + + val flag = new SampleFlag(0) + Flag_1 + flag.has(Flag_1) must beTrue + + } + + } + +} + +object SampleFlag { + + val Flag_1 = 0x0080 + val Flag_2 = 0x0100 + val Flag_3 = 0x0200 + + val FlagMap = Map( + "Flag_1" -> Flag_1, + "Flag_2" -> Flag_2, + "Flag_3" -> Flag_3 + ) + +} + +class SampleFlag( flag : Int ) extends Flag[SampleFlag]( flag, SampleFlag.FlagMap ) { + protected def create(value: Int): SampleFlag = new SampleFlag(value) +} \ No newline at end of file From b2e578768d04f4f650c3356aaaa92c10e609a336 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 May 2013 11:42:40 -0300 Subject: [PATCH 069/357] Applying fix for #13 on master from @fwbrasil --- .../github/mauricio/async/db/util/BitMap.scala | 15 ++++++++++----- .../mauricio/async/db/util/BitMapSpec.scala | 4 ++-- .../db/postgresql/PostgreSQLConnection.scala | 9 ++++++++- .../codec/PostgreSQLConnectionDelegate.scala | 1 + .../codec/PostgreSQLConnectionHandler.scala | 3 +++ .../DatabaseConnectionHandlerSpec.scala | 14 ++++++++++++-- 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala index 1b8a28ee..469b7c28 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.util object BitMap { final val Bytes = Array( 128, 64, 32, 16, 8, 4, 2, 1 ) - def apply( bytes : Seq[Byte] ) : BitMap = new BitMap(bytes : _*) + def apply( bytes : Byte * ) : BitMap = new BitMap(bytes.toArray) } @@ -30,7 +30,7 @@ object BitMap { * @param bytes */ -class BitMap( bytes : Byte * ) extends IndexedSeq[(Int,Boolean)] { +class BitMap( bytes : Array[Byte] ) extends IndexedSeq[(Int,Boolean)] { val length = bytes.length * 8 @@ -45,18 +45,23 @@ class BitMap( bytes : Byte * ) extends IndexedSeq[(Int,Boolean)] { def isSet( index : Int ) : Boolean = { val quotient = index / 8 val remainder = index % 8 - val byte = bytes(quotient) - (byte | BitMap.Bytes(remainder)) == byte + (bytes(quotient) & BitMap.Bytes(remainder)) != 0 } + def set( index : Int ) { + val quotient = index / 8 + val remainder = index % 8 + + bytes(quotient) = (bytes(quotient) | BitMap.Bytes(remainder)).asInstanceOf[Byte] + } override def foreach[U](f: ((Int, Boolean)) => U) { var currentIndex = 0 for ( byte <- bytes ) { var x = 0 while ( x < BitMap.Bytes.length ) { - f( currentIndex, (byte | BitMap.Bytes(x)) == byte ) + f( currentIndex, (byte & BitMap.Bytes(x)) != 0 ) x += 1 currentIndex += 1 } diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala index f9d962c4..11f3304d 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala @@ -24,7 +24,7 @@ class BitMapSpec extends Specification { "correctly set and unset bits" in { - val bitMap = new BitMap(0x32, 121) + val bitMap = BitMap(0x32, 121) bitMap.isSet(0) must beFalse bitMap.isSet(1) must beFalse @@ -35,7 +35,7 @@ class BitMapSpec extends Specification { bitMap.isSet(6) must beTrue bitMap.isSet(7) must beFalse - bitMap.toString === "0011001001111001" + bitMap.toString === Array("0011", "0010", "0111", "1001").mkString("") } 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 c423dfb9..c9417c14 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 @@ -184,11 +184,18 @@ class PostgreSQLConnection this.currentQuery.get.addRawRow(m.values) } + override def onParseComplete() { + setColumnDatas(Array.empty) + } + override def onRowDescription(m: RowDescriptionMessage) { this.currentQuery = Option(new MutableResultSet(m.columnDatas, configuration.charset, this.decoderRegistry)) + this.setColumnDatas(m.columnDatas) + } + private def setColumnDatas( columnDatas : Array[PostgreSQLColumnData] ) { if (this.currentPreparedStatement.isDefined) { - this.parsedStatements.put(this.currentPreparedStatement.get, m.columnDatas) + this.parsedStatements.put(this.currentPreparedStatement.get, columnDatas) } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala index 2ac7427e..69936392 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala @@ -29,6 +29,7 @@ trait PostgreSQLConnectionDelegate { def onError( throwable : Throwable ) def onParameterStatus( message : ParameterStatusMessage ) def onReadyForQuery() + def onParseComplete() def onRowDescription(message : RowDescriptionMessage) } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index ffd111a6..c51013e3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -158,11 +158,14 @@ class PostgreSQLConnectionHandler connectionDelegate.onParameterStatus(m.asInstanceOf[ParameterStatusMessage]) } case ServerMessage.ParseComplete => { + log.debug("Parse complete received - {}", m) + connectionDelegate.onParseComplete() } case ServerMessage.ReadyForQuery => { connectionDelegate.onReadyForQuery() } case ServerMessage.RowDescription => { + log.debug("Row description received - {}", m) connectionDelegate.onRowDescription(m.asInstanceOf[RowDescriptionMessage]) } case _ => { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index f20f9dfa..4362dd93 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -205,6 +205,18 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } + "execute a prepared statement without any parameters multiple times" in { + + withHandler { + handler => + executeDdl(handler, this.messagesCreate) + executePreparedStatement(handler, "UPDATE messages SET content = content") + executePreparedStatement(handler, "UPDATE messages SET content = content") + ok + } + + } + "login using MD5 authentication" in { val configuration = new Configuration( @@ -350,8 +362,6 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } - - } } From 795e8a0263aa759a1b1168700a449146a20bec15 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 May 2013 11:44:55 -0300 Subject: [PATCH 070/357] Updating changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d94a78f..deaf99f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.1.2 (unreleased) +* Multiple executions of a prepared statement that doesn't return rows fail - @fwbrasil - #13 * Optimize match/cases to `@switch` (#10) * Reimplement the PostgreSQLMD5Digest.java in Scala - (#8) * Implement MySQL support, should be able to execute common statements and login with password (#9) From 8fd1e299bdb3440fa12c0cb1035b31bf6dbc6308 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Fri, 10 May 2013 14:01:59 -0300 Subject: [PATCH 071/357] do not accept returned connections to pool that aren't ready for query --- .../db/postgresql/PostgreSQLConnection.scala | 10 +++++++--- .../postgresql/pool/ConnectionObjectFactory.scala | 9 +++++---- .../pool/SingleThreadedAsyncObjectPoolSpec.scala | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) 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 c9417c14..da3a6661 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 @@ -235,15 +235,19 @@ class PostgreSQLConnection authenticationMessage.challengeType) } } - - private def validateQuery(query: String) { + + def validateIfItIsReadyForQuery(errorMessage: String) = if (this.queryPromise.isDefined) { - log.error("[{}] - Can't run query because there is one query pending already", this.currentCount) + log.error(errorMessage, this.currentCount) throw new ConnectionStillRunningQueryException( this.currentCount, this.readyForQuery ) } + + private def validateQuery(query: String) { + this.validateIfItIsReadyForQuery( + errorMessage = "[{}] - Can't run query because there is one query pending already") if (query == null || query.isEmpty) { throw new QueryMustNotBeNullOrEmptyException(query) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index cbbac280..6d6d430b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -62,11 +62,12 @@ class ConnectionObjectFactory( val configuration : Configuration ) extends Objec def validate( item : PostgreSQLConnection ) : Try[PostgreSQLConnection] = { Try { - if ( item.isConnected && !item.hasRecentError ) { - item - } else { + if ( !item.isConnected || item.hasRecentError ) { throw new ClosedChannelException() - } + } + item.validateIfItIsReadyForQuery( + errorMessage = "[{}] - Trying to give back a connection that is not ready for query") + item } } 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 4abb7976..70749be4 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 @@ -24,6 +24,7 @@ import org.specs2.mutable.Specification import scala.concurrent.Await import scala.concurrent.duration._ import scala.language.postfixOps +import com.github.mauricio.async.db.postgresql.exceptions.ConnectionStillRunningQueryException class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { @@ -122,6 +123,20 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH } } + + "it should not accept returned connections that aren't ready for query" in { + + withPool { + pool => + val connection = get(pool) + connection.sendPreparedStatement("select 1") + + await(pool.giveBack(connection)) must throwA[ConnectionStillRunningQueryException] + pool.availables.size === 0 + pool.inUse.size === 0 + } + + } } From 8b00f0832e2b5df2110203bf9173c76ef8e91260 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Fri, 10 May 2013 23:08:38 -0300 Subject: [PATCH 072/357] fix #16 - used a hex string hash code as the prepared statement id - used unnamed portals --- .../db/postgresql/PostgreSQLConnection.scala | 19 +++++++++++------- .../ExecutePreparedStatementEncoder.scala | 15 +++++++------- .../PreparedStatementOpeningEncoder.scala | 20 ++++++++++--------- .../PreparedStatementExecuteMessage.scala | 4 ++-- .../frontend/PreparedStatementMessage.scala | 1 + .../PreparedStatementOpeningMessage.scala | 4 ++-- .../DatabaseConnectionHandlerSpec.scala | 13 +++++++++++- 7 files changed, 48 insertions(+), 28 deletions(-) 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 da3a6661..cf4ed05f 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 @@ -123,15 +123,20 @@ class PostgreSQLConnection this.setQueryPromise(promise) this.currentPreparedStatement = Some(realQuery) - if (!this.isParsed(realQuery)) { - write(new PreparedStatementOpeningMessage(realQuery, values, this.encoderRegistry)) + val queryId = this.queryId(realQuery) + + if (!this.isParsed(queryId)) { + write(new PreparedStatementOpeningMessage(queryId, realQuery, values, this.encoderRegistry)) } else { - this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(realQuery), configuration.charset, this.decoderRegistry)) - write(new PreparedStatementExecuteMessage(realQuery, values, this.encoderRegistry)) + this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(queryId), configuration.charset, this.decoderRegistry)) + write(new PreparedStatementExecuteMessage(queryId, realQuery, values, this.encoderRegistry)) } promise.future } + + private def queryId(query: String) = + Integer.toHexString(query.hashCode) override def onError( exception : Throwable ) { this.setErrorOnFutures(exception) @@ -195,12 +200,12 @@ class PostgreSQLConnection private def setColumnDatas( columnDatas : Array[PostgreSQLColumnData] ) { if (this.currentPreparedStatement.isDefined) { - this.parsedStatements.put(this.currentPreparedStatement.get, columnDatas) + this.parsedStatements.put(queryId(this.currentPreparedStatement.get), columnDatas) } } - private def isParsed(query: String): Boolean = { - this.parsedStatements.containsKey(query) + private def isParsed(queryId: String): Boolean = { + this.parsedStatements.containsKey(queryId) } override def onAuthenticationResponse(message: AuthenticationMessage) { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 2da65cbb..e9bd1138 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -29,16 +29,17 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR val m = message.asInstanceOf[PreparedStatementExecuteMessage] + val emptyStringBytes = "".getBytes(charset) val queryBytes = m.query.getBytes(charset) - + val queryIdBytes = m.queryId.getBytes(charset) val bindBuffer = ChannelBuffers.dynamicBuffer(1024) bindBuffer.writeByte(ServerMessage.Bind) bindBuffer.writeInt(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes("".getBytes(charset)) bindBuffer.writeByte(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes(queryIdBytes) bindBuffer.writeByte(0) bindBuffer.writeShort(0) @@ -59,23 +60,23 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR ChannelUtils.writeLength(bindBuffer) - val executeLength = 1 + 4 + queryBytes.length + 1 + 4 + val executeLength = 1 + 4 + emptyStringBytes.length + 1 + 4 val executeBuffer = ChannelBuffers.buffer(executeLength) executeBuffer.writeByte(ServerMessage.Execute) executeBuffer.writeInt(executeLength - 1) - executeBuffer.writeBytes(queryBytes) + executeBuffer.writeBytes(emptyStringBytes) executeBuffer.writeByte(0) executeBuffer.writeInt(0) - val closeLength = 1 + 4 + 1 + queryBytes.length + 1 + val closeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') - closeBuffer.writeBytes(queryBytes) + closeBuffer.writeBytes(emptyStringBytes) closeBuffer.writeByte(0) val syncBuffer = ChannelBuffers.buffer(5) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index a4a37806..41a780a3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -29,7 +29,9 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val m = message.asInstanceOf[PreparedStatementOpeningMessage] + val emptyStringBytes = "".getBytes(charset) val queryBytes = m.query.getBytes(charset) + val queryIdBytes = m.queryId.getBytes(charset) val columnCount = m.valueTypes.size val parseBuffer = ChannelBuffers.dynamicBuffer(1024) @@ -37,7 +39,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR parseBuffer.writeByte(ServerMessage.Parse) parseBuffer.writeInt(0) - parseBuffer.writeBytes(queryBytes) + parseBuffer.writeBytes(queryIdBytes) parseBuffer.writeByte(0) parseBuffer.writeBytes(queryBytes) parseBuffer.writeByte(0) @@ -55,9 +57,9 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR bindBuffer.writeByte(ServerMessage.Bind) bindBuffer.writeInt(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes(emptyStringBytes) bindBuffer.writeByte(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes(queryIdBytes) bindBuffer.writeByte(0) bindBuffer.writeShort(0) @@ -78,33 +80,33 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR ChannelUtils.writeLength(bindBuffer) - val describeLength = 1 + 4 + 1 + queryBytes.length + 1 + val describeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 val describeBuffer = ChannelBuffers.buffer(describeLength) describeBuffer.writeByte(ServerMessage.Describe) describeBuffer.writeInt(describeLength - 1) describeBuffer.writeByte('P') - describeBuffer.writeBytes(queryBytes) + describeBuffer.writeBytes(emptyStringBytes) describeBuffer.writeByte(0) - val executeLength = 1 + 4 + queryBytes.length + 1 + 4 + val executeLength = 1 + 4 + emptyStringBytes.length + 1 + 4 val executeBuffer = ChannelBuffers.buffer(executeLength) executeBuffer.writeByte(ServerMessage.Execute) executeBuffer.writeInt(executeLength - 1) - executeBuffer.writeBytes(queryBytes) + executeBuffer.writeBytes(emptyStringBytes) executeBuffer.writeByte(0) executeBuffer.writeInt(0) - val closeLength = 1 + 4 + 1 + queryBytes.length + 1 + val closeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') - closeBuffer.writeBytes(queryBytes) + closeBuffer.writeBytes(emptyStringBytes) closeBuffer.writeByte(0) val syncBuffer = ChannelBuffers.buffer(5) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index 4779573b..7fc1d93c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class PreparedStatementExecuteMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(ServerMessage.Execute, query, values, encoderRegistry) +class PreparedStatementExecuteMessage(queryId: String, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(queryId, ServerMessage.Execute, query, values, encoderRegistry) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index 240ccf70..a76b8208 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementMessage( + val queryId: String, kind: Byte, val query: String, val values: Seq[Any], diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index d77bb99f..bdce254d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class PreparedStatementOpeningMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file +class PreparedStatementOpeningMessage(queryId: String, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(queryId: String, ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 4362dd93..2a7e1dae 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -361,7 +361,18 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } } - + + "support prepared statement with more than 64 characters" in { + withHandler { + handler => + executeDdl( handler, this.messagesCreate ) + val stmt = "SELECT id, content, moment FROM messages WHERE id is not null AND content is not null " + executePreparedStatement(handler, stmt + "AND moment is not null") + executePreparedStatement(handler, stmt + "AND moment is null") + ok + } + } + } } From 30c34c5cb38e2350008f88af36089c8d2bfc6fa1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 May 2013 00:30:14 -0300 Subject: [PATCH 073/357] Initial prepared statement support on MySQL --- CHANGELOG.md | 1 + .../async/db/general/ArrayRowData.scala | 6 +- .../async/db/general/MutableResultSet.scala | 10 ++ .../mauricio/async/db/util/BitMap.scala | 16 +++ .../mauricio/async/db/util/PrintUtils.scala | 33 +++++ .../async/db/mysql/MySQLConnection.scala | 30 ++-- .../db/mysql/binary/BinaryRowDecoder.scala | 80 +++++++++++ .../mysql/binary/decoder/BinaryDecoder.scala | 25 ++++ .../db/mysql/binary/decoder/LongDecoder.scala | 23 +++ .../mysql/binary/decoder/StringDecoder.scala | 25 ++++ .../mysql/binary/encoder/BinaryEncoder.scala | 25 ++++ .../mysql/codec/MySQLConnectionHandler.scala | 85 ++++++++--- .../db/mysql/codec/MySQLFrameDecoder.scala | 133 ++++++++++++++---- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 15 +- .../mysql/codec/PreparedStatementHolder.scala | 47 +++++++ .../decoder/ColumnDefinitionDecoder.scala | 2 +- .../ParamProcessingFinishedDecoder.scala | 28 ++++ ...paredStatementPrepareResponseDecoder.scala | 50 +++++++ .../PreparedStatementExecuteEncoder.scala | 37 +++++ .../PreparedStatementPrepareEncoder.scala | 36 +++++ .../mysql/message/client/ClientMessage.scala | 3 + .../PreparedStatementExecuteMessage.scala | 20 +++ .../client/PreparedStatementMessage.scala | 20 +++ .../PreparedStatementPrepareMessage.scala | 20 +++ .../db/mysql/message/client/QuitMessage.scala | 6 +- .../message/server/BinaryRowMessage.scala | 21 +++ .../server/ColumnDefinitionMessage.scala | 2 +- .../ParamProcessingFinishedMessage.scala | 20 +++ .../PreparedStatementPrepareResponse.scala | 24 ++++ .../mysql/message/server/ServerMessage.scala | 5 +- .../async/db/mysql/ConnectionHelper.scala | 4 + .../db/mysql/PreparedStatementsSpec.scala | 46 ++++++ .../mysql/binary/BinaryRowDecoderSpec.scala | 66 +++++++++ 33 files changed, 901 insertions(+), 63 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/PreparedStatementHolder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementPrepareMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamProcessingFinishedMessage.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/PreparedStatementPrepareResponse.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index deaf99f7..ac5275a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.1.2 (unreleased) +* Do not accept returned connections to pool that aren't ready for query - @fwbrasil - #15 * Multiple executions of a prepared statement that doesn't return rows fail - @fwbrasil - #13 * Optimize match/cases to `@switch` (#10) * Reimplement the PostgreSQLMD5Digest.java in Scala - (#8) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala index 386a343c..c232a12a 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala @@ -17,8 +17,11 @@ package com.github.mauricio.async.db.general import com.github.mauricio.async.db.RowData +import scala.collection.mutable -class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] ) extends RowData { +class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] ) + extends RowData +{ private val columns = new Array[Any](columnCount) @@ -59,4 +62,5 @@ class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] def update(i: Int, x: Any) = columns(i) = x def length: Int = columns.length + } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index 907aae14..e6490843 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -74,4 +74,14 @@ class MutableResultSet[T <: ColumnData]( this.rows += realRow } + def addRow( row : Seq[Any] ) { + val realRow = new ArrayRowData( columnMapping.size, this.rows.size, this.columnMapping ) + var x = 0 + while ( x < row.size ) { + realRow(x) = row(x) + x += 1 + } + this.rows += realRow + } + } \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala index 469b7c28..ada45f00 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala @@ -68,6 +68,22 @@ class BitMap( bytes : Array[Byte] ) extends IndexedSeq[(Int,Boolean)] { } } + def foreachWithLimit[U]( limit : Int, f: ((Int, Boolean)) => U ) { + var currentIndex = 0 + for ( byte <- bytes ) { + var x = 0 + while ( x < BitMap.Bytes.length ) { + f( currentIndex, (byte & BitMap.Bytes(x)) != 0 ) + x += 1 + currentIndex += 1 + + if ( currentIndex >= limit ) { + return + } + } + } + } + def apply(idx: Int): (Int, Boolean) = (idx,this.isSet(idx)) override def toString: String = this.map( entry => if ( entry._2 ) '1' else '0' ).mkString("") diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala new file mode 100644 index 00000000..9bd33703 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala @@ -0,0 +1,33 @@ +/* + * 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.util + +import org.jboss.netty.buffer.ChannelBuffer + +object PrintUtils { + + private val log = Log.getByName(this.getClass.getName) + + def printArray( name : String, buffer : ChannelBuffer ) { + buffer.markReaderIndex() + val bytes = new Array[Byte](buffer.readableBytes()) + buffer.readBytes(bytes) + buffer.resetReaderIndex() + log.debug( s"$name Array[Byte](${bytes.mkString(", ")})" ) + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 0d4389b1..97b38d83 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -20,15 +20,17 @@ import com.github.mauricio.async.db.column.ColumnDecoderRegistry import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} import com.github.mauricio.async.db.mysql.column.MySQLColumnDecoderRegistry import com.github.mauricio.async.db.mysql.exceptions.MySQLException -import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage, QuitMessage, HandshakeResponseMessage} -import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} +import com.github.mauricio.async.db.mysql.message.client._ +import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.{Connection, ResultSet, QueryResult, Configuration} +import com.github.mauricio.async.db._ import org.jboss.netty.channel._ +import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} -import scala.util.{Failure, Success} +import scala.util.Failure +import scala.util.Success object MySQLConnection { val log = Log.get[MySQLConnection] @@ -54,6 +56,7 @@ class MySQLConnection( private final val connectionPromise = Promise[Connection]() private final val disconnectionPromise = Promise[Connection]() + private var queryPromise: Promise[QueryResult] = null private var connected = false @@ -69,7 +72,7 @@ class MySQLConnection( if ( this.isConnected ) { if (!this.disconnectionPromise.isCompleted) { - this.connectionHandler.write(QuitMessage).onComplete { + this.connectionHandler.write(QuitMessage.Instance).onComplete { case Success(channelFuture) => { this.connectionHandler.disconnect.onComplete { case Success(closeFuture) => this.disconnectionPromise.trySuccess(this) @@ -91,13 +94,11 @@ class MySQLConnection( override def exceptionCaught(throwable: Throwable) { log.error("Transport failure", throwable) - this.connectionPromise.tryFailure(throwable) this.failQueryPromise(throwable) } override def onOk(message: OkMessage) { - log.debug("Received OK {}", message) this.connectionPromise.trySuccess(this) if (this.isQuerying) { @@ -115,7 +116,6 @@ class MySQLConnection( } def onEOF(message: EOFMessage) { - log.debug("Received EOF message - {}", message) if (this.isQuerying) { this.succeedQueryPromise( new MySQLQueryResult( @@ -130,8 +130,6 @@ class MySQLConnection( } override def onHandshake(message: HandshakeMessage) { - log.debug("Received handshake message - {}", message) - this.connectionHandler.write(new HandshakeResponseMessage( configuration.username, configuration.charset, @@ -152,14 +150,10 @@ class MySQLConnection( def sendQuery(query: String): Future[QueryResult] = { this.queryPromise = Promise[QueryResult] - this.write(new QueryMessage(query)) + this.connectionHandler.write(new QueryMessage(query)) this.queryPromise.future } - private def write(message: ClientMessage): ChannelFuture = { - this.connectionHandler.write(message) - } - private def failQueryPromise(t: Throwable) { if (this.isQuerying) { @@ -202,5 +196,9 @@ class MySQLConnection( def isConnected: Boolean = this.connectionHandler.isConnected - def sendPreparedStatement(query: String, values: Seq[Any]): Future[QueryResult] = ??? + def sendPreparedStatement(query: String, values: Seq[Any]): Future[QueryResult] = { + this.queryPromise = Promise[QueryResult] + this.connectionHandler.write(new PreparedStatementMessage(query, values)) + this.queryPromise.future + } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala new file mode 100644 index 00000000..8ea0343d --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -0,0 +1,80 @@ +/* + * 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.mysql.binary + +import com.github.mauricio.async.db.exceptions.BufferNotFullyConsumedException +import com.github.mauricio.async.db.mysql.binary.decoder.{LongDecoder, StringDecoder, BinaryDecoder} +import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage +import com.github.mauricio.async.db.util.{Log, PrintUtils, BitMap} +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer +import scala.collection.mutable.ArrayBuffer +import com.github.mauricio.async.db.mysql.MySQLHelper + +object BinaryRowDecoder { + final val log = Log.get[BinaryRowDecoder] +} + +class BinaryRowDecoder( charset : Charset ) { + + import BinaryRowDecoder.log + + private final val stringDecoder = new StringDecoder(charset) + + def decode( buffer : ChannelBuffer, columns : Seq[ColumnDefinitionMessage] ) : IndexedSeq[Any] = { + + log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) + + val totalBytes = (columns.size + 7 + 2) + val quotient = totalBytes / 8 + val remainder = totalBytes % 8 + + val bitMapSource = new Array[Byte]( if ( remainder == 0 ) quotient else quotient + 1 ) + + log.debug("Bit map size is {} - columns count is {}", bitMapSource.length, columns.length) + + buffer.readBytes(bitMapSource) + val bitMap = new BitMap(bitMapSource) + + val row = new ArrayBuffer[Any](columns.size) + + bitMap.foreachWithLimit(columns.length, { + case ( index, isNull ) => { + if ( isNull ) { + row += null + } else { + row += decoderFor(columns(index).columnType).decode(buffer) + } + } + }) + + if ( buffer.readableBytes() != 0 ) { + throw new BufferNotFullyConsumedException(buffer) + } + + row + } + + def decoderFor( columnType : Int ) : BinaryDecoder = { + columnType match { + case ColumnTypes.FIELD_TYPE_VARCHAR | ColumnTypes.FIELD_TYPE_VAR_STRING => this.stringDecoder + case ColumnTypes.FIELD_TYPE_LONGLONG => LongDecoder + } + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala new file mode 100644 index 00000000..605fc6d4 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +trait BinaryDecoder { + + def decode( buffer : ChannelBuffer ) : Any + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala new file mode 100644 index 00000000..22de9a7b --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +object LongDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = buffer.readLong() +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala new file mode 100644 index 00000000..d4e96596 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import java.nio.charset.Charset + +class StringDecoder( charset : Charset ) extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = buffer.readLengthEncodedString(charset) +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala new file mode 100644 index 00000000..9444326a --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +trait BinaryEncoder { + + def encode( value : Any ) : ChannelBuffer + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 16daf418..a9eb4e38 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -19,10 +19,7 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.Configuration import com.github.mauricio.async.db.column.ColumnDecoderRegistry import com.github.mauricio.async.db.general.{ColumnData, MutableResultSet} -import com.github.mauricio.async.db.mysql.message.client.ClientMessage -import com.github.mauricio.async.db.mysql.message.server.ErrorMessage -import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage -import com.github.mauricio.async.db.mysql.message.server.OkMessage +import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture @@ -32,8 +29,11 @@ import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.channel._ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import scala.annotation.switch -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent.{ExecutionContext, Promise, Future} +import com.github.mauricio.async.db.mysql.binary.BinaryRowDecoder +import org.jboss.netty.buffer.HeapChannelBufferFactory +import java.nio.ByteOrder object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -62,6 +62,11 @@ class MySQLConnectionHandler( private final val decoder = new MySQLFrameDecoder(configuration.charset) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() + private final val parsedStatements = new HashMap[String,PreparedStatementHolder]() + private final val binaryRowDecoder = new BinaryRowDecoder(configuration.charset) + + private var currentPreparedStatementHolder : PreparedStatementHolder = null + private var currentPreparedStatement : PreparedStatementMessage = null private var currentQuery : MutableResultSet[ColumnData] = null private var currentContext: ChannelHandlerContext = null @@ -81,6 +86,8 @@ class MySQLConnectionHandler( this.bootstrap.setOption("child.tcpNoDelay", true) this.bootstrap.setOption("child.keepAlive", true) + this.bootstrap.setOption("bufferFactory", HeapChannelBufferFactory.getInstance(ByteOrder.LITTLE_ENDIAN)); + this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { case exception => this.connectionPromise.failure(exception) } @@ -117,23 +124,41 @@ class MySQLConnectionHandler( } case ServerMessage.ColumnDefinition => { - log.debug("Received column definition - {}", m) - this.currentColumns += m.asInstanceOf[ColumnDefinitionMessage] + val message = m.asInstanceOf[ColumnDefinitionMessage] + + if ( currentPreparedStatementHolder != null && this.currentPreparedStatementHolder.needsAny ) { + this.currentPreparedStatementHolder.add(message) + } + + this.currentColumns += message } case ServerMessage.ColumnDefinitionFinished => { - log.debug("Column processing finished, waiting for rows now -> {}", m) - this.currentQuery = new MutableResultSet[ColumnData]( this.currentColumns.map( c => new ColumnData(c.name, c.columnType) ), configuration.charset, columnDecoderRegistry ) + if ( this.currentPreparedStatementHolder != null ) { + this.parsedStatements.put( this.currentPreparedStatementHolder.statement, this.currentPreparedStatementHolder ) + this.executePreparedStatement( this.currentPreparedStatementHolder.statementId, this.currentPreparedStatement.values ) + this.currentPreparedStatementHolder = null + this.currentPreparedStatement = null + } + + } + case ServerMessage.PreparedStatementPrepareResponse => { + this.onPreparedStatementPrepareResponse(m.asInstanceOf[PreparedStatementPrepareResponse]) } case ServerMessage.Row => { - log.debug("Received row - {}", m) this.currentQuery.addRawRow(m.asInstanceOf[ResultSetRowMessage]) } + case ServerMessage.BinaryRow => { + val message = m.asInstanceOf[BinaryRowMessage] + this.currentQuery.addRow( this.binaryRowDecoder.decode(message.buffer, this.currentColumns )) + } + case ServerMessage.ParamProcessingFinished => { + } } } } @@ -161,18 +186,35 @@ class MySQLConnectionHandler( def afterRemove(ctx: ChannelHandlerContext) {} - def write(message: ClientMessage): ChannelFuture = { - if (message.kind == ClientMessage.Query) { - this.decoder.queryProcessStarted() + def write( message : QueryMessage ) : ChannelFuture = { + this.decoder.queryProcessStarted() + this.currentContext.getChannel.write(message) + } + + def write( message : PreparedStatementMessage ) { + this.parsedStatements.get(message.statement) match { + case Some( item ) => { + this.currentColumns.clear() + this.executePreparedStatement(item.statementId, message.values) + } + case None => { + this.currentPreparedStatement = message + decoder.preparedStatementPrepareStarted() + this.currentContext.getChannel.write( new PreparedStatementPrepareMessage(message.statement) ) + } } + } + + def write( message : HandshakeResponseMessage ) : ChannelFuture = { this.currentContext.getChannel.write(message) } - def disconnect: ChannelFuture = { - val future = this.currentContext.getChannel.close() - future + def write( message : QuitMessage ) : ChannelFuture = { + this.currentContext.getChannel.write(message) } + def disconnect: ChannelFuture = this.currentContext.getChannel.close() + private def clearQueryState { this.currentColumns.clear() this.currentQuery = null @@ -186,4 +228,15 @@ class MySQLConnectionHandler( } } + private def executePreparedStatement( statementId : Array[Byte], values : Seq[Any] ) { + decoder.preparedStatementExecuteStarted() + this.currentColumns.clear() + this.currentContext.getChannel.write(new PreparedStatementExecuteMessage( statementId, values )) + } + + private def onPreparedStatementPrepareResponse( message : PreparedStatementPrepareResponse ) { + log.debug("Received prepare response from server {}", message) + this.currentPreparedStatementHolder = new PreparedStatementHolder( this.currentPreparedStatement.statement, message) + } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 50ac2ece..d1eebfd2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -18,14 +18,16 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} import com.github.mauricio.async.db.mysql.decoder._ -import com.github.mauricio.async.db.mysql.message.server.ServerMessage +import com.github.mauricio.async.db.mysql.message.server.{BinaryRowMessage, ColumnProcessingFinishedMessage, PreparedStatementPrepareResponse, ServerMessage} import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util.{PrintUtils, Log} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.frame.FrameDecoder +import com.github.mauricio.async.db.mysql.message.client.PreparedStatementPrepareMessage +import com.github.mauricio.async.db.mysql.MySQLHelper object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] @@ -33,25 +35,38 @@ object MySQLFrameDecoder { class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { + import MySQLFrameDecoder.log private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) private final val okDecoder = new OkDecoder(charset) private final val columnDecoder = new ColumnDefinitionDecoder(charset) private final val rowDecoder = new ResultSetRowDecoder(charset) + private final val preparedStatementPrepareDecoder = new PreparedStatementPrepareResponseDecoder() private var processingColumns = false + private var processingParams = false private var isInQuery = false - private var totalColumns: Long = 0 - private var processedColumns: Long = 0 + private var isPreparedStatementPrepare = false + private var isPreparedStatementExecute = false + private var isPreparedStatementExecuteRows = false + + private var totalParams = 0L + private var processedParams = 0L + private var totalColumns = 0L + private var processedColumns = 0L def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { if (buffer.readableBytes() > 4) { + //val requestDump = MySQLHelper.dumpAsHex(buffer, buffer.readableBytes()) + //log.debug(s"Server message\n${requestDump}") + //PrintUtils.printArray( "any message", buffer) + buffer.markReaderIndex() val size = read3BytesInt(buffer) - val sequence = buffer.readByte() + val sequence = buffer.readUnsignedByte() if (buffer.readableBytes() >= size) { @@ -59,8 +74,8 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val slice = buffer.readSlice(size) - //val requestDump = MySQLHelper.dumpAsHex(slice, slice.readableBytes()) - //log.debug(s"Server message is type ${"%02x".format(messageType)} ( $messageType - $size bytes)\n${requestDump}") + //val dump = MySQLHelper.dumpAsHex(slice, slice.readableBytes()) + //log.debug(s"Message type $messageType - message size - $size - sequence - $sequence\n$dump") // removing initial kind byte so that we can switch // on known messages but add it back if this is a query process @@ -73,21 +88,36 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { this.errorDecoder } case ServerMessage.EOF => { - if ( this.processingColumns ) { - this.processingColumns = false - ColumnProcessingFinishedDecoder + + if (this.processingParams && this.totalParams > 0) { + this.processingParams = false + ParamProcessingFinishedDecoder } else { - this.clear - EOFMessageDecoder + if (this.processingColumns) { + this.processingColumns = false + ColumnProcessingFinishedDecoder + } else { + this.clear + EOFMessageDecoder + } } + } case ServerMessage.Ok => { - this.clear - this.okDecoder + if (this.isPreparedStatementPrepare) { + this.preparedStatementPrepareDecoder + } else { + if ( this.isPreparedStatementExecuteRows ) { + null + } else { + this.clear + this.okDecoder + } + } } case _ => { - if ( this.isInQuery ) { + if (this.isInQuery) { null } else { throw new ParserNotAvailableException(messageType) @@ -96,12 +126,38 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { } } - if ( decoder == null ) { - slice.readerIndex( slice.readerIndex() - 1 ) - return decodeQueryResult(slice, buffer) + if (decoder == null) { + slice.readerIndex(slice.readerIndex() - 1) + val result = decodeQueryResult(slice) + + if (slice.readableBytes() != 0) { + throw new BufferNotFullyConsumedException(slice) + } + + return result } else { val result = decoder.decode(slice) + result match { + case m : PreparedStatementPrepareResponse => { + this.totalColumns = m.columnsCount + this.totalParams = m.paramsCount + } + case m : ColumnProcessingFinishedMessage if this.isPreparedStatementPrepare => { + this.clear + } + case m : ColumnProcessingFinishedMessage if this.isPreparedStatementExecute => { + this.isPreparedStatementExecuteRows = true + } + case _ => + } + + if (result.isInstanceOf[PreparedStatementPrepareResponse]) { + val message = result.asInstanceOf[PreparedStatementPrepareResponse] + this.totalColumns = message.columnsCount + this.totalParams = message.paramsCount + } + if (slice.readableBytes() != 0) { throw new BufferNotFullyConsumedException(slice) } @@ -118,30 +174,59 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { return null } + def preparedStatementPrepareStarted() { + this.processingParams = true + this.processingColumns = true + this.isPreparedStatementPrepare = true + this.queryProcessStarted() + } + + def preparedStatementExecuteStarted() { + this.queryProcessStarted() + this.isPreparedStatementExecute = true + this.processingParams = false + } + def queryProcessStarted() { this.isInQuery = true this.processingColumns = true } - private def decodeQueryResult( slice : ChannelBuffer, buffer : ChannelBuffer ) : AnyRef = { - if ( this.totalColumns == 0 ) { + private def decodeQueryResult(slice: ChannelBuffer): AnyRef = { + if (this.totalColumns == 0) { this.totalColumns = slice.readBinaryLength return null } else { - if ( this.totalColumns == this.processedColumns ) { - this.rowDecoder.decode(slice) - } else { - this.processedColumns += 1 + + if (this.totalParams != this.processedParams) { + this.processedParams += 1 this.columnDecoder.decode(slice) + } else { + if (this.totalColumns == this.processedColumns) { + if ( this.isPreparedStatementExecute ) { + new BinaryRowMessage(slice.readSlice(slice.readableBytes())) + } else { + this.rowDecoder.decode(slice) + } + } else { + this.processedColumns += 1 + this.columnDecoder.decode(slice) + } } + } } private def clear { + this.isPreparedStatementPrepare = false + this.isPreparedStatementExecute = false + this.isPreparedStatementExecuteRows = false this.isInQuery = false this.processingColumns = false this.totalColumns = 0 this.processedColumns = 0 + this.totalParams = 0 + this.processedParams = 0 } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 127535fb..b867cf0b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException -import com.github.mauricio.async.db.mysql.encoder.{QueryMessageEncoder, QuitMessageEncoder, HandshakeResponseEncoder} +import com.github.mauricio.async.db.mysql.encoder._ import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.{ChannelUtils, Log} @@ -33,8 +33,11 @@ object MySQLOneToOneEncoder { class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends OneToOneEncoder { - private val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) + private final val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private final val queryEncoder = new QueryMessageEncoder(charset) + private final val prepareEncoder = new PreparedStatementPrepareEncoder(charset) + private final val executeEncoder = new PreparedStatementExecuteEncoder() + private var sequence = 1 def encode(ctx: ChannelHandlerContext, channel: Channel, msg: Any): ChannelBuffer = { @@ -51,6 +54,14 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten sequence = 0 this.queryEncoder } + case ClientMessage.PreparedStatementExecute => { + sequence = 0 + this.executeEncoder + } + case ClientMessage.PreparedStatementPrepare => { + sequence = 0 + this.prepareEncoder + } case _ => throw new EncoderNotAvailableException(message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/PreparedStatementHolder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/PreparedStatementHolder.scala new file mode 100644 index 00000000..4578a118 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/PreparedStatementHolder.scala @@ -0,0 +1,47 @@ +/* + * 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.mysql.codec + +import com.github.mauricio.async.db.mysql.message.server.{ColumnDefinitionMessage, PreparedStatementPrepareResponse} +import scala.collection.mutable.ArrayBuffer + +class PreparedStatementHolder( val statement : String, val message : PreparedStatementPrepareResponse ) { + + val columns = new ArrayBuffer[ColumnDefinitionMessage] + val parameters = new ArrayBuffer[ColumnDefinitionMessage] + + def statementId : Array[Byte] = message.statementId + + def needsParameters : Boolean = message.paramsCount != this.parameters.length + + def needsColumns : Boolean = message.columnsCount != this.columns.length + + def needsAny : Boolean = this.needsParameters || this.needsColumns + + def add( column : ColumnDefinitionMessage ) { + if ( this.needsParameters ) { + this.parameters += column + } else { + if ( this.needsColumns ) { + this.columns += column + } + } + } + + override def toString: String = s"PreparedStatementHolder($statement)" + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala index 9fd04bce..e88826e5 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala @@ -41,7 +41,7 @@ class ColumnDefinitionDecoder(charset: Charset) extends MessageDecoder { val characterSet = buffer.readUnsignedShort() val columnLength = buffer.readUnsignedInt() - val columnType = buffer.readByte() + val columnType = buffer.readUnsignedByte() val flags = buffer.readShort() val decimals = buffer.readByte() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala new file mode 100644 index 00000000..32a08e9e --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.{ParamProcessingFinishedMessage, ServerMessage} + +object ParamProcessingFinishedDecoder extends MessageDecoder { + + def decode(buffer: ChannelBuffer): ServerMessage = { + new ParamProcessingFinishedMessage(EOFMessageDecoder.decode(buffer)) + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala new file mode 100644 index 00000000..73db4f52 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala @@ -0,0 +1,50 @@ +/* + * 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.mysql.decoder + +import com.github.mauricio.async.db.mysql.message.server.{PreparedStatementPrepareResponse, ServerMessage} +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.MySQLHelper +import com.github.mauricio.async.db.util.Log + +class PreparedStatementPrepareResponseDecoder extends MessageDecoder { + + final val log = Log.get[PreparedStatementPrepareResponseDecoder] + + def decode(buffer: ChannelBuffer): ServerMessage = { + + //val dump = MySQLHelper.dumpAsHex(buffer, buffer.readableBytes()) + //log.debug("prepared statement response dump is \n{}", dump) + + val statementId = Array[Byte]( buffer.readByte(), buffer.readByte(), buffer.readByte(), buffer.readByte() ) + val columnsCount = buffer.readUnsignedShort() + val paramsCount = buffer.readUnsignedShort() + + // filler + buffer.readByte() + + val warningCount = buffer.readShort() + + new PreparedStatementPrepareResponse( + statementId = statementId, + warningCount = warningCount, + columnsCount = columnsCount, + paramsCount = paramsCount + ) + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala new file mode 100644 index 00000000..c2313946 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala @@ -0,0 +1,37 @@ +/* + * 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.mysql.encoder + +import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementExecuteMessage, ClientMessage} +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelUtils + +class PreparedStatementExecuteEncoder extends MessageEncoder { + + def encode(message: ClientMessage): ChannelBuffer = { + val m = message.asInstanceOf[PreparedStatementExecuteMessage] + + val buffer = ChannelUtils.packetBuffer() + buffer.writeByte( m.kind ) + buffer.writeBytes(m.statementId) + buffer.writeByte(0x00) // no cursor + buffer.writeInt(1) + + buffer + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala new file mode 100644 index 00000000..bd9def71 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.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.mysql.encoder + +import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementPrepareMessage, ClientMessage} +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelUtils +import java.nio.charset.Charset + +class PreparedStatementPrepareEncoder( charset : Charset ) extends MessageEncoder { + + def encode(message: ClientMessage): ChannelBuffer = { + val m = message.asInstanceOf[PreparedStatementPrepareMessage] + val statement = m.statement.getBytes(charset) + val buffer = ChannelUtils.packetBuffer( 4 + 1 + statement.size) + buffer.writeByte( m.kind ) + buffer.writeBytes( statement ) + + buffer + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala index 8272238f..e2d1d3c4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala @@ -23,6 +23,9 @@ object ClientMessage { final val ClientProtocolVersion = 0x09 final val Quit = 0x01 final val Query = 0x03 + final val PreparedStatementPrepare = 0x16 + final val PreparedStatementExecute = 0x17 + final val PreparedStatement = 0x18 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala new file mode 100644 index 00000000..48097659 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.client + +case class PreparedStatementExecuteMessage ( statementId : Array[Byte], values : Seq[Any] ) + extends ClientMessage( ClientMessage.PreparedStatementExecute ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala new file mode 100644 index 00000000..780ce8e6 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.client + +case class PreparedStatementMessage ( statement : String, values : Seq[Any] ) + extends ClientMessage( ClientMessage.PreparedStatement ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementPrepareMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementPrepareMessage.scala new file mode 100644 index 00000000..385eb011 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementPrepareMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.client + +case class PreparedStatementPrepareMessage( statement : String ) + extends ClientMessage( ClientMessage.PreparedStatementPrepare ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala index 7f796c3a..a73aeadc 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/QuitMessage.scala @@ -1,3 +1,7 @@ package com.github.mauricio.async.db.mysql.message.client -object QuitMessage extends ClientMessage( ClientMessage.Quit ) \ No newline at end of file +object QuitMessage { + val Instance = new QuitMessage(); +} + +class QuitMessage extends ClientMessage( ClientMessage.Quit ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala new file mode 100644 index 00000000..ff8a6836 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala @@ -0,0 +1,21 @@ +/* + * 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.mysql.message.server + +import org.jboss.netty.buffer.ChannelBuffer + +case class BinaryRowMessage ( buffer : ChannelBuffer ) extends ServerMessage( ServerMessage.BinaryRow ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala index e95efaf4..64d5f2bd 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -25,7 +25,7 @@ case class ColumnDefinitionMessage( originalName: String, characterSet: Int, columnLength: Long, - columnType: Byte, + columnType: Int, flags: Short, decimals: Byte ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamProcessingFinishedMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamProcessingFinishedMessage.scala new file mode 100644 index 00000000..145c5422 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamProcessingFinishedMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.server + +case class ParamProcessingFinishedMessage( eofMessage : EOFMessage ) + extends ServerMessage( ServerMessage.ParamProcessingFinished ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/PreparedStatementPrepareResponse.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/PreparedStatementPrepareResponse.scala new file mode 100644 index 00000000..fd3ff480 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/PreparedStatementPrepareResponse.scala @@ -0,0 +1,24 @@ +/* + * 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.mysql.message.server + +case class PreparedStatementPrepareResponse ( + statementId : Array[Byte], + warningCount : Short, + paramsCount : Int, + columnsCount : Int ) + extends ServerMessage( ServerMessage.PreparedStatementPrepareResponse ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala index 6b35e51b..8543b455 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala @@ -30,7 +30,10 @@ object ServerMessage { // but we use them to simplify the switch statements final val ColumnDefinition = 100 final val ColumnDefinitionFinished = 101 - final val Row = 102 + final val ParamProcessingFinished = 102 + final val Row = 103 + final val BinaryRow = 104 + final val PreparedStatementPrepareResponse = 105 } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index ecd36570..6b91467a 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -46,4 +46,8 @@ trait ConnectionHelper { await( connection.sendQuery(query) ) } + def executePreparedStatement( connection : MySQLConnection, query : String, values : Any * ) : QueryResult = { + await( connection.sendPreparedStatement(query, values) ) + } + } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala new file mode 100644 index 00000000..d73b0edc --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -0,0 +1,46 @@ +/* + * 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.mysql + +import org.specs2.mutable.Specification + +class PreparedStatementsSpec extends Specification with ConnectionHelper { + + "connection" should { + + "be able to execute prepared statements" in { + + withConnection { + connection => + val result = executePreparedStatement(connection, "select 1 as id , 'joe' as name").rows.get + + result(0)("name") === "joe" + result(0)("id") === 1 + result.length === 1 + + val otherResult = executePreparedStatement(connection, "select 1 as id , 'joe' as name").rows.get + + otherResult(0)("name") === "joe" + otherResult(0)("id") === 1 + otherResult.length === 1 + } + + } + + } + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala new file mode 100644 index 00000000..40f13c10 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala @@ -0,0 +1,66 @@ +/* + * 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.mysql.binary + +import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage +import org.jboss.netty.buffer.ChannelBuffers +import org.jboss.netty.util.CharsetUtil +import org.specs2.mutable.Specification +import java.nio.ByteOrder + +class BinaryRowDecoderSpec extends Specification { + + val idAndName = Array[Byte](0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) + val idAndNameColumns = Array( + createColumn("id", ColumnTypes.FIELD_TYPE_LONGLONG), + createColumn("name", ColumnTypes.FIELD_TYPE_VAR_STRING) ) + val decoder = new BinaryRowDecoder(CharsetUtil.UTF_8) + + "binary row decoder" should { + + "decoder a long and a string from the byte array" in { + + val buffer = ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, idAndName) + val result = decoder.decode(buffer, idAndNameColumns) + + result(0) === 1L + result(1) === "joe" + + } + + } + + def createColumn( name : String, columnType : Int ) : ColumnDefinitionMessage = { + + new ColumnDefinitionMessage( + "root", + "root", + "users", + "users", + name, + name, + -1, + 0, + columnType, + 0, + 0 + ) + + } + +} From 1ff6405e377c783f4439186c22b14cf71cbd1bcf Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 May 2013 01:37:39 -0300 Subject: [PATCH 074/357] Correctly detect null values when processing prepared statement result sets --- .../mauricio/async/db/util/BitMap.scala | 39 +++++++++++-------- .../mauricio/async/db/util/BitMapSpec.scala | 17 ++++++++ .../db/mysql/binary/BinaryRowDecoder.scala | 19 ++++++--- .../db/mysql/binary/decoder/NullDecoder.scala | 23 +++++++++++ .../db/mysql/PreparedStatementsSpec.scala | 15 +++++++ .../mysql/binary/BinaryRowDecoderSpec.scala | 15 ++++++- 6 files changed, 104 insertions(+), 24 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala index ada45f00..8884b85d 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.util object BitMap { - final val Bytes = Array( 128, 64, 32, 16, 8, 4, 2, 1 ) + final val Bytes = Array(128, 64, 32, 16, 8, 4, 2, 1) - def apply( bytes : Byte * ) : BitMap = new BitMap(bytes.toArray) + def apply(bytes: Byte*): BitMap = new BitMap(bytes.toArray) } @@ -30,7 +30,7 @@ object BitMap { * @param bytes */ -class BitMap( bytes : Array[Byte] ) extends IndexedSeq[(Int,Boolean)] { +class BitMap(bytes: Array[Byte]) extends IndexedSeq[(Int, Boolean)] { val length = bytes.length * 8 @@ -42,14 +42,14 @@ class BitMap( bytes : Array[Byte] ) extends IndexedSeq[(Int,Boolean)] { * @return */ - def isSet( index : Int ) : Boolean = { + def isSet(index: Int): Boolean = { val quotient = index / 8 val remainder = index % 8 (bytes(quotient) & BitMap.Bytes(remainder)) != 0 } - def set( index : Int ) { + def set(index: Int) { val quotient = index / 8 val remainder = index % 8 @@ -58,33 +58,38 @@ class BitMap( bytes : Array[Byte] ) extends IndexedSeq[(Int,Boolean)] { override def foreach[U](f: ((Int, Boolean)) => U) { var currentIndex = 0 - for ( byte <- bytes ) { + for (byte <- bytes) { var x = 0 - while ( x < BitMap.Bytes.length ) { - f( currentIndex, (byte & BitMap.Bytes(x)) != 0 ) + while (x < BitMap.Bytes.length) { + f(currentIndex, (byte & BitMap.Bytes(x)) != 0) x += 1 currentIndex += 1 } } } - def foreachWithLimit[U]( limit : Int, f: ((Int, Boolean)) => U ) { - var currentIndex = 0 - for ( byte <- bytes ) { - var x = 0 - while ( x < BitMap.Bytes.length ) { - f( currentIndex, (byte & BitMap.Bytes(x)) != 0 ) + def foreachWithLimit[U](startIndex: Int, length: Int, f: ((Int, Boolean)) => U) { + var currentIndex = startIndex + var start = startIndex / 8 + var x = startIndex % 8 + val limit = length + startIndex + while (start < bytes.length) { + val byte = this.bytes(start) + while (x < BitMap.Bytes.length) { + f(currentIndex, (byte & BitMap.Bytes(x)) != 0) x += 1 currentIndex += 1 - if ( currentIndex >= limit ) { + if (currentIndex >= limit) { return } } + x = 0 + start += 1 } } - def apply(idx: Int): (Int, Boolean) = (idx,this.isSet(idx)) + def apply(idx: Int): (Int, Boolean) = (idx, this.isSet(idx)) - override def toString: String = this.map( entry => if ( entry._2 ) '1' else '0' ).mkString("") + override def toString: String = this.map(entry => if (entry._2) '1' else '0').mkString("") } diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala index 11f3304d..4dce05e1 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.util import org.specs2.mutable.Specification +import scala.collection.mutable.ArrayBuffer class BitMapSpec extends Specification { @@ -39,6 +40,22 @@ class BitMapSpec extends Specification { } + "correctly foreach over a piece of the bitmap" in { + + val bitMap = BitMap(0, 16) + + val buffer = new ArrayBuffer[Character]() + + bitMap.foreachWithLimit(9, 3, { + case ( index, isNull ) => { + buffer += (if( isNull ) '1' else '0') + } + }) + + buffer.mkString("") === "001" + + } + } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index 8ea0343d..33fd4af0 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql.binary import com.github.mauricio.async.db.exceptions.BufferNotFullyConsumedException -import com.github.mauricio.async.db.mysql.binary.decoder.{LongDecoder, StringDecoder, BinaryDecoder} +import com.github.mauricio.async.db.mysql.binary.decoder._ import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage import com.github.mauricio.async.db.util.{Log, PrintUtils, BitMap} @@ -25,20 +25,25 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import scala.collection.mutable.ArrayBuffer import com.github.mauricio.async.db.mysql.MySQLHelper +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage object BinaryRowDecoder { final val log = Log.get[BinaryRowDecoder] + final val BitMapOffset = 9 } class BinaryRowDecoder( charset : Charset ) { - import BinaryRowDecoder.log + import BinaryRowDecoder._ private final val stringDecoder = new StringDecoder(charset) def decode( buffer : ChannelBuffer, columns : Seq[ColumnDefinitionMessage] ) : IndexedSeq[Any] = { - log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) + //log.debug("columns are {}", columns) + + //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) + PrintUtils.printArray("bitmap", buffer) val totalBytes = (columns.size + 7 + 2) val quotient = totalBytes / 8 @@ -46,19 +51,21 @@ class BinaryRowDecoder( charset : Charset ) { val bitMapSource = new Array[Byte]( if ( remainder == 0 ) quotient else quotient + 1 ) - log.debug("Bit map size is {} - columns count is {}", bitMapSource.length, columns.length) + //log.debug("Bit map size is {} - columns count is {}", bitMapSource.length, columns.length) buffer.readBytes(bitMapSource) val bitMap = new BitMap(bitMapSource) + //log.debug("bitmap is {}", bitMap) + val row = new ArrayBuffer[Any](columns.size) - bitMap.foreachWithLimit(columns.length, { + bitMap.foreachWithLimit( BitMapOffset, columns.length, { case ( index, isNull ) => { if ( isNull ) { row += null } else { - row += decoderFor(columns(index).columnType).decode(buffer) + row += decoderFor(columns(index - BitMapOffset).columnType).decode(buffer) } } }) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala new file mode 100644 index 00000000..403f0be1 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +object NullDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = null +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index d73b0edc..d6948e58 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -41,6 +41,21 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } + "be able to detect a null value in a prepared statement" in { + + withConnection { + connection => + val result = executePreparedStatement(connection, "select 1 as id , 'joe' as name, NULL as null_value").rows.get + + result(0)("name") === "joe" + result(0)("id") === 1 + result(0)("null_value") must beNull + result.length === 1 + + } + + } + } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala index 40f13c10..37d40563 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala @@ -25,11 +25,15 @@ import java.nio.ByteOrder class BinaryRowDecoderSpec extends Specification { + val decoder = new BinaryRowDecoder(CharsetUtil.UTF_8) + val idAndName = Array[Byte](0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) val idAndNameColumns = Array( createColumn("id", ColumnTypes.FIELD_TYPE_LONGLONG), createColumn("name", ColumnTypes.FIELD_TYPE_VAR_STRING) ) - val decoder = new BinaryRowDecoder(CharsetUtil.UTF_8) + + val idNameAndNull = Array[Byte](0, 16, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) + val idNameAndNullColumns = idAndNameColumns ++ List( createColumn("null_value", ColumnTypes.FIELD_TYPE_NULL) ) "binary row decoder" should { @@ -43,6 +47,15 @@ class BinaryRowDecoderSpec extends Specification { } + "decode a row with an long, a string and a null" in { + val buffer = ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, idNameAndNull) + val result = decoder.decode(buffer, idNameAndNullColumns) + + result(0) === 1L + result(1) === "joe" + result(2) must beNull + } + } def createColumn( name : String, columnType : Int ) : ColumnDefinitionMessage = { From 2f5e0d394207aa2417f26cd94f51713acc0488e8 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Fri, 10 May 2013 23:08:38 -0300 Subject: [PATCH 075/357] fix #16 - used a hex string hash code as the prepared statement id - used unnamed portals --- .../db/postgresql/PostgreSQLConnection.scala | 19 +++++++++++------- .../ExecutePreparedStatementEncoder.scala | 15 +++++++------- .../PreparedStatementOpeningEncoder.scala | 20 ++++++++++--------- .../PreparedStatementExecuteMessage.scala | 4 ++-- .../frontend/PreparedStatementMessage.scala | 1 + .../PreparedStatementOpeningMessage.scala | 4 ++-- .../DatabaseConnectionHandlerSpec.scala | 13 +++++++++++- 7 files changed, 48 insertions(+), 28 deletions(-) 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 da3a6661..cf4ed05f 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 @@ -123,15 +123,20 @@ class PostgreSQLConnection this.setQueryPromise(promise) this.currentPreparedStatement = Some(realQuery) - if (!this.isParsed(realQuery)) { - write(new PreparedStatementOpeningMessage(realQuery, values, this.encoderRegistry)) + val queryId = this.queryId(realQuery) + + if (!this.isParsed(queryId)) { + write(new PreparedStatementOpeningMessage(queryId, realQuery, values, this.encoderRegistry)) } else { - this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(realQuery), configuration.charset, this.decoderRegistry)) - write(new PreparedStatementExecuteMessage(realQuery, values, this.encoderRegistry)) + this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(queryId), configuration.charset, this.decoderRegistry)) + write(new PreparedStatementExecuteMessage(queryId, realQuery, values, this.encoderRegistry)) } promise.future } + + private def queryId(query: String) = + Integer.toHexString(query.hashCode) override def onError( exception : Throwable ) { this.setErrorOnFutures(exception) @@ -195,12 +200,12 @@ class PostgreSQLConnection private def setColumnDatas( columnDatas : Array[PostgreSQLColumnData] ) { if (this.currentPreparedStatement.isDefined) { - this.parsedStatements.put(this.currentPreparedStatement.get, columnDatas) + this.parsedStatements.put(queryId(this.currentPreparedStatement.get), columnDatas) } } - private def isParsed(query: String): Boolean = { - this.parsedStatements.containsKey(query) + private def isParsed(queryId: String): Boolean = { + this.parsedStatements.containsKey(queryId) } override def onAuthenticationResponse(message: AuthenticationMessage) { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 2da65cbb..e9bd1138 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -29,16 +29,17 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR val m = message.asInstanceOf[PreparedStatementExecuteMessage] + val emptyStringBytes = "".getBytes(charset) val queryBytes = m.query.getBytes(charset) - + val queryIdBytes = m.queryId.getBytes(charset) val bindBuffer = ChannelBuffers.dynamicBuffer(1024) bindBuffer.writeByte(ServerMessage.Bind) bindBuffer.writeInt(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes("".getBytes(charset)) bindBuffer.writeByte(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes(queryIdBytes) bindBuffer.writeByte(0) bindBuffer.writeShort(0) @@ -59,23 +60,23 @@ class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderR ChannelUtils.writeLength(bindBuffer) - val executeLength = 1 + 4 + queryBytes.length + 1 + 4 + val executeLength = 1 + 4 + emptyStringBytes.length + 1 + 4 val executeBuffer = ChannelBuffers.buffer(executeLength) executeBuffer.writeByte(ServerMessage.Execute) executeBuffer.writeInt(executeLength - 1) - executeBuffer.writeBytes(queryBytes) + executeBuffer.writeBytes(emptyStringBytes) executeBuffer.writeByte(0) executeBuffer.writeInt(0) - val closeLength = 1 + 4 + 1 + queryBytes.length + 1 + val closeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') - closeBuffer.writeBytes(queryBytes) + closeBuffer.writeBytes(emptyStringBytes) closeBuffer.writeByte(0) val syncBuffer = ChannelBuffers.buffer(5) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index a4a37806..41a780a3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -29,7 +29,9 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val m = message.asInstanceOf[PreparedStatementOpeningMessage] + val emptyStringBytes = "".getBytes(charset) val queryBytes = m.query.getBytes(charset) + val queryIdBytes = m.queryId.getBytes(charset) val columnCount = m.valueTypes.size val parseBuffer = ChannelBuffers.dynamicBuffer(1024) @@ -37,7 +39,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR parseBuffer.writeByte(ServerMessage.Parse) parseBuffer.writeInt(0) - parseBuffer.writeBytes(queryBytes) + parseBuffer.writeBytes(queryIdBytes) parseBuffer.writeByte(0) parseBuffer.writeBytes(queryBytes) parseBuffer.writeByte(0) @@ -55,9 +57,9 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR bindBuffer.writeByte(ServerMessage.Bind) bindBuffer.writeInt(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes(emptyStringBytes) bindBuffer.writeByte(0) - bindBuffer.writeBytes(queryBytes) + bindBuffer.writeBytes(queryIdBytes) bindBuffer.writeByte(0) bindBuffer.writeShort(0) @@ -78,33 +80,33 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR ChannelUtils.writeLength(bindBuffer) - val describeLength = 1 + 4 + 1 + queryBytes.length + 1 + val describeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 val describeBuffer = ChannelBuffers.buffer(describeLength) describeBuffer.writeByte(ServerMessage.Describe) describeBuffer.writeInt(describeLength - 1) describeBuffer.writeByte('P') - describeBuffer.writeBytes(queryBytes) + describeBuffer.writeBytes(emptyStringBytes) describeBuffer.writeByte(0) - val executeLength = 1 + 4 + queryBytes.length + 1 + 4 + val executeLength = 1 + 4 + emptyStringBytes.length + 1 + 4 val executeBuffer = ChannelBuffers.buffer(executeLength) executeBuffer.writeByte(ServerMessage.Execute) executeBuffer.writeInt(executeLength - 1) - executeBuffer.writeBytes(queryBytes) + executeBuffer.writeBytes(emptyStringBytes) executeBuffer.writeByte(0) executeBuffer.writeInt(0) - val closeLength = 1 + 4 + 1 + queryBytes.length + 1 + val closeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 val closeBuffer = ChannelBuffers.buffer(closeLength) closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') - closeBuffer.writeBytes(queryBytes) + closeBuffer.writeBytes(emptyStringBytes) closeBuffer.writeByte(0) val syncBuffer = ChannelBuffers.buffer(5) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index 4779573b..7fc1d93c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class PreparedStatementExecuteMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(ServerMessage.Execute, query, values, encoderRegistry) +class PreparedStatementExecuteMessage(queryId: String, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(queryId, ServerMessage.Execute, query, values, encoderRegistry) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index 240ccf70..a76b8208 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementMessage( + val queryId: String, kind: Byte, val query: String, val values: Seq[Any], diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index d77bb99f..bdce254d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class PreparedStatementOpeningMessage(query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file +class PreparedStatementOpeningMessage(queryId: String, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(queryId: String, ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 4362dd93..2a7e1dae 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -361,7 +361,18 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } } - + + "support prepared statement with more than 64 characters" in { + withHandler { + handler => + executeDdl( handler, this.messagesCreate ) + val stmt = "SELECT id, content, moment FROM messages WHERE id is not null AND content is not null " + executePreparedStatement(handler, stmt + "AND moment is not null") + executePreparedStatement(handler, stmt + "AND moment is null") + ok + } + } + } } From a893173a1619b9ea35bfc2565fefa890593b8ad3 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Sat, 11 May 2013 01:46:34 -0300 Subject: [PATCH 076/357] fix bug with queries that returns multiple columns with the same name --- .../mauricio/async/db/general/ArrayRowData.scala | 4 ++-- .../async/db/general/MutableResultSet.scala | 6 +++--- .../DatabaseConnectionHandlerSpec.scala | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala index c232a12a..934b303d 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.general import com.github.mauricio.async.db.RowData import scala.collection.mutable -class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] ) +class ArrayRowData( columnCount : Int, row : Int, val mapping : List[(String, Int)] ) extends RowData { @@ -41,7 +41,7 @@ class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] * @param columnName * @return */ - def apply(columnName: String): Any = columns( mapping(columnName) ) + def apply(columnName: String): Any = columns( mapping.find(_._1 == columnName).get._2 ) /** * diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index e6490843..a1485ef6 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -33,10 +33,10 @@ class MutableResultSet[T <: ColumnData]( decoder : ColumnDecoderRegistry) extends ResultSet { private val rows = new ArrayBuffer[RowData]() - private val columnMapping: Map[String, Int] = this.columnTypes.indices.map( + private val columnMapping: List[(String, Int)] = this.columnTypes.indices.map( index => - ( this.columnTypes(index).name, index ) ) - .toMap + ( this.columnTypes(index).name, index ) ).toList + override def columnNames : IndexedSeq[String] = this.columnTypes.map( data => data.name ) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala index 2a7e1dae..0998df54 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala @@ -158,6 +158,21 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } } + + "select rows that has duplicate column names" in { + + withHandler { + handler => + val result = executeQuery(handler, "SELECT 1 COL, 2 COL") + + val row = result.rows.get(0) + + row(0) === 1 + row(1) === 2 + + } + + } "execute a prepared statement" in { From 6a135e335c58d022e8d11ba8f706c499c5b8a8ea Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 May 2013 10:12:08 -0300 Subject: [PATCH 077/357] Avoid using find to get a column number, this is a hot path, it has to be fast --- .../mauricio/async/db/general/ArrayRowData.scala | 4 ++-- .../mauricio/async/db/general/MutableResultSet.scala | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala index 934b303d..c232a12a 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.general import com.github.mauricio.async.db.RowData import scala.collection.mutable -class ArrayRowData( columnCount : Int, row : Int, val mapping : List[(String, Int)] ) +class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] ) extends RowData { @@ -41,7 +41,7 @@ class ArrayRowData( columnCount : Int, row : Int, val mapping : List[(String, In * @param columnName * @return */ - def apply(columnName: String): Any = columns( mapping.find(_._1 == columnName).get._2 ) + def apply(columnName: String): Any = columns( mapping(columnName) ) /** * diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index a1485ef6..37a15014 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -33,9 +33,9 @@ class MutableResultSet[T <: ColumnData]( decoder : ColumnDecoderRegistry) extends ResultSet { private val rows = new ArrayBuffer[RowData]() - private val columnMapping: List[(String, Int)] = this.columnTypes.indices.map( + private val columnMapping: Map[String, Int] = this.columnTypes.indices.map( index => - ( this.columnTypes(index).name, index ) ).toList + ( this.columnTypes(index).name, index ) ).toMap override def columnNames : IndexedSeq[String] = this.columnTypes.map( data => data.name ) @@ -45,7 +45,7 @@ class MutableResultSet[T <: ColumnData]( override def apply(idx: Int): RowData = this.rows(idx) def addRawRow(row: Array[ChannelBuffer]) { - val realRow = new ArrayRowData(columnMapping.size, this.rows.size, this.columnMapping) + val realRow = new ArrayRowData(columnTypes.size, this.rows.size, this.columnMapping) realRow.indices.foreach { index => @@ -60,7 +60,7 @@ class MutableResultSet[T <: ColumnData]( } def addRawRow( row : Seq[String] ) { - val realRow = new ArrayRowData(columnMapping.size, this.rows.size, this.columnMapping) + val realRow = new ArrayRowData(columnTypes.size, this.rows.size, this.columnMapping) realRow.indices.foreach { index => @@ -75,7 +75,7 @@ class MutableResultSet[T <: ColumnData]( } def addRow( row : Seq[Any] ) { - val realRow = new ArrayRowData( columnMapping.size, this.rows.size, this.columnMapping ) + val realRow = new ArrayRowData( columnTypes.size, this.rows.size, this.columnMapping ) var x = 0 while ( x < row.size ) { realRow(x) = row(x) From 539d50666355fe2e370be233c85f6c92377d9231 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 May 2013 15:43:49 -0300 Subject: [PATCH 078/357] Updated some specs, moved prepared statement specs to a separate test, renamed DatabaseConnectionHandlerSpec to PostgreSQLConnectionSpec --- .../db/postgresql/PostgreSQLConnection.scala | 64 ++++++------ .../postgresql/PreparedStatementHolder.scala | 25 +++++ .../codec/PostgreSQLConnectionDelegate.scala | 3 - .../codec/PostgreSQLConnectionHandler.scala | 1 - .../encoders/CredentialEncoder.scala | 3 - .../ExecutePreparedStatementEncoder.scala | 67 ++----------- .../PreparedStatementEncoderHelper.scala | 97 +++++++++++++++++++ .../PreparedStatementOpeningEncoder.scala | 79 ++------------- .../PreparedStatementExecuteMessage.scala | 4 +- .../frontend/PreparedStatementMessage.scala | 4 +- .../PreparedStatementOpeningMessage.scala | 4 +- .../pool/ConnectionObjectFactory.scala | 3 +- ...c.scala => PostgreSQLConnectionSpec.scala} | 46 +-------- .../db/postgresql/PreparedStatementSpec.scala | 93 ++++++++++++++++++ .../SingleThreadedAsyncObjectPoolSpec.scala | 2 +- 15 files changed, 277 insertions(+), 218 deletions(-) create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala rename postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/{DatabaseConnectionHandlerSpec.scala => PostgreSQLConnectionSpec.scala} (84%) create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala 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 cf4ed05f..ee0a6c29 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 @@ -24,13 +24,18 @@ import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.{AtomicReference, AtomicLong} +import java.util.concurrent.atomic._ import messages.backend._ import messages.frontend._ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.collection.JavaConversions._ import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.Some +import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage +import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage +import com.github.mauricio.async.db.postgresql.messages.backend.RowDescriptionMessage +import com.github.mauricio.async.db.postgresql.messages.backend.ParameterStatusMessage object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] @@ -52,11 +57,12 @@ class PostgreSQLConnection private final val connectionHandler = new PostgreSQLConnectionHandler( configuration, encoderRegistry, decoderRegistry, this ) private final val currentCount = Counter.incrementAndGet() + private final val preparedStatementsCounter = new AtomicInteger() private final implicit val executionContext = ExecutionContext.fromExecutorService(configuration.workerPool) private var readyForQuery = false - private val parameterStatus = new ConcurrentHashMap[String, String]() - private val parsedStatements = new ConcurrentHashMap[String, Array[PostgreSQLColumnData]]() + private val parameterStatus = new scala.collection.mutable.HashMap[String, String]() + private val parsedStatements = new scala.collection.mutable.HashMap[String, PreparedStatementHolder]() private var authenticated = false private val connectionFuture = Promise[Connection]() @@ -121,22 +127,26 @@ class PostgreSQLConnection this.readyForQuery = false val promise = Promise[QueryResult]() this.setQueryPromise(promise) - this.currentPreparedStatement = Some(realQuery) + this.currentPreparedStatement = Some(query) - val queryId = this.queryId(realQuery) - - if (!this.isParsed(queryId)) { - write(new PreparedStatementOpeningMessage(queryId, realQuery, values, this.encoderRegistry)) - } else { - this.currentQuery = Some(new MutableResultSet(this.parsedStatements.get(queryId), configuration.charset, this.decoderRegistry)) - write(new PreparedStatementExecuteMessage(queryId, realQuery, values, this.encoderRegistry)) + this.isParsed(query) match { + case Some(holder) => { + this.currentQuery = Some(new MutableResultSet(holder.columnDatas, configuration.charset, this.decoderRegistry)) + write(new PreparedStatementExecuteMessage(holder.statementId, realQuery, values, this.encoderRegistry)) + } + case None => { + val statementId = this.preparedStatementsCounter.incrementAndGet() + this.parsedStatements.put( query, new PreparedStatementHolder( statementId ) ) + write(new PreparedStatementOpeningMessage(statementId, realQuery, values, this.encoderRegistry)) + } } promise.future } - - private def queryId(query: String) = - Integer.toHexString(query.hashCode) + + private def isParsed(query: String): Option[PreparedStatementHolder] = { + this.parsedStatements.get(query) + } override def onError( exception : Throwable ) { this.setErrorOnFutures(exception) @@ -147,7 +157,7 @@ class PostgreSQLConnection private def setErrorOnFutures(e: Throwable) { this.recentError = true - log.error("[%s] - Error on connection".format(currentCount), e) + log.error("Error on connection", e) if (!this.connectionFuture.isCompleted) { this.connectionFuture.failure(e) @@ -168,7 +178,7 @@ class PostgreSQLConnection } override def onError(m: ErrorMessage) { - log.error("[%s] - Error with message -> {}".format(currentCount), m) + log.error("Error with message -> {}", m) val error = new GenericDatabaseException(m) error.fillInStackTrace() @@ -189,10 +199,6 @@ class PostgreSQLConnection this.currentQuery.get.addRawRow(m.values) } - override def onParseComplete() { - setColumnDatas(Array.empty) - } - override def onRowDescription(m: RowDescriptionMessage) { this.currentQuery = Option(new MutableResultSet(m.columnDatas, configuration.charset, this.decoderRegistry)) this.setColumnDatas(m.columnDatas) @@ -200,19 +206,16 @@ class PostgreSQLConnection private def setColumnDatas( columnDatas : Array[PostgreSQLColumnData] ) { if (this.currentPreparedStatement.isDefined) { - this.parsedStatements.put(queryId(this.currentPreparedStatement.get), columnDatas) + val holder = this.parsedStatements(this.currentPreparedStatement.get) + holder.columnDatas = columnDatas } } - private def isParsed(queryId: String): Boolean = { - this.parsedStatements.containsKey(queryId) - } - override def onAuthenticationResponse(message: AuthenticationMessage) { message match { case m: AuthenticationOkMessage => { - log.debug("[{}] - Successfully logged in to database", this.currentCount) + log.debug("Successfully logged in to database") this.authenticated = true } case m: AuthenticationChallengeCleartextMessage => { @@ -243,7 +246,7 @@ class PostgreSQLConnection def validateIfItIsReadyForQuery(errorMessage: String) = if (this.queryPromise.isDefined) { - log.error(errorMessage, this.currentCount) + log.error(errorMessage) throw new ConnectionStillRunningQueryException( this.currentCount, this.readyForQuery @@ -251,8 +254,7 @@ class PostgreSQLConnection } private def validateQuery(query: String) { - this.validateIfItIsReadyForQuery( - errorMessage = "[{}] - Can't run query because there is one query pending already") + this.validateIfItIsReadyForQuery("Can't run query because there is one query pending already") if (query == null || query.isEmpty) { throw new QueryMustNotBeNullOrEmptyException(query) @@ -274,7 +276,7 @@ class PostgreSQLConnection if (promise.isDefined) { this.clearQueryPromise - log.error("[{}] - Setting error on future {}", this.currentCount, promise) + log.error("Setting error on future {}", promise) promise.get.failure(t) } } @@ -293,6 +295,6 @@ class PostgreSQLConnection } override def toString: String = { - "%s{counter=%s}".format(this.getClass.getSimpleName, this.currentCount) + s"${this.getClass.getSimpleName}{counter=${this.currentCount}}" } } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala new file mode 100644 index 00000000..454b7a85 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala @@ -0,0 +1,25 @@ +/* + * 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 + +import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumnData + +class PreparedStatementHolder( val statementId : Int ) { + + var columnDatas : Array[PostgreSQLColumnData] = Array.empty + +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala index 69936392..939646ee 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala @@ -16,8 +16,6 @@ package com.github.mauricio.async.db.postgresql.codec -import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage -import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage import com.github.mauricio.async.db.postgresql.messages.backend._ trait PostgreSQLConnectionDelegate { @@ -29,7 +27,6 @@ trait PostgreSQLConnectionDelegate { def onError( throwable : Throwable ) def onParameterStatus( message : ParameterStatusMessage ) def onReadyForQuery() - def onParseComplete() def onRowDescription(message : RowDescriptionMessage) } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index c51013e3..8602e552 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -159,7 +159,6 @@ class PostgreSQLConnectionHandler } case ServerMessage.ParseComplete => { log.debug("Parse complete received - {}", m) - connectionDelegate.onParseComplete() } case ServerMessage.ReadyForQuery => { connectionDelegate.onReadyForQuery() diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index 98819339..a6ee3a11 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -34,9 +34,6 @@ class CredentialEncoder(charset: Charset) extends Encoder { credentialMessage.password.getBytes(charset) } case AuthenticationResponseType.MD5 => { - - println("======> salt is Array[Byte](" + credentialMessage.salt.get.mkString(", ") + ")") - PasswordHelper.encode( credentialMessage.username, credentialMessage.password, diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index e9bd1138..48244807 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -23,67 +23,20 @@ import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class ExecutePreparedStatementEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { +class ExecutePreparedStatementEncoder( + charset: Charset, + encoder : ColumnEncoderRegistry) + extends Encoder + with PreparedStatementEncoderHelper +{ def encode(message: ClientMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] - val emptyStringBytes = "".getBytes(charset) - val queryBytes = m.query.getBytes(charset) - val queryIdBytes = m.queryId.getBytes(charset) - val bindBuffer = ChannelBuffers.dynamicBuffer(1024) - - bindBuffer.writeByte(ServerMessage.Bind) - bindBuffer.writeInt(0) - - bindBuffer.writeBytes("".getBytes(charset)) - bindBuffer.writeByte(0) - bindBuffer.writeBytes(queryIdBytes) - bindBuffer.writeByte(0) - - bindBuffer.writeShort(0) - - bindBuffer.writeShort(m.values.length) - - for (value <- m.values) { - if (value == null) { - bindBuffer.writeInt(-1) - } else { - val encoded = encoder.encode(value).getBytes(charset) - bindBuffer.writeInt(encoded.length) - bindBuffer.writeBytes(encoded) - } - } - - bindBuffer.writeShort(0) - - ChannelUtils.writeLength(bindBuffer) - - val executeLength = 1 + 4 + emptyStringBytes.length + 1 + 4 - val executeBuffer = ChannelBuffers.buffer(executeLength) - executeBuffer.writeByte(ServerMessage.Execute) - executeBuffer.writeInt(executeLength - 1) - - executeBuffer.writeBytes(emptyStringBytes) - executeBuffer.writeByte(0) - - executeBuffer.writeInt(0) - - val closeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 - val closeBuffer = ChannelBuffers.buffer(closeLength) - closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) - closeBuffer.writeInt(closeLength - 1) - closeBuffer.writeByte('P') - - closeBuffer.writeBytes(emptyStringBytes) - closeBuffer.writeByte(0) - - val syncBuffer = ChannelBuffers.buffer(5) - syncBuffer.writeByte(ServerMessage.Sync) - syncBuffer.writeInt(4) - - ChannelBuffers.wrappedBuffer(bindBuffer, executeBuffer, syncBuffer, closeBuffer) + val statementIdBytes = m.statementId.toString.getBytes(charset) + writeExecutePortal( statementIdBytes, m.values, encoder, charset ) } -} + +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala new file mode 100644 index 00000000..88acb93a --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -0,0 +1,97 @@ +/* + * 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.encoders + +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage +import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.column.ColumnEncoderRegistry +import java.nio.charset.Charset + +trait PreparedStatementEncoderHelper { + + def writeExecutePortal( + statementIdBytes: Array[Byte], + values: Seq[Any], + encoder: ColumnEncoderRegistry, + charset: Charset, + writeDescribe: Boolean = false + ): ChannelBuffer = { + + val bindBuffer = ChannelBuffers.dynamicBuffer(1024) + + bindBuffer.writeByte(ServerMessage.Bind) + bindBuffer.writeInt(0) + + bindBuffer.writeBytes(statementIdBytes) + bindBuffer.writeByte(0) + bindBuffer.writeBytes(statementIdBytes) + bindBuffer.writeByte(0) + + bindBuffer.writeShort(0) + + bindBuffer.writeShort(values.length) + + for (value <- values) { + if (value == null) { + bindBuffer.writeInt(-1) + } else { + val encoded = encoder.encode(value).getBytes(charset) + bindBuffer.writeInt(encoded.length) + bindBuffer.writeBytes(encoded) + } + } + + bindBuffer.writeShort(0) + + ChannelUtils.writeLength(bindBuffer) + + if ( writeDescribe ) { + val describeLength = 1 + 4 + 1 + statementIdBytes.length + 1 + val describeBuffer = bindBuffer + describeBuffer.writeByte(ServerMessage.Describe) + describeBuffer.writeInt(describeLength - 1) + describeBuffer.writeByte('P') + describeBuffer.writeBytes(statementIdBytes) + describeBuffer.writeByte(0) + } + + val executeLength = 1 + 4 + statementIdBytes.length + 1 + 4 + val executeBuffer = ChannelBuffers.buffer(executeLength) + executeBuffer.writeByte(ServerMessage.Execute) + executeBuffer.writeInt(executeLength - 1) + executeBuffer.writeBytes(statementIdBytes) + executeBuffer.writeByte(0) + executeBuffer.writeInt(0) + + val closeLength = 1 + 4 + 1 + statementIdBytes.length + 1 + val closeBuffer = ChannelBuffers.buffer(closeLength) + closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) + closeBuffer.writeInt(closeLength - 1) + closeBuffer.writeByte('P') + closeBuffer.writeBytes(statementIdBytes) + closeBuffer.writeByte(0) + + val syncBuffer = ChannelBuffers.buffer(5) + syncBuffer.writeByte(ServerMessage.Sync) + syncBuffer.writeInt(4) + + ChannelBuffers.wrappedBuffer(bindBuffer, executeBuffer, syncBuffer, closeBuffer) + + } + +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 41a780a3..8711b750 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -23,15 +23,16 @@ import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder { +class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) + extends Encoder + with PreparedStatementEncoderHelper +{ override def encode(message: ClientMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementOpeningMessage] - val emptyStringBytes = "".getBytes(charset) - val queryBytes = m.query.getBytes(charset) - val queryIdBytes = m.queryId.getBytes(charset) + val statementIdBytes = m.statementId.toString.getBytes(charset) val columnCount = m.valueTypes.size val parseBuffer = ChannelBuffers.dynamicBuffer(1024) @@ -39,9 +40,9 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR parseBuffer.writeByte(ServerMessage.Parse) parseBuffer.writeInt(0) - parseBuffer.writeBytes(queryIdBytes) + parseBuffer.writeBytes(statementIdBytes) parseBuffer.writeByte(0) - parseBuffer.writeBytes(queryBytes) + parseBuffer.writeBytes(m.query.getBytes(charset)) parseBuffer.writeByte(0) parseBuffer.writeShort(columnCount) @@ -52,69 +53,9 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR ChannelUtils.writeLength(parseBuffer) - val bindBuffer = ChannelBuffers.dynamicBuffer(1024) - - bindBuffer.writeByte(ServerMessage.Bind) - bindBuffer.writeInt(0) - - bindBuffer.writeBytes(emptyStringBytes) - bindBuffer.writeByte(0) - bindBuffer.writeBytes(queryIdBytes) - bindBuffer.writeByte(0) - - bindBuffer.writeShort(0) - - bindBuffer.writeShort(m.values.length) - - for (value <- m.values) { - if (value == null) { - bindBuffer.writeInt(-1) - } else { - val encoded = encoder.encode(value).getBytes(charset) - bindBuffer.writeInt(encoded.length) - bindBuffer.writeBytes(encoded) - } - } - - bindBuffer.writeShort(0) - - ChannelUtils.writeLength(bindBuffer) - - val describeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 - val describeBuffer = ChannelBuffers.buffer(describeLength) - describeBuffer.writeByte(ServerMessage.Describe) - describeBuffer.writeInt(describeLength - 1) - - describeBuffer.writeByte('P') - - describeBuffer.writeBytes(emptyStringBytes) - describeBuffer.writeByte(0) - - val executeLength = 1 + 4 + emptyStringBytes.length + 1 + 4 - val executeBuffer = ChannelBuffers.buffer(executeLength) - executeBuffer.writeByte(ServerMessage.Execute) - executeBuffer.writeInt(executeLength - 1) - - executeBuffer.writeBytes(emptyStringBytes) - executeBuffer.writeByte(0) - - executeBuffer.writeInt(0) - - val closeLength = 1 + 4 + 1 + emptyStringBytes.length + 1 - val closeBuffer = ChannelBuffers.buffer(closeLength) - closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) - closeBuffer.writeInt(closeLength - 1) - closeBuffer.writeByte('P') - - closeBuffer.writeBytes(emptyStringBytes) - closeBuffer.writeByte(0) - - val syncBuffer = ChannelBuffers.buffer(5) - syncBuffer.writeByte(ServerMessage.Sync) - syncBuffer.writeInt(4) - - ChannelBuffers.wrappedBuffer(parseBuffer, bindBuffer, describeBuffer, executeBuffer, closeBuffer, syncBuffer) + val executeBuffer = writeExecutePortal(statementIdBytes, m.values, encoder, charset, true) + ChannelBuffers.wrappedBuffer(parseBuffer, executeBuffer) } -} +} \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala index 7fc1d93c..6b6d70e0 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementExecuteMessage.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class PreparedStatementExecuteMessage(queryId: String, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(queryId, ServerMessage.Execute, query, values, encoderRegistry) +class PreparedStatementExecuteMessage(statementId: Int, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(statementId, ServerMessage.Execute, query, values, encoderRegistry) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala index a76b8208..a16bba91 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementMessage.scala @@ -19,11 +19,11 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry class PreparedStatementMessage( - val queryId: String, + val statementId: Int, kind: Byte, val query: String, val values: Seq[Any], - encoderRegistry : ColumnEncoderRegistry + encoderRegistry: ColumnEncoderRegistry ) extends ClientMessage(kind) { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index bdce254d..7863033e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -19,5 +19,5 @@ package com.github.mauricio.async.db.postgresql.messages.frontend import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -class PreparedStatementOpeningMessage(queryId: String, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(queryId: String, ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file +class PreparedStatementOpeningMessage(statementId: Int, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) + extends PreparedStatementMessage(statementId: Int, ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala index 6d6d430b..77591c3b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala @@ -65,8 +65,7 @@ class ConnectionObjectFactory( val configuration : Configuration ) extends Objec if ( !item.isConnected || item.hasRecentError ) { throw new ClosedChannelException() } - item.validateIfItIsReadyForQuery( - errorMessage = "[{}] - Trying to give back a connection that is not ready for query") + item.validateIfItIsReadyForQuery("Trying to give back a connection that is not ready for query") item } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnectionSpec.scala similarity index 84% rename from postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala rename to postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnectionSpec.scala index 0998df54..092ae9dd 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseConnectionHandlerSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnectionSpec.scala @@ -27,7 +27,7 @@ import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ -class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelper { +class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { val create = """create temp table type_test_table ( bigserial_column bigserial not null, @@ -88,17 +88,6 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe val preparedStatementInsertReturning = " insert into prepared_statement_test (name) values ('John Doe') returning id" val preparedStatementSelect = "select * from prepared_statement_test" - val messagesCreate = """CREATE TEMP TABLE messages - ( - id bigserial NOT NULL, - content character varying(255) NOT NULL, - moment date NOT NULL, - CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) - )""" - val messagesInsert = "INSERT INTO messages (content,moment) VALUES (?,?) RETURNING id" - val messagesUpdate = "UPDATE messages SET content = ?, moment = ? WHERE id = ?" - val messagesSelectOne = "SELECT id, content, moment FROM messages WHERE id = ?" - "handler" should { "connect to the database" in { @@ -220,18 +209,6 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } - "execute a prepared statement without any parameters multiple times" in { - - withHandler { - handler => - executeDdl(handler, this.messagesCreate) - executePreparedStatement(handler, "UPDATE messages SET content = content") - executePreparedStatement(handler, "UPDATE messages SET content = content") - ok - } - - } - "login using MD5 authentication" in { val configuration = new Configuration( @@ -366,27 +343,6 @@ class DatabaseConnectionHandlerSpec extends Specification with DatabaseTestHelpe } must throwA[QueryMustNotBeNullOrEmptyException] } - - "raise an exception if the parameter count is different from the given parameters count" in { - - withHandler { - handler => - executeDdl( handler, this.messagesCreate ) - executePreparedStatement(handler, this.messagesSelectOne) must throwAn[InsufficientParametersException] - } - - } - - "support prepared statement with more than 64 characters" in { - withHandler { - handler => - executeDdl( handler, this.messagesCreate ) - val stmt = "SELECT id, content, moment FROM messages WHERE id is not null AND content is not null " - executePreparedStatement(handler, stmt + "AND moment is not null") - executePreparedStatement(handler, stmt + "AND moment is null") - ok - } - } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala new file mode 100644 index 00000000..7311f08a --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -0,0 +1,93 @@ +/* + * 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 + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.postgresql.exceptions.InsufficientParametersException +import org.joda.time.LocalDate + +class PreparedStatementSpec extends Specification with DatabaseTestHelper { + + val filler = List.fill(64)(" ").mkString("") + + val messagesCreate = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + content character varying(255) NOT NULL, + moment date NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + val messagesInsert = s"INSERT INTO messages $filler (content,moment) VALUES (?,?) RETURNING id" + val messagesInsertReverted = s"INSERT INTO messages $filler (moment,content) VALUES (?,?) RETURNING id" + val messagesUpdate = "UPDATE messages SET content = ?, moment = ? WHERE id = ?" + val messagesSelectOne = "SELECT id, content, moment FROM messages WHERE id = ?" + val messagesSelectAll = "SELECT id, content, moment FROM messages" + + "prepared statements" should { + + "support prepared statement with more than 64 characters" in { + withHandler { + handler => + + val firstContent = "Some Moment" + val secondContent = "Some Other Moment" + val date = LocalDate.now() + + executeDdl(handler, this.messagesCreate) + executePreparedStatement(handler, this.messagesInsert, Array(firstContent, date)) + executePreparedStatement(handler, this.messagesInsertReverted, Array(date, secondContent)) + + val rows = executePreparedStatement(handler, this.messagesSelectAll).rows.get + + rows.length === 2 + + rows(0)("id") === 1 + rows(0)("content") === firstContent + rows(0)("moment") === date + + rows(1)("id") === 2 + rows(1)("content") === secondContent + rows(1)("moment") === date + + } + } + + "execute a prepared statement without any parameters multiple times" in { + + withHandler { + handler => + executeDdl(handler, this.messagesCreate) + executePreparedStatement(handler, "UPDATE messages SET content = content") + executePreparedStatement(handler, "UPDATE messages SET content = content") + ok + } + + } + + "raise an exception if the parameter count is different from the given parameters count" in { + + withHandler { + handler => + executeDdl(handler, this.messagesCreate) + executePreparedStatement(handler, this.messagesSelectOne) must throwAn[InsufficientParametersException] + } + + } + + } + +} 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 70749be4..baa81c6e 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 @@ -129,7 +129,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH withPool { pool => val connection = get(pool) - connection.sendPreparedStatement("select 1") + connection.sendPreparedStatement("SELECT pg_sleep(3)") await(pool.giveBack(connection)) must throwA[ConnectionStillRunningQueryException] pool.availables.size === 0 From 35d9b53d92f5b44fd0d82590d9b67660c6d4e2e8 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 May 2013 22:52:16 -0300 Subject: [PATCH 079/357] Use Duration for TIME objects since they represent a absolute time ( 850:00:00 ) and not time of day --- .../db/mysql/binary/BinaryRowDecoder.scala | 38 ++++++++++---- .../binary/decoder/BigDecimalDecoder.scala | 27 ++++++++++ .../binary/decoder/ByteArrayDecoder.scala | 30 +++++++++++ .../db/mysql/binary/decoder/ByteDecoder.scala | 23 +++++++++ .../db/mysql/binary/decoder/DateDecoder.scala | 24 +++++++++ .../mysql/binary/decoder/DoubleDecoder.scala | 23 +++++++++ .../mysql/binary/decoder/FloatDecoder.scala | 23 +++++++++ .../mysql/binary/decoder/IntegerDecoder.scala | 23 +++++++++ .../mysql/binary/decoder/ShortDecoder.scala | 23 +++++++++ .../db/mysql/binary/decoder/TimeDecoder.scala | 39 ++++++++++++++ .../binary/decoder/TimestampDecoder.scala | 43 ++++++++++++++++ .../mysql/codec/MySQLConnectionHandler.scala | 2 +- .../column/MySQLColumnDecoderRegistry.scala | 2 +- .../async/db/mysql/column/TimeDecoder.scala | 44 ++++++++++++++++ .../mauricio/async/db/mysql/QuerySpec.scala | 7 ++- .../db/mysql/column/TimeDecoderSpec.scala | 51 +++++++++++++++++++ 16 files changed, 405 insertions(+), 17 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/TimeDecoder.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/column/TimeDecoderSpec.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index 33fd4af0..bb05d06a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -24,21 +24,20 @@ import com.github.mauricio.async.db.util.{Log, PrintUtils, BitMap} import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import scala.collection.mutable.ArrayBuffer -import com.github.mauricio.async.db.mysql.MySQLHelper -import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage object BinaryRowDecoder { final val log = Log.get[BinaryRowDecoder] final val BitMapOffset = 9 } -class BinaryRowDecoder( charset : Charset ) { +class BinaryRowDecoder(charset: Charset) { import BinaryRowDecoder._ + private final val bigDecimalDecoder = new BigDecimalDecoder(charset) private final val stringDecoder = new StringDecoder(charset) - def decode( buffer : ChannelBuffer, columns : Seq[ColumnDefinitionMessage] ) : IndexedSeq[Any] = { + def decode(buffer: ChannelBuffer, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { //log.debug("columns are {}", columns) @@ -49,7 +48,7 @@ class BinaryRowDecoder( charset : Charset ) { val quotient = totalBytes / 8 val remainder = totalBytes % 8 - val bitMapSource = new Array[Byte]( if ( remainder == 0 ) quotient else quotient + 1 ) + val bitMapSource = new Array[Byte](if (remainder == 0) quotient else quotient + 1) //log.debug("Bit map size is {} - columns count is {}", bitMapSource.length, columns.length) @@ -60,9 +59,9 @@ class BinaryRowDecoder( charset : Charset ) { val row = new ArrayBuffer[Any](columns.size) - bitMap.foreachWithLimit( BitMapOffset, columns.length, { - case ( index, isNull ) => { - if ( isNull ) { + bitMap.foreachWithLimit(BitMapOffset, columns.length, { + case (index, isNull) => { + if (isNull) { row += null } else { row += decoderFor(columns(index - BitMapOffset).columnType).decode(buffer) @@ -70,17 +69,34 @@ class BinaryRowDecoder( charset : Charset ) { } }) - if ( buffer.readableBytes() != 0 ) { + if (buffer.readableBytes() != 0) { throw new BufferNotFullyConsumedException(buffer) } row } - def decoderFor( columnType : Int ) : BinaryDecoder = { + def decoderFor(columnType: Int): BinaryDecoder = { columnType match { - case ColumnTypes.FIELD_TYPE_VARCHAR | ColumnTypes.FIELD_TYPE_VAR_STRING => this.stringDecoder + case ColumnTypes.FIELD_TYPE_VARCHAR | + ColumnTypes.FIELD_TYPE_VAR_STRING | + ColumnTypes.FIELD_TYPE_STRING => this.stringDecoder + case ColumnTypes.FIELD_TYPE_BLOB | + ColumnTypes.FIELD_TYPE_LONG_BLOB | + ColumnTypes.FIELD_TYPE_MEDIUM_BLOB | + ColumnTypes.FIELD_TYPE_TINY_BLOB => ByteArrayDecoder case ColumnTypes.FIELD_TYPE_LONGLONG => LongDecoder + case ColumnTypes.FIELD_TYPE_LONG | ColumnTypes.FIELD_TYPE_INT24 => IntegerDecoder + case ColumnTypes.FIELD_TYPE_YEAR | ColumnTypes.FIELD_TYPE_SHORT => ShortDecoder + case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder + case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleDecoder + case ColumnTypes.FIELD_TYPE_FLOAT => FloatDecoder + case ColumnTypes.FIELD_TYPE_NUMERIC | + ColumnTypes.FIELD_TYPE_DECIMAL | + ColumnTypes.FIELD_TYPE_NEW_DECIMAL => this.bigDecimalDecoder + case ColumnTypes.FIELD_TYPE_DATETIME | ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampDecoder + case ColumnTypes.FIELD_TYPE_DATE => DateDecoder + case ColumnTypes.FIELD_TYPE_TIME => TimeDecoder } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala new file mode 100644 index 00000000..5418c551 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala @@ -0,0 +1,27 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import java.nio.charset.Charset + +class BigDecimalDecoder( charset : Charset ) extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = { + BigDecimal( buffer.readLengthEncodedString(charset) ) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala new file mode 100644 index 00000000..45ec3066 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala @@ -0,0 +1,30 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper + +object ByteArrayDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = { + val length = buffer.readBinaryLength + val bytes = new Array[Byte](length.toInt) + buffer.readBytes(bytes) + + bytes + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala new file mode 100644 index 00000000..a8ed0ba4 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +object ByteDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = buffer.readByte() +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala new file mode 100644 index 00000000..e77c8dfb --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala @@ -0,0 +1,24 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.LocalDate + +object DateDecoder extends BinaryDecoder { + override def decode(buffer: ChannelBuffer): LocalDate = TimestampDecoder.decode(buffer).toLocalDate +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala new file mode 100644 index 00000000..7fe3406f --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +object DoubleDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = buffer.readDouble() +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala new file mode 100644 index 00000000..d62fa26f --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +object FloatDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = buffer.readFloat() +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala new file mode 100644 index 00000000..71e502e9 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala @@ -0,0 +1,23 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +object IntegerDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = buffer.readInt() +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala new file mode 100644 index 00000000..8a4c7a49 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala @@ -0,0 +1,23 @@ +/* +* 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer + +object ShortDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Any = buffer.readShort() +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala new file mode 100644 index 00000000..23638bde --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala @@ -0,0 +1,39 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import scala.concurrent.duration._ + +object TimeDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): Duration = { + + buffer.readUnsignedByte() match { + case 0 => 0.seconds + case 8 => buffer.readUnsignedInt().days + + buffer.readUnsignedByte().hours + + buffer.readUnsignedByte().minutes + + buffer.readUnsignedByte().seconds + case 12 => buffer.readUnsignedInt().days + + buffer.readUnsignedByte().hours + + buffer.readUnsignedByte().minutes + + buffer.readUnsignedByte().seconds + + buffer.readUnsignedInt().millis + } + + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala new file mode 100644 index 00000000..ad5e5d92 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala @@ -0,0 +1,43 @@ +/* + * 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.mysql.binary.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.LocalDateTime + +object TimestampDecoder extends BinaryDecoder { + def decode(buffer: ChannelBuffer): LocalDateTime = { + + val size = buffer.readUnsignedByte() + + size match { + case 0 => LocalDateTime.now() + .withDate(0,0,0) + .withTime(0,0,0,0) + case 4 => new LocalDateTime() + .withDate( buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte() ) + .withTime(0,0,0,0) + case 7 => new LocalDateTime() + .withDate( buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte() ) + .withTime( buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), 0) + case 11 => new LocalDateTime() + .withDate( buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte() ) + .withTime( buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedInt().toInt) + } + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index a9eb4e38..14c833e9 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -89,7 +89,7 @@ class MySQLConnectionHandler( this.bootstrap.setOption("bufferFactory", HeapChannelBufferFactory.getInstance(ByteOrder.LITTLE_ENDIAN)); this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { - case exception => this.connectionPromise.failure(exception) + case exception => this.connectionPromise.tryFailure(exception) } this.connectionPromise.future diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala index 17c1dcf3..72a43f15 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -44,7 +44,7 @@ class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { case ColumnTypes.FIELD_TYPE_NEWDATE => DateEncoderDecoder case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder case ColumnTypes.FIELD_TYPE_STRING => StringEncoderDecoder - case ColumnTypes.FIELD_TYPE_TIME => TimeEncoderDecoder.Instance + case ColumnTypes.FIELD_TYPE_TIME => TimeDecoder case ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampEncoderDecoder.Instance case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder case ColumnTypes.FIELD_TYPE_VAR_STRING => StringEncoderDecoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/TimeDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/TimeDecoder.scala new file mode 100644 index 00000000..56098c3c --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/TimeDecoder.scala @@ -0,0 +1,44 @@ +/* + * 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.mysql.column + +import com.github.mauricio.async.db.column.ColumnDecoder +import scala.concurrent.duration._ + +object TimeDecoder extends ColumnDecoder { + + final val Hour = 1.hour.toMillis + + override def decode(value: String): Duration = { + + val pieces = value.split(':') + + val secondsAndMillis = pieces(2).split('.') + + val parts = if ( secondsAndMillis.length == 2 ) { + (secondsAndMillis(0).toInt,secondsAndMillis(1).toInt) + } else { + (secondsAndMillis(0).toInt,0) + } + + val hours = pieces(0).toInt + val minutes = pieces(1).toInt + + hours.hours + minutes.minutes + parts._1.seconds + parts._2.millis + } + +} \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 17069e5c..7bbc9bd9 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -19,6 +19,8 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.mysql.exceptions.MySQLException import org.joda.time.{ReadableDateTime, LocalTime, LocalDate} import org.specs2.mutable.Specification +import scala.concurrent.duration.Duration +import java.util.concurrent.TimeUnit class QuerySpec extends Specification with ConnectionHelper { @@ -111,10 +113,7 @@ class QuerySpec extends Specification with ConnectionHelper { timestamp.getSecondOfMinute === 7 - val time = result("created_at_time").asInstanceOf[LocalTime] - time.getHourOfDay === 3 - time.getMinuteOfHour === 14 - time.getSecondOfMinute === 7 + result("created_at_time") === Duration( 3, TimeUnit.HOURS ) + Duration( 14, TimeUnit.MINUTES ) + Duration( 7, TimeUnit.SECONDS ) val year = result("created_at_year").asInstanceOf[Int] diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/column/TimeDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/column/TimeDecoderSpec.scala new file mode 100644 index 00000000..2560b8ea --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/column/TimeDecoderSpec.scala @@ -0,0 +1,51 @@ +/* + * 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.mysql.column + +import java.util.concurrent.TimeUnit +import org.specs2.mutable.Specification +import scala.concurrent.duration.Duration + +class TimeDecoderSpec extends Specification { + + "decoder" in { + + "handle a time" in { + + val time = "120:10:07" + val duration = Duration( 120, TimeUnit.HOURS ) + + Duration( 10, TimeUnit.MINUTES ) + + Duration( 7, TimeUnit.SECONDS ) + + TimeDecoder.decode(time) === duration + } + + "handle a time with millis" in { + + val time = "120:10:07.00098" + val duration = Duration( 120, TimeUnit.HOURS ) + + Duration( 10, TimeUnit.MINUTES ) + + Duration( 7, TimeUnit.SECONDS ) + + Duration( 98, TimeUnit.MILLISECONDS ) + + TimeDecoder.decode(time) === duration + + } + + } + +} From aaaabac4b1d41c0187f2665f4d16b1a1264cace5 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 May 2013 23:34:41 -0300 Subject: [PATCH 080/357] Adding specs to prepared statement decoding --- .../db/mysql/binary/BinaryRowDecoder.scala | 16 +++-- .../db/mysql/binary/decoder/TimeDecoder.scala | 42 ++++++++++--- .../binary/decoder/TimestampDecoder.scala | 18 +++--- .../column/MySQLColumnDecoderRegistry.scala | 2 +- .../async/db/mysql/ConnectionHelper.scala | 55 ++++++++++++++++ .../db/mysql/PreparedStatementsSpec.scala | 62 +++++++++++++++++++ .../mauricio/async/db/mysql/QuerySpec.scala | 61 +----------------- .../binary/decoder/BinaryDecodersSpec.scala | 28 +++++++++ 8 files changed, 200 insertions(+), 84 deletions(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index bb05d06a..e7a83d9a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -20,10 +20,12 @@ import com.github.mauricio.async.db.exceptions.BufferNotFullyConsumedException import com.github.mauricio.async.db.mysql.binary.decoder._ import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage -import com.github.mauricio.async.db.util.{Log, PrintUtils, BitMap} +import com.github.mauricio.async.db.util._ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import scala.collection.mutable.ArrayBuffer +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage +import com.github.mauricio.async.db.mysql.MySQLHelper object BinaryRowDecoder { final val log = Log.get[BinaryRowDecoder] @@ -39,9 +41,9 @@ class BinaryRowDecoder(charset: Charset) { def decode(buffer: ChannelBuffer, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { - //log.debug("columns are {}", columns) + log.debug("columns are {}", columns) - //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) + log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) PrintUtils.printArray("bitmap", buffer) val totalBytes = (columns.size + 7 + 2) @@ -64,11 +66,17 @@ class BinaryRowDecoder(charset: Charset) { if (isNull) { row += null } else { - row += decoderFor(columns(index - BitMapOffset).columnType).decode(buffer) + val decoder = decoderFor(columns(index - BitMapOffset).columnType) + + log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") + + row += decoder.decode(buffer) } } }) + log.debug("values are {}", row) + if (buffer.readableBytes() != 0) { throw new BufferNotFullyConsumedException(buffer) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala index 23638bde..f952955f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala @@ -24,15 +24,39 @@ object TimeDecoder extends BinaryDecoder { buffer.readUnsignedByte() match { case 0 => 0.seconds - case 8 => buffer.readUnsignedInt().days + - buffer.readUnsignedByte().hours + - buffer.readUnsignedByte().minutes + - buffer.readUnsignedByte().seconds - case 12 => buffer.readUnsignedInt().days + - buffer.readUnsignedByte().hours + - buffer.readUnsignedByte().minutes + - buffer.readUnsignedByte().seconds + - buffer.readUnsignedInt().millis + case 8 => { + + val isNegative = buffer.readUnsignedByte() == 1 + + val duration = buffer.readUnsignedInt().days + + buffer.readUnsignedByte().hours + + buffer.readUnsignedByte().minutes + + buffer.readUnsignedByte().seconds + + if ( isNegative ) { + duration.neg() + } else { + duration + } + + } + case 12 => { + + val isNegative = buffer.readUnsignedByte() == 1 + + val duration = buffer.readUnsignedInt().days + + buffer.readUnsignedByte().hours + + buffer.readUnsignedByte().minutes + + buffer.readUnsignedByte().seconds + + buffer.readUnsignedInt().millis + + if ( isNegative ) { + duration.neg() + } else { + duration + } + + } } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala index ad5e5d92..af6ad974 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala @@ -21,23 +21,21 @@ import org.joda.time.LocalDateTime object TimestampDecoder extends BinaryDecoder { def decode(buffer: ChannelBuffer): LocalDateTime = { - val size = buffer.readUnsignedByte() size match { case 0 => LocalDateTime.now() - .withDate(0,0,0) - .withTime(0,0,0,0) + .withDate(0, 0, 0) + .withTime(0, 0, 0, 0) case 4 => new LocalDateTime() - .withDate( buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte() ) - .withTime(0,0,0,0) + .withDate(buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte()) + .withTime(0, 0, 0, 0) case 7 => new LocalDateTime() - .withDate( buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte() ) - .withTime( buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), 0) + .withDate(buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte()) + .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), 0) case 11 => new LocalDateTime() - .withDate( buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte() ) - .withTime( buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedInt().toInt) + .withDate(buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte()) + .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedInt().toInt) } } - } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala index 72a43f15..0c4ba848 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -49,7 +49,7 @@ class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder case ColumnTypes.FIELD_TYPE_VAR_STRING => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_VARCHAR => StringEncoderDecoder - case ColumnTypes.FIELD_TYPE_YEAR => IntegerEncoderDecoder + case ColumnTypes.FIELD_TYPE_YEAR => ShortEncoderDecoder case _ => StringEncoderDecoder } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index 6b91467a..3b4df97e 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -21,6 +21,61 @@ import com.github.mauricio.async.db.{QueryResult, Configuration} trait ConnectionHelper { + val createTableNumericColumns = + """ + |create temporary table numbers ( + |id int auto_increment not null, + |number_tinyint tinyint not null, + |number_smallint smallint not null, + |number_mediumint mediumint not null, + |number_int int not null, + |number_bigint bigint not null, + |number_decimal decimal(9,6), + |number_float float, + |number_double double, + |primary key (id) + |) + """.stripMargin + + val insertTableNumericColumns = + """ + |insert into numbers ( + |number_tinyint, + |number_smallint, + |number_mediumint, + |number_int, + |number_bigint, + |number_decimal, + |number_float, + |number_double + |) values + |(-100, 32766, 8388607, 2147483647, 9223372036854775807, 450.764491, 14.7, 87650.9876) + """.stripMargin + + val createTableTimeColumns = + """CREATE TEMPORARY TABLE posts ( + id INT NOT NULL AUTO_INCREMENT, + created_at_date DATE not null, + created_at_datetime DATETIME not null, + created_at_timestamp TIMESTAMP not null, + created_at_time TIME not null, + created_at_year YEAR not null, + primary key (id) + )""" + + val insertTableTimeColumns = + """ + |insert into posts (created_at_date, created_at_datetime, created_at_timestamp, created_at_time, created_at_year) + |values ( '2038-01-19', '2013-01-19 03:14:07', '2020-01-19 03:14:07', '03:14:07', '1999' ) + """.stripMargin + + final val createTable = """CREATE TEMPORARY TABLE users ( + id INT NOT NULL AUTO_INCREMENT , + name VARCHAR(255) CHARACTER SET 'utf8' NOT NULL , + PRIMARY KEY (id) );""" + final val insert = """INSERT INTO users (name) VALUES ('Maurício Aragão')""" + final val select = """SELECT * FROM users""" + def defaultConfiguration = new Configuration( "mysql_async", "localhost", diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index d6948e58..3c895927 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -17,6 +17,9 @@ package com.github.mauricio.async.db.mysql import org.specs2.mutable.Specification +import org.joda.time._ +import scala.concurrent.duration.Duration +import java.util.concurrent.TimeUnit class PreparedStatementsSpec extends Specification with ConnectionHelper { @@ -56,6 +59,65 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } + "be able to select numbers and process them" in { + + withConnection { + connection => + executeQuery(connection, createTableNumericColumns) + executeQuery(connection, insertTableNumericColumns) + val result = executePreparedStatement(connection, "SELECT * FROM numbers").rows.get(0) + + result("number_tinyint").asInstanceOf[Byte] === -100 + result("number_smallint").asInstanceOf[Short] === 32766 + result("number_mediumint").asInstanceOf[Int] === 8388607 + result("number_int").asInstanceOf[Int] === 2147483647 + result("number_bigint").asInstanceOf[Long] === 9223372036854775807L + result("number_decimal") === BigDecimal(450.764491) + result("number_float") === 14.7F + result("number_double") === 87650.9876 + } + + } + + "be able to select from a table with timestamps" in { + + withConnection { + connection => + executeQuery(connection, createTableTimeColumns) + executeQuery(connection, insertTableTimeColumns) + val result = executePreparedStatement(connection, "SELECT * FROM posts").rows.get(0) + + val date = result("created_at_date").asInstanceOf[LocalDate] + + date.getYear === 2038 + date.getMonthOfYear === 1 + date.getDayOfMonth === 19 + + val dateTime = result("created_at_datetime").asInstanceOf[LocalDateTime] + dateTime.getYear === 2013 + dateTime.getMonthOfYear === 1 + dateTime.getDayOfMonth === 19 + dateTime.getHourOfDay === 3 + dateTime.getMinuteOfHour === 14 + dateTime.getSecondOfMinute === 7 + + val timestamp = result("created_at_timestamp").asInstanceOf[LocalDateTime] + timestamp.getYear === 2020 + timestamp.getMonthOfYear === 1 + timestamp.getDayOfMonth === 19 + timestamp.getHourOfDay === 3 + timestamp.getMinuteOfHour === 14 + timestamp.getSecondOfMinute === 7 + + result("created_at_time") === Duration( 3, TimeUnit.HOURS ) + Duration( 14, TimeUnit.MINUTES ) + Duration( 7, TimeUnit.SECONDS ) + + val year = result("created_at_year").asInstanceOf[Short] + + year === 1999 + } + + } + } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 7bbc9bd9..70caeb47 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -24,13 +24,6 @@ import java.util.concurrent.TimeUnit class QuerySpec extends Specification with ConnectionHelper { - final val createTable = """CREATE TEMPORARY TABLE users ( - id INT NOT NULL AUTO_INCREMENT , - name VARCHAR(255) CHARACTER SET 'utf8' NOT NULL , - PRIMARY KEY (id) );""" - final val insert = """INSERT INTO users (name) VALUES ('Maurício Aragão')""" - final val select = """SELECT * FROM users""" - "connection" should { "be able to run a DML query" in { @@ -67,23 +60,6 @@ class QuerySpec extends Specification with ConnectionHelper { "be able to select from a table with timestamps" in { - val createTableTimeColumns = - """CREATE TEMPORARY TABLE posts ( - id INT NOT NULL AUTO_INCREMENT, - created_at_date DATE not null, - created_at_datetime DATETIME not null, - created_at_timestamp TIMESTAMP not null, - created_at_time TIME not null, - created_at_year YEAR not null, - primary key (id) - )""" - - val insertTableTimeColumns = - """ - |insert into posts (created_at_date, created_at_datetime, created_at_timestamp, created_at_time, created_at_year) - |values ( '2038-01-19', '2013-01-19 03:14:07', '2020-01-19 03:14:07', '03:14:07', '1999' ) - """.stripMargin - withConnection { connection => executeQuery(connection, createTableTimeColumns) @@ -115,48 +91,15 @@ class QuerySpec extends Specification with ConnectionHelper { result("created_at_time") === Duration( 3, TimeUnit.HOURS ) + Duration( 14, TimeUnit.MINUTES ) + Duration( 7, TimeUnit.SECONDS ) - val year = result("created_at_year").asInstanceOf[Int] + val year = result("created_at_year").asInstanceOf[Short] year === 1999 - - } } "be able to select from a table with the various numeric types" in { - val createTableNumericColumns = - """ - |create temporary table numbers ( - |id int auto_increment not null, - |number_tinyint tinyint not null, - |number_smallint smallint not null, - |number_mediumint mediumint not null, - |number_int int not null, - |number_bigint bigint not null, - |number_decimal decimal(9,6), - |number_float float, - |number_double double, - |primary key (id) - |) - """.stripMargin - - val insertTableNumericColumns = - """ - |insert into numbers ( - |number_tinyint, - |number_smallint, - |number_mediumint, - |number_int, - |number_bigint, - |number_decimal, - |number_float, - |number_double - |) values - |(-100, 32766, 8388607, 2147483647, 9223372036854775807, 450.764491, 14.7, 87650.9876) - """.stripMargin - withConnection { connection => executeQuery(connection, createTableNumericColumns) @@ -171,8 +114,6 @@ class QuerySpec extends Specification with ConnectionHelper { result("number_decimal") === BigDecimal(450.764491) result("number_float") === 14.7F result("number_double") === 87650.9876 - - } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala new file mode 100644 index 00000000..4b33dd14 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.binary.decoder + +import org.specs2.mutable.Specification + +class BinaryDecodersSpec extends Specification { + + "decoders" should { + + + } + +} From 6f0d38e3efbe4a09b8e5ed6a8b69f3d4372aeec5 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 12 May 2013 01:48:21 -0300 Subject: [PATCH 081/357] More work on prepared statements support for MySQL --- .../mauricio/async/db/util/BitMap.scala | 19 +++++ .../async/db/util/ChannelWrapper.scala | 28 ++++++- .../db/mysql/binary/BinaryRowDecoder.scala | 25 ++---- .../db/mysql/binary/BinaryRowEncoder.scala | 76 +++++++++++++++++++ .../mysql/binary/encoder/BinaryEncoder.scala | 2 +- .../db/mysql/binary/encoder/ByteEncoder.scala | 25 ++++++ .../db/mysql/binary/encoder/DateEncoder.scala | 37 +++++++++ .../mysql/binary/encoder/DoubleEncoder.scala | 25 ++++++ .../mysql/binary/encoder/FloatEncoder.scala | 25 ++++++ .../mysql/binary/encoder/IntegerEncoder.scala | 25 ++++++ .../db/mysql/binary/encoder/LongEncoder.scala | 25 ++++++ .../mysql/binary/encoder/ShortEncoder.scala | 25 ++++++ .../mysql/binary/encoder/StringEncoder.scala | 27 +++++++ .../binary/encoder/TimestampEncoder.scala | 46 +++++++++++ .../binary/decoder/BinaryDecodersSpec.scala | 1 + 15 files changed, 391 insertions(+), 20 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala index 8884b85d..1e7114f3 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala @@ -16,11 +16,30 @@ package com.github.mauricio.async.db.util +import org.jboss.netty.buffer.ChannelBuffer + object BitMap { final val Bytes = Array(128, 64, 32, 16, 8, 4, 2, 1) def apply(bytes: Byte*): BitMap = new BitMap(bytes.toArray) + def forSize( totalBits : Int ) : BitMap = { + val quotient = totalBits / 8 + val remainder = totalBits % 8 + val finalSize = if ( remainder == 0 ) quotient else quotient + 1 + new BitMap( new Array[Byte](finalSize) ) + } + + def fromBuffer( totalBits : Int, buffer : ChannelBuffer ) : BitMap = { + val quotient = totalBits / 8 + val remainder = totalBits % 8 + + val bitMapSource = new Array[Byte](if (remainder == 0) quotient else quotient + 1) + buffer.readBytes(bitMapSource) + + new BitMap(bitMapSource) + } + } /** diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index 7d735ceb..678f4092 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -61,7 +61,6 @@ class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { } def readBinaryLength : Long = { - val firstByte = buffer.readUnsignedByte() if ( firstByte <= 250 ) { @@ -78,4 +77,31 @@ class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { } + def writeLength( length : Long ) { + if (length < 251) { + buffer.writeByte( length.asInstanceOf[Byte]) + } else if (length < 65536L) { + buffer.writeByte(252) + buffer.writeInt(length.asInstanceOf[Int]) + } else if (length < 16777216L) { + buffer.writeByte(253) + writeLongInt(length.asInstanceOf[Int]) + } else { + buffer.writeByte(254) + buffer.writeLong(length) + } + } + + def writeLongInt(i : Int) { + buffer.writeByte( i & 0xff ) + buffer.writeByte( i >>> 8 ) + buffer.writeByte( i >>> 16 ) + } + + def writeLenghtEncodedString( value : String, charset : Charset ) { + val bytes = value.getBytes(charset) + writeLength(bytes.length) + buffer.writeBytes(bytes) + } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index e7a83d9a..f0b76a99 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -24,8 +24,6 @@ import com.github.mauricio.async.db.util._ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import scala.collection.mutable.ArrayBuffer -import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage -import com.github.mauricio.async.db.mysql.MySQLHelper object BinaryRowDecoder { final val log = Log.get[BinaryRowDecoder] @@ -41,21 +39,12 @@ class BinaryRowDecoder(charset: Charset) { def decode(buffer: ChannelBuffer, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { - log.debug("columns are {}", columns) - - log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) - PrintUtils.printArray("bitmap", buffer) - - val totalBytes = (columns.size + 7 + 2) - val quotient = totalBytes / 8 - val remainder = totalBytes % 8 + //log.debug("columns are {}", columns) - val bitMapSource = new Array[Byte](if (remainder == 0) quotient else quotient + 1) + //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) + //PrintUtils.printArray("bitmap", buffer) - //log.debug("Bit map size is {} - columns count is {}", bitMapSource.length, columns.length) - - buffer.readBytes(bitMapSource) - val bitMap = new BitMap(bitMapSource) + val bitMap = BitMap.fromBuffer( columns.size + 7 + 2, buffer ) //log.debug("bitmap is {}", bitMap) @@ -68,14 +57,14 @@ class BinaryRowDecoder(charset: Charset) { } else { val decoder = decoderFor(columns(index - BitMapOffset).columnType) - log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") + //log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") row += decoder.decode(buffer) } } }) - log.debug("values are {}", row) + //log.debug("values are {}", row) if (buffer.readableBytes() != 0) { throw new BufferNotFullyConsumedException(buffer) @@ -108,4 +97,4 @@ class BinaryRowDecoder(charset: Charset) { } } -} +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala new file mode 100644 index 00000000..df4a253f --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -0,0 +1,76 @@ +/* + * 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.mysql.binary + +import java.nio.charset.Charset +import com.github.mauricio.async.db.mysql.binary.encoder._ +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.{BitMap, ChannelUtils} +import org.joda.time._ + +class BinaryRowEncoder( charset : Charset ) { + + private final val stringEncoder = new StringEncoder(charset) + + def encode( values : Seq[Any] ) : ChannelBuffer = { + + val buffer = ChannelUtils.packetBuffer() + val bitMap = BitMap.forSize(values.length) + + var index = 0 + + while ( index < values.length ) { + val value = values(index) + if ( value == null ) { + bitMap.set(index) + } else { + val encoder = encoderFor(value) + encoder.encode(value, buffer) + } + index += 1 + } + + buffer + } + + private def encoderFor( v : Any ) : BinaryEncoder = { + + v match { + case value : String | + decimalJava : java.math.BigDecimal | + decimal : BigDecimal | + integerJava : java.math.BigInteger | + integer : BigInt => this.stringEncoder + case value : Byte | v : java.lang.Byte => ByteEncoder + case value : Short | v : java.lang.Short => ShortEncoder + case value : Int | v : java.lang.Integer => IntegerEncoder + case value : Long | v : java.lang.Long => LongEncoder + case value : Float | v : java.lang.Float => FloatEncoder + case value : Double | v : java.lang.Double => DoubleEncoder + case v : ReadableDateTime => TimestampEncoder + case v : ReadableInstant => TimestampEncoder + case v : LocalDateTime => TimestampEncoder + case v : java.util.Date => TimestampEncoder + case v : java.sql.Timestamp => TimestampEncoder + case v : java.util.Calendar => TimestampEncoder + case d : LocalDate => DateEncoder + case d : java.sql.Date => DateEncoder + } + + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala index 9444326a..b5c74373 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala @@ -20,6 +20,6 @@ import org.jboss.netty.buffer.ChannelBuffer trait BinaryEncoder { - def encode( value : Any ) : ChannelBuffer + def encode( value : Any, buffer : ChannelBuffer ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala new file mode 100644 index 00000000..6e9051ec --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +object ByteEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + buffer.writeByte(value.asInstanceOf[Byte]) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala new file mode 100644 index 00000000..a7b0916c --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala @@ -0,0 +1,37 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.LocalDate +import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException + +object DateEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val date = value match { + case d : LocalDate => d + case d : java.sql.Date => new LocalDate(d) + case _ => throw new DateEncoderNotAvailableException(value) + } + + buffer.writeByte(4) + buffer.writeShort(date.getYear) + buffer.writeByte(date.getMonthOfYear) + buffer.writeByte(date.getDayOfMonth) + + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala new file mode 100644 index 00000000..ea97cb99 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +object DoubleEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + buffer.writeDouble(value.asInstanceOf[Double]) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala new file mode 100644 index 00000000..98c574f7 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +object FloatEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + buffer.writeFloat(value.asInstanceOf[Float]) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala new file mode 100644 index 00000000..f412520c --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +object IntegerEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + buffer.writeInt(value.asInstanceOf[Int]) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala new file mode 100644 index 00000000..9c64f2fa --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +object LongEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + buffer.writeLong(value.asInstanceOf[Long]) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala new file mode 100644 index 00000000..ff05bc19 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala @@ -0,0 +1,25 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +object ShortEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + buffer.writeShort(value.asInstanceOf[Short]) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala new file mode 100644 index 00000000..ca8fe44f --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala @@ -0,0 +1,27 @@ +/* + * 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.mysql.binary.encoder + +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer + +class StringEncoder( charset : Charset ) extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + buffer.writeLenghtEncodedString(value.toString, charset) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala new file mode 100644 index 00000000..4296f753 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala @@ -0,0 +1,46 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time._ +import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException + +object TimestampEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + + val instant = value match { + case v : ReadableDateTime => v + case v : ReadableInstant => new DateTime(v.getMillis) + case v : LocalDateTime => v.toDateTime( DateTimeZone.UTC ) + case v : java.util.Date => new DateTime(v) + case v : java.sql.Timestamp => new DateTime(v) + case v : java.util.Calendar => new DateTime(v) + case _ => throw new DateEncoderNotAvailableException(value) + } + + buffer.writeByte(11) + buffer.writeShort(instant.getYear) + buffer.writeByte(instant.getMonthOfYear) + buffer.writeByte(instant.getDayOfMonth) + buffer.writeByte(instant.getHourOfDay) + buffer.writeByte(instant.getMinuteOfHour) + buffer.writeByte(instant.getSecondOfMinute) + buffer.writeInt(instant.getMillisOfDay) + + } +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala index 4b33dd14..f5c25517 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala @@ -23,6 +23,7 @@ class BinaryDecodersSpec extends Specification { "decoders" should { + } } From e620f1b478efd1b700ec4d7b384d1e7c2e130ba2 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Sun, 12 May 2013 10:39:56 -0300 Subject: [PATCH 082/357] fix #18 --- .../async/db/postgresql/PostgreSQLConnection.scala | 13 +++++++++---- .../db/postgresql/PostgreSQLConnectionSpec.scala | 10 ++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) 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 ee0a6c29..b565d42c 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 @@ -36,6 +36,7 @@ import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage import com.github.mauricio.async.db.postgresql.messages.backend.RowDescriptionMessage import com.github.mauricio.async.db.postgresql.messages.backend.ParameterStatusMessage +import com.github.mauricio.async.db.QueryResult object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] @@ -71,6 +72,8 @@ class PostgreSQLConnection private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None) private var currentQuery: Option[MutableResultSet[PostgreSQLColumnData]] = None private var currentPreparedStatement: Option[String] = None + + private var queryResult: Option[QueryResult] = None def isReadyForQuery: Boolean = this.readyForQuery @@ -170,11 +173,13 @@ class PostgreSQLConnection } override def onReadyForQuery() { + this.connectionFuture.trySuccess(this) + + queryResult.map(this.succeedQueryPromise) + + this.queryResult = None this.recentError = false this.readyForQuery = true - this.clearQueryPromise - - this.connectionFuture.trySuccess(this) } override def onError(m: ErrorMessage) { @@ -188,7 +193,7 @@ class PostgreSQLConnection override def onCommandComplete(m: CommandCompleteMessage) { this.currentPreparedStatement = None - this.succeedQueryPromise(new QueryResult(m.rowsAffected, m.statusMessage, this.currentQuery)) + queryResult = Some(new QueryResult(m.rowsAffected, m.statusMessage, this.currentQuery)) } override def onParameterStatus(m: ParameterStatusMessage) { 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 092ae9dd..b12582f5 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 @@ -344,6 +344,16 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { } + "execute multiple prepared statements" in { + withHandler { + handler => + executeDdl(handler, this.preparedStatementCreate) + for (i <- 0 until 500) + executePreparedStatement(handler, this.preparedStatementInsert) + ok + } + } + } } From 5a80c0393e08be1d54ab72c920e3795f35937667 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 12 May 2013 10:46:06 -0300 Subject: [PATCH 083/357] Separating different date formats in different pieces --- .../db/mysql/binary/BinaryRowEncoder.scala | 12 +++--- .../binary/encoder/CalendarEncoder.scala | 28 ++++++++++++++ .../db/mysql/binary/encoder/DateEncoder.scala | 6 +-- .../binary/encoder/DateTimeEncoder.scala | 37 +++++++++++++++++++ .../binary/encoder/JavaDateEncoder.scala | 27 ++++++++++++++ .../binary/encoder/LocalDateTimeEncoder.scala | 27 ++++++++++++++ .../encoder/ReadableInstantEncoder.scala | 27 ++++++++++++++ .../mysql/binary/encoder/SQLDateEncoder.scala | 28 ++++++++++++++ .../binary/encoder/TimestampEncoder.scala | 25 ++----------- 9 files changed, 184 insertions(+), 33 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index df4a253f..57395180 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -61,14 +61,14 @@ class BinaryRowEncoder( charset : Charset ) { case value : Long | v : java.lang.Long => LongEncoder case value : Float | v : java.lang.Float => FloatEncoder case value : Double | v : java.lang.Double => DoubleEncoder - case v : ReadableDateTime => TimestampEncoder - case v : ReadableInstant => TimestampEncoder - case v : LocalDateTime => TimestampEncoder - case v : java.util.Date => TimestampEncoder + case v : ReadableDateTime => DateTimeEncoder + case v : ReadableInstant => ReadableInstantEncoder + case v : LocalDateTime => LocalDateTimeEncoder + case v : java.util.Date => JavaDateEncoder case v : java.sql.Timestamp => TimestampEncoder - case v : java.util.Calendar => TimestampEncoder + case v : java.util.Calendar => CalendarEncoder case d : LocalDate => DateEncoder - case d : java.sql.Date => DateEncoder + case d : java.sql.Date => SQLDateEncoder } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala new file mode 100644 index 00000000..8f94e94d --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import java.util.Calendar +import org.joda.time.DateTime + +object CalendarEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val calendar = value.asInstanceOf[Calendar] + DateTimeEncoder.encode(new DateTime(calendar), buffer) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala index a7b0916c..4cc9515d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala @@ -22,11 +22,7 @@ import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException object DateEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { - val date = value match { - case d : LocalDate => d - case d : java.sql.Date => new LocalDate(d) - case _ => throw new DateEncoderNotAvailableException(value) - } + val date = value.asInstanceOf[LocalDate] buffer.writeByte(4) buffer.writeShort(date.getYear) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala new file mode 100644 index 00000000..7ad2f2c3 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala @@ -0,0 +1,37 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time._ +import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException + +object DateTimeEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val instant = value.asInstanceOf[ReadableDateTime] + + buffer.writeByte(11) + buffer.writeShort(instant.getYear) + buffer.writeByte(instant.getMonthOfYear) + buffer.writeByte(instant.getDayOfMonth) + buffer.writeByte(instant.getHourOfDay) + buffer.writeByte(instant.getMinuteOfHour) + buffer.writeByte(instant.getSecondOfMinute) + buffer.writeInt(instant.getMillisOfDay) + + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala new file mode 100644 index 00000000..9ff477a5 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala @@ -0,0 +1,27 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.DateTime + +object JavaDateEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val date = value.asInstanceOf[java.util.Date] + DateTimeEncoder.encode( new DateTime(date), buffer ) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala new file mode 100644 index 00000000..82191d90 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala @@ -0,0 +1,27 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.{DateTimeZone, LocalDateTime} + +object LocalDateTimeEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val date = value.asInstanceOf[LocalDateTime] + DateTimeEncoder.encode(date.toDateTime( DateTimeZone.UTC ), buffer) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala new file mode 100644 index 00000000..da1e43bb --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala @@ -0,0 +1,27 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.{DateTime, ReadableInstant} + +object ReadableInstantEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val date = value.asInstanceOf[ReadableInstant] + DateTimeEncoder.encode(new DateTime(date.getMillis), buffer) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala new file mode 100644 index 00000000..b404a486 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.LocalDate + +object SQLDateEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val date = value.asInstanceOf[java.sql.Date] + + DateEncoder.encode(new LocalDate(date), buffer) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala index 4296f753..dfe6bd05 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala @@ -17,30 +17,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer -import org.joda.time._ -import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException +import org.joda.time.DateTime object TimestampEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { - - val instant = value match { - case v : ReadableDateTime => v - case v : ReadableInstant => new DateTime(v.getMillis) - case v : LocalDateTime => v.toDateTime( DateTimeZone.UTC ) - case v : java.util.Date => new DateTime(v) - case v : java.sql.Timestamp => new DateTime(v) - case v : java.util.Calendar => new DateTime(v) - case _ => throw new DateEncoderNotAvailableException(value) - } - - buffer.writeByte(11) - buffer.writeShort(instant.getYear) - buffer.writeByte(instant.getMonthOfYear) - buffer.writeByte(instant.getDayOfMonth) - buffer.writeByte(instant.getHourOfDay) - buffer.writeByte(instant.getMinuteOfHour) - buffer.writeByte(instant.getSecondOfMinute) - buffer.writeInt(instant.getMillisOfDay) - + val date = value.asInstanceOf[java.sql.Timestamp] + DateTimeEncoder.encode(new DateTime(date), buffer) } } From 6e81e4adcb016cbd7b1e3ed36d5535d9cb6fa762 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 12 May 2013 17:11:51 -0300 Subject: [PATCH 084/357] Fixing bad match/case on MySQL binary encoder --- .../db/mysql/binary/BinaryRowEncoder.scala | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 57395180..3f6b1cfc 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -50,17 +50,23 @@ class BinaryRowEncoder( charset : Charset ) { private def encoderFor( v : Any ) : BinaryEncoder = { v match { - case value : String | - decimalJava : java.math.BigDecimal | - decimal : BigDecimal | - integerJava : java.math.BigInteger | - integer : BigInt => this.stringEncoder - case value : Byte | v : java.lang.Byte => ByteEncoder - case value : Short | v : java.lang.Short => ShortEncoder - case value : Int | v : java.lang.Integer => IntegerEncoder - case value : Long | v : java.lang.Long => LongEncoder - case value : Float | v : java.lang.Float => FloatEncoder - case value : Double | v : java.lang.Double => DoubleEncoder + case value : String => this.stringEncoder + case integer : BigInt => this.stringEncoder + case integerJava : java.math.BigInteger => this.stringEncoder + case decimal : BigDecimal => this.stringEncoder + case decimalJava : java.math.BigDecimal => this.stringEncoder + case value : Byte => ByteEncoder + case v : java.lang.Byte => ByteEncoder + case value : Short => ShortEncoder + case v : java.lang.Short => ShortEncoder + case value : Int => IntegerEncoder + case v : java.lang.Integer => IntegerEncoder + case value : Long => LongEncoder + case v : java.lang.Long => LongEncoder + case value : Float => FloatEncoder + case v : java.lang.Float => FloatEncoder + case value : Double => DoubleEncoder + case v : java.lang.Double => DoubleEncoder case v : ReadableDateTime => DateTimeEncoder case v : ReadableInstant => ReadableInstantEncoder case v : LocalDateTime => LocalDateTimeEncoder From d7504b4e813f83003b7037a1ae23a92276d75aae Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 12 May 2013 20:55:43 -0300 Subject: [PATCH 085/357] Started work for BYTEA support on PostgreSQL --- .../github/mauricio/async/db/util/Hex.java | 232 ++++++++++++++++++ .../async/db/column/TimeEncoderDecoder.scala | 2 +- .../mauricio/async/db/util/HexCodec.scala | 108 ++++++++ .../mauricio/async/db/util/Version.scala | 59 +++++ .../mauricio/async/db/util/HexCodecSpec.scala | 43 ++++ .../mauricio/async/db/util/VersionSpec.scala | 78 ++++++ .../db/mysql/binary/BinaryRowEncoder.scala | 4 +- .../db/postgresql/PostgreSQLConnection.scala | 18 +- .../codec/PostgreSQLConnectionHandler.scala | 2 +- .../column/ByteArrayEncoderDecoder.scala | 38 +++ .../db/postgresql/column/ColumnTypes.scala | 3 + .../PostgreSQLColumnEncoderRegistry.scala | 16 +- .../PreparedStatementEncoderHelper.scala | 4 +- ...ByteArrayFormatNotSupportedException.scala | 28 +++ 14 files changed, 613 insertions(+), 22 deletions(-) create mode 100644 db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/Version.scala create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/util/VersionSpec.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ByteArrayFormatNotSupportedException.scala diff --git a/db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java b/db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java new file mode 100644 index 00000000..11a8fc69 --- /dev/null +++ b/db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java @@ -0,0 +1,232 @@ +package com.github.mauricio.async.db.util; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +import org.jboss.netty.util.CharsetUtil; + +import java.nio.charset.Charset; + + +/** + * Converts hexadecimal Strings. The charset used for certain operation can be set. + * + * This class is thread-safe. + * + * @since 1.1 + * @version $Id$ + */ +public class Hex { + + public static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_LOWER = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_UPPER = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + /** + * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The + * returned array will be half the length of the passed array, as it takes two characters to represent any given + * byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param data + * An array of characters containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied char array. + * @throws IllegalArgumentException + * Thrown if an odd number or illegal of characters is supplied + */ + public static byte[] decodeHex(final char[] data) throws IllegalArgumentException { + + final int len = data.length; + + if ((len & 0x01) != 0) { + throw new IllegalArgumentException("Odd number of characters."); + } + + final byte[] out = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) { + int f = toDigit(data[j], j) << 4; + j++; + f = f | toDigit(data[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @return A char[] containing hexadecimal characters + */ + public static char[] encodeHex(final byte[] data) { + return encodeHex(data, true); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @param toLowerCase + * {@code true} converts to lowercase, {@code false} to uppercase + * @return A char[] containing hexadecimal characters + * @since 1.4 + */ + public static char[] encodeHex(final byte[] data, final boolean toLowerCase) { + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @param toDigits + * the output alphabet + * @return A char[] containing hexadecimal characters + * @since 1.4 + */ + protected static char[] encodeHex(final byte[] data, final char[] toDigits) { + final int l = data.length; + final char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + + /** + * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned + * String will be double the length of the passed array, as it takes two characters to represent any given byte. + * + * @param data + * a byte[] to convert to Hex characters + * @return A String containing hexadecimal characters + * @since 1.4 + */ + public static String encodeHexString(final byte[] data) { + return new String(encodeHex(data)); + } + + /** + * Converts a hexadecimal character to an integer. + * + * @param ch + * A character to convert to an integer digit + * @param index + * The index of the character in the source + * @return An integer + * @throws IllegalStateException + * Thrown if ch is an illegal hex character + */ + protected static int toDigit(final char ch, final int index) throws IllegalStateException { + final int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new IllegalStateException("Illegal hexadecimal character " + ch + " at index " + index); + } + return digit; + } + + private final Charset charset; + + /** + * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET} + */ + public Hex() { + // use default encoding + this.charset = DEFAULT_CHARSET; + } + + /** + * Creates a new codec with the given Charset. + * + * @param charset + * the charset. + * @since 1.7 + */ + public Hex(final Charset charset) { + this.charset = charset; + } + + /** + * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values. + * The returned array will be half the length of the passed array, as it takes two characters to represent any given + * byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param array + * An array of character bytes containing hexadecimal digits + * @return A byte array containing binary data decoded from the supplied byte array (representing characters). + * @throws IllegalStateException + * Thrown if an odd number of characters is supplied to this function + * @see #decodeHex(char[]) + */ + public byte[] decode(final byte[] array) throws IllegalStateException { + return decodeHex(new String(array, getCharset()).toCharArray()); + } + + /** + * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each + * byte in order. The returned array will be double the length of the passed array, as it takes two characters to + * represent any given byte. + *

+ * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by + * {@link #getCharset()}. + *

+ * + * @param array + * a byte[] to convert to Hex characters + * @return A byte[] containing the bytes of the hexadecimal characters + * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid. + * @see #encodeHex(byte[]) + */ + public byte[] encode(final byte[] array) { + return encodeHexString(array).getBytes(this.getCharset()); + } + + /** + * Gets the charset. + * + * @return the charset. + * @since 1.7 + */ + public Charset getCharset() { + return this.charset; + } + +} \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala index bcb3cafd..9a801775 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.column import org.joda.time.LocalTime -import org.joda.time.format.{DateTimeFormatterBuilder, DateTimeFormat} +import org.joda.time.format.DateTimeFormatterBuilder object TimeEncoderDecoder { val Instance = new TimeEncoderDecoder() diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala new file mode 100644 index 00000000..17395b25 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala @@ -0,0 +1,108 @@ +/* + * 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.util + +import java.nio.CharBuffer + +/** + * The code from this class was copied from the Hex class at commons-codec + */ + +object HexCodec { + + private final val Digits = Array[Char]('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F') + + private def toDigit( ch : Char, index : Int ) : Int = { + val digit = Character.digit( ch, 16 ) + + if ( digit == -1 ) { + throw new IllegalArgumentException("Illegal hexadecimal character " + ch + " at index " + index); + } + + return digit + } + + /** + * + * Turns a HEX based char sequence into a Byte array + * + * @param value + * @param start + * @return + */ + + def decode( value : CharSequence, start : Int = 0 ) : Array[Byte] = { + + val length = value.length - start + + if ( (length & 0x01) != 0 ) { + throw new IllegalArgumentException("Odd number of characters. A hex encoded byte array has to be even.") + } + + val out = new Array[Byte](length >> 1) + + var i = 0 + var j = start + + while ( j < length ) { + var f = toDigit(value.charAt(j), j) << 4 + j += 1 + f = f | toDigit(value.charAt(j), j) + j += 1 + out(i) = (f & 0xff).asInstanceOf[Byte] + i += 1 + } + + out + } + + /** + * + * Encodes a byte array into a String encoded with Hex values. + * + * @param bytes + * @param prefix + * @return + */ + + def encode( bytes : Array[Byte], prefix : Array[Char] = Array.empty ) : String = { + val length = (bytes.length * 2) + prefix.length + val chars = new Array[Char](length) + + if ( prefix.length != 0 ) { + var x = 0 + while ( x < prefix.length ) { + chars(x) = prefix(x) + } + } + + val dataLength = bytes.length + var j = prefix.length + var i = 0 + + while ( i < dataLength ) { + chars(j) = Digits(( 0xF0 & bytes(i)) >>> 4 ) + j += 1 + chars(j) = Digits( 0x0F & bytes(i) ) + j += 1 + i += 1 + } + + new String(chars) + } + +} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Version.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Version.scala new file mode 100644 index 00000000..1d991324 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Version.scala @@ -0,0 +1,59 @@ +/* + * 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.util + +import scala.util._ +import scala.util.Success + +object Version { + + private def tryParse( index : Int, pieces : Array[String] ) : Int = { + + Try { + pieces(index).toInt + } match { + case Success(value) => value + case Failure(e) => 0 + } + + } + + def apply( version : String ) : Version = { + val pieces = version.split('.') + new Version( tryParse(0, pieces), tryParse(1, pieces), tryParse(2, pieces) ) + } + +} + +case class Version( major : Int, minor : Int, maintenance : Int ) extends Ordered[Version] { + override def compare( y: Version): Int = { + + if ( this == y ) { + return 0 + } + + if ( this.major != y.major ) { + return this.major.compare(y.major) + } + + if ( this.minor != y.minor ) { + return this.minor.compare(y.minor) + } + + this.maintenance.compare(y.maintenance) + } +} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala new file mode 100644 index 00000000..d5ef0b75 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala @@ -0,0 +1,43 @@ +/* + * 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.util + +import org.specs2.mutable.Specification + +class HexCodecSpec extends Specification { + + val sampleArray = Array[Byte](83, 97, 121, 32, 72, 101, 108, 108, 111, 32, 116, 111, 32, 77, 121, 32, 76, 105, 116, 116, 108, 101, 32, 70, 114, 105, 101, 110, 100) + val sampleHex = "5361792048656c6c6f20746f204d79204c6974746c6520467269656e64".toUpperCase + + "codec" should { + + "correctly generate an array of bytes" in { + + val bytes = HexCodec.decode( "5361792048656c6c6f20746f204d79204c6974746c6520467269656e64" ) + bytes === sampleArray + + } + + "correctly generate a string from an array of bytes" in { + + HexCodec.encode(sampleArray) === sampleHex + + } + + } + +} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/VersionSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/VersionSpec.scala new file mode 100644 index 00000000..0c4e7972 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/VersionSpec.scala @@ -0,0 +1,78 @@ +/* + * 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.util + +import org.specs2.mutable.Specification + +class VersionSpec extends Specification { + + "version" should { + + "correctly parse versions" in { + val version = Version("9.1.4") + version.major === 9 + version.minor === 1 + version.maintenance === 4 + } + + "correctly parse with missing fields" in { + val version = Version("8.7") + version.major === 8 + version.minor === 7 + version.maintenance === 0 + } + + "correctly compare between major different versions" in { + + val version1 = Version("8.2.0") + val version2 = Version("9.2.0") + + version2 must beGreaterThan(version1) + + } + + "correctly compare between major different versions" in { + + val version1 = Version("8.2.0") + val version2 = Version("8.2.0") + + version2 === version1 + + } + + + "correctly compare between major different versions" in { + + val version1 = Version("8.2.8") + val version2 = Version("8.2.87") + + version2 must beGreaterThan( version1 ) + + } + + "correctly compare two different versions" in { + + val version1 = Version("9.1.2") + val version2 = Version("9.2.0") + + version2 must beGreaterThan(version1) + + } + + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 3f6b1cfc..16f44b0b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -70,11 +70,11 @@ class BinaryRowEncoder( charset : Charset ) { case v : ReadableDateTime => DateTimeEncoder case v : ReadableInstant => ReadableInstantEncoder case v : LocalDateTime => LocalDateTimeEncoder - case v : java.util.Date => JavaDateEncoder case v : java.sql.Timestamp => TimestampEncoder + case d : java.sql.Date => SQLDateEncoder case v : java.util.Calendar => CalendarEncoder + case v : java.util.Date => JavaDateEncoder case d : LocalDate => DateEncoder - case d : java.sql.Date => SQLDateEncoder } } 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 b565d42c..fd626a5c 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 @@ -16,31 +16,23 @@ 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.general.MutableResultSet import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler} import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, PostgreSQLColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ -import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} -import java.util.concurrent.ConcurrentHashMap +import com.github.mauricio.async.db.util.{Version, Log} +import com.github.mauricio.async.db.{Configuration, Connection} import java.util.concurrent.atomic._ import messages.backend._ import messages.frontend._ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some -import scala.collection.JavaConversions._ import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.Some -import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage -import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage -import com.github.mauricio.async.db.postgresql.messages.backend.RowDescriptionMessage -import com.github.mauricio.async.db.postgresql.messages.backend.ParameterStatusMessage -import com.github.mauricio.async.db.QueryResult object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] - val Name = "Netty-PostgreSQL-driver-0.1.2" InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) val Counter = new AtomicLong() } @@ -72,6 +64,7 @@ class PostgreSQLConnection private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None) private var currentQuery: Option[MutableResultSet[PostgreSQLColumnData]] = None private var currentPreparedStatement: Option[String] = None + private var version = Version(0,0,0) private var queryResult: Option[QueryResult] = None @@ -198,6 +191,9 @@ class PostgreSQLConnection override def onParameterStatus(m: ParameterStatusMessage) { this.parameterStatus.put(m.key, m.value) + if ( m.key == "server_version" ) { + this.version = Version(m.value) + } } override def onDataRow(m: DataRowMessage) { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index 8602e552..de31af4b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -51,7 +51,7 @@ class PostgreSQLConnectionHandler private val properties = List( "user" -> configuration.username, "database" -> configuration.database, - "application_name" -> "Netty-PostgreSQL-driver-0.1.0", + "application_name" -> "Netty-PostgreSQL-driver-0.1.2-SNAPSHOT", "client_encoding" -> configuration.charset.name(), "DateStyle" -> "ISO", "extra_float_digits" -> "2") diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala new file mode 100644 index 00000000..64a93564 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala @@ -0,0 +1,38 @@ +/* + * 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.column + +import com.github.mauricio.async.db.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.postgresql.exceptions.ByteArrayFormatNotSupportedException + +class ByteArrayEncoderDecoder extends ColumnEncoderDecoder { + + var useHex = true + + override def decode(value: String): Any = { + + if (value.startsWith("\\\\x")) { + + } else { + throw new ByteArrayFormatNotSupportedException() + } + + } + + override def encode(value: Any): String = ??? + +} 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 2478ca56..2a7e8d43 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 @@ -53,6 +53,9 @@ object ColumnTypes { final val Boolean = 16 final val BooleanArray = 1000 + final val ByteA = 17 + final val ByteA_Array = 1001 + final val OIDArray = 1028 final val MoneyArray = 791 final val NameArray = 1003 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 440dd535..59801d36 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 @@ -20,6 +20,8 @@ import com.github.mauricio.async.db.column._ import org.joda.time._ import scala.Some import scala.collection.JavaConversions._ +import org.jboss.netty.util.CharsetUtil +import java.nio.charset.Charset object PostgreSQLColumnEncoderRegistry { val Instance = new PostgreSQLColumnEncoderRegistry() @@ -27,7 +29,9 @@ object PostgreSQLColumnEncoderRegistry { class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { - private val classesSequence: List[(Class[_], (ColumnEncoderDecoder, Int))] = List( + private final val byteArrayEncoder = new ByteArrayEncoderDecoder() + + private val classesSequence_ : List[(Class[_], (ColumnEncoder, Int))] = List( classOf[Int] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), classOf[java.lang.Integer] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), @@ -50,9 +54,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[java.math.BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), classOf[LocalDate] -> ( DateEncoderDecoder -> ColumnTypes.Date ), - classOf[LocalTime] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time), classOf[DateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), - classOf[ReadablePartial] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time), classOf[ReadableDateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[ReadableInstant] -> (DateEncoderDecoder -> ColumnTypes.Date), @@ -61,9 +63,15 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[java.sql.Time] -> ( TimeEncoderDecoder.Instance -> ColumnTypes.Time ), classOf[java.sql.Timestamp] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.util.Calendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), - classOf[java.util.GregorianCalendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone) + classOf[java.util.GregorianCalendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), + classOf[Array[Byte]] -> ( this.byteArrayEncoder -> ColumnTypes.ByteA ), + classOf[Byte] -> ( this.byteArrayEncoder -> ColumnTypes.ByteA ) ) + private final val classesSequence = (classOf[LocalTime] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time)) :: + (classOf[ReadablePartial] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time)) :: + classesSequence_ + private final val classes = classesSequence.toMap def encode(value: Any): String = { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala index 88acb93a..9a3404e3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -50,9 +50,7 @@ trait PreparedStatementEncoderHelper { if (value == null) { bindBuffer.writeInt(-1) } else { - val encoded = encoder.encode(value).getBytes(charset) - bindBuffer.writeInt(encoded.length) - bindBuffer.writeBytes(encoded) + encoder.encode(value, bindBuffer) } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ByteArrayFormatNotSupportedException.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ByteArrayFormatNotSupportedException.scala new file mode 100644 index 00000000..457c9869 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ByteArrayFormatNotSupportedException.scala @@ -0,0 +1,28 @@ +/* + * 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.exceptions + +import com.github.mauricio.async.db.exceptions.DatabaseException + +object ByteArrayFormatNotSupportedException { + + final val Message = """The bytea 'escape' format is not yet supported, you need to use a PG version that uses the 'hex' format (version 9 and onwards)""" + +} + +class ByteArrayFormatNotSupportedException + extends DatabaseException( ByteArrayFormatNotSupportedException.Message ) \ No newline at end of file From bd10f49c28000b8178546af194dbbe6cf34a7e9b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 May 2013 12:08:06 -0300 Subject: [PATCH 086/357] Implemented byte arrays support for PG 9 and onward, support for < 9 not there yet - fixes ssue #5 --- .../github/mauricio/async/db/util/Hex.java | 232 ------------------ .../mauricio/async/db/util/BitMap.scala | 5 + .../mauricio/async/db/util/HexCodec.scala | 6 +- .../mauricio/async/db/util/HexCodecSpec.scala | 24 +- .../db/mysql/binary/BinaryRowEncoder.scala | 7 +- .../column/ByteArrayEncoderDecoder.scala | 17 +- .../PostgreSQLColumnDecoderRegistry.scala | 1 + .../PostgreSQLColumnEncoderRegistry.scala | 5 +- .../column/SingleByteEncoderDecoder.scala | 32 +++ .../PreparedStatementEncoderHelper.scala | 12 +- .../postgresql/PostgreSQLConnectionSpec.scala | 70 +++++- 11 files changed, 152 insertions(+), 259 deletions(-) delete mode 100644 db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/SingleByteEncoderDecoder.scala diff --git a/db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java b/db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java deleted file mode 100644 index 11a8fc69..00000000 --- a/db-async-common/src/main/java/com/github/mauricio/async/db/util/Hex.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.github.mauricio.async.db.util; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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. - */ - -import org.jboss.netty.util.CharsetUtil; - -import java.nio.charset.Charset; - - -/** - * Converts hexadecimal Strings. The charset used for certain operation can be set. - * - * This class is thread-safe. - * - * @since 1.1 - * @version $Id$ - */ -public class Hex { - - public static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; - - /** - * Used to build output as Hex - */ - private static final char[] DIGITS_LOWER = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - /** - * Used to build output as Hex - */ - private static final char[] DIGITS_UPPER = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - /** - * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The - * returned array will be half the length of the passed array, as it takes two characters to represent any given - * byte. An exception is thrown if the passed char array has an odd number of elements. - * - * @param data - * An array of characters containing hexadecimal digits - * @return A byte array containing binary data decoded from the supplied char array. - * @throws IllegalArgumentException - * Thrown if an odd number or illegal of characters is supplied - */ - public static byte[] decodeHex(final char[] data) throws IllegalArgumentException { - - final int len = data.length; - - if ((len & 0x01) != 0) { - throw new IllegalArgumentException("Odd number of characters."); - } - - final byte[] out = new byte[len >> 1]; - - // two characters form the hex value. - for (int i = 0, j = 0; j < len; i++) { - int f = toDigit(data[j], j) << 4; - j++; - f = f | toDigit(data[j], j); - j++; - out[i] = (byte) (f & 0xFF); - } - - return out; - } - - /** - * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. - * The returned array will be double the length of the passed array, as it takes two characters to represent any - * given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @return A char[] containing hexadecimal characters - */ - public static char[] encodeHex(final byte[] data) { - return encodeHex(data, true); - } - - /** - * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. - * The returned array will be double the length of the passed array, as it takes two characters to represent any - * given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @param toLowerCase - * {@code true} converts to lowercase, {@code false} to uppercase - * @return A char[] containing hexadecimal characters - * @since 1.4 - */ - public static char[] encodeHex(final byte[] data, final boolean toLowerCase) { - return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); - } - - /** - * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. - * The returned array will be double the length of the passed array, as it takes two characters to represent any - * given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @param toDigits - * the output alphabet - * @return A char[] containing hexadecimal characters - * @since 1.4 - */ - protected static char[] encodeHex(final byte[] data, final char[] toDigits) { - final int l = data.length; - final char[] out = new char[l << 1]; - // two characters form the hex value. - for (int i = 0, j = 0; i < l; i++) { - out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; - out[j++] = toDigits[0x0F & data[i]]; - } - return out; - } - - /** - * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned - * String will be double the length of the passed array, as it takes two characters to represent any given byte. - * - * @param data - * a byte[] to convert to Hex characters - * @return A String containing hexadecimal characters - * @since 1.4 - */ - public static String encodeHexString(final byte[] data) { - return new String(encodeHex(data)); - } - - /** - * Converts a hexadecimal character to an integer. - * - * @param ch - * A character to convert to an integer digit - * @param index - * The index of the character in the source - * @return An integer - * @throws IllegalStateException - * Thrown if ch is an illegal hex character - */ - protected static int toDigit(final char ch, final int index) throws IllegalStateException { - final int digit = Character.digit(ch, 16); - if (digit == -1) { - throw new IllegalStateException("Illegal hexadecimal character " + ch + " at index " + index); - } - return digit; - } - - private final Charset charset; - - /** - * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET} - */ - public Hex() { - // use default encoding - this.charset = DEFAULT_CHARSET; - } - - /** - * Creates a new codec with the given Charset. - * - * @param charset - * the charset. - * @since 1.7 - */ - public Hex(final Charset charset) { - this.charset = charset; - } - - /** - * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values. - * The returned array will be half the length of the passed array, as it takes two characters to represent any given - * byte. An exception is thrown if the passed char array has an odd number of elements. - * - * @param array - * An array of character bytes containing hexadecimal digits - * @return A byte array containing binary data decoded from the supplied byte array (representing characters). - * @throws IllegalStateException - * Thrown if an odd number of characters is supplied to this function - * @see #decodeHex(char[]) - */ - public byte[] decode(final byte[] array) throws IllegalStateException { - return decodeHex(new String(array, getCharset()).toCharArray()); - } - - /** - * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each - * byte in order. The returned array will be double the length of the passed array, as it takes two characters to - * represent any given byte. - *

- * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by - * {@link #getCharset()}. - *

- * - * @param array - * a byte[] to convert to Hex characters - * @return A byte[] containing the bytes of the hexadecimal characters - * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid. - * @see #encodeHex(byte[]) - */ - public byte[] encode(final byte[] array) { - return encodeHexString(array).getBytes(this.getCharset()); - } - - /** - * Gets the charset. - * - * @return the charset. - * @since 1.7 - */ - public Charset getCharset() { - return this.charset; - } - -} \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala index 1e7114f3..3778c5b9 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala @@ -111,4 +111,9 @@ class BitMap(bytes: Array[Byte]) extends IndexedSeq[(Int, Boolean)] { def apply(idx: Int): (Int, Boolean) = (idx, this.isSet(idx)) override def toString: String = this.map(entry => if (entry._2) '1' else '0').mkString("") + + def write( buffer : ChannelBuffer ) { + buffer.writeBytes(bytes) + } + } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala index 17395b25..5b74d27f 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/HexCodec.scala @@ -16,8 +16,6 @@ package com.github.mauricio.async.db.util -import java.nio.CharBuffer - /** * The code from this class was copied from the Hex class at commons-codec */ @@ -48,6 +46,7 @@ object HexCodec { def decode( value : CharSequence, start : Int = 0 ) : Array[Byte] = { val length = value.length - start + val end = value.length() if ( (length & 0x01) != 0 ) { throw new IllegalArgumentException("Odd number of characters. A hex encoded byte array has to be even.") @@ -58,7 +57,7 @@ object HexCodec { var i = 0 var j = start - while ( j < length ) { + while ( j < end ) { var f = toDigit(value.charAt(j), j) << 4 j += 1 f = f | toDigit(value.charAt(j), j) @@ -87,6 +86,7 @@ object HexCodec { var x = 0 while ( x < prefix.length ) { chars(x) = prefix(x) + x += 1 } } diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala index d5ef0b75..27aa9777 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/HexCodecSpec.scala @@ -18,10 +18,18 @@ package com.github.mauricio.async.db.util import org.specs2.mutable.Specification +object HexCodecSpec { + + final val sampleArray = Array[Byte](83, 97, 121, 32, 72, 101, 108, 108, 111, 32, 116, 111, 32, 77, 121, 32, 76, 105, 116, 116, 108, 101, 32, 70, 114, 105, 101, 110, 100) + final val sampleHex = "5361792048656c6c6f20746f204d79204c6974746c6520467269656e64".toUpperCase + final val HexStart = "\\x" + final val HexStartChars = HexStart.toCharArray + +} + class HexCodecSpec extends Specification { - val sampleArray = Array[Byte](83, 97, 121, 32, 72, 101, 108, 108, 111, 32, 116, 111, 32, 77, 121, 32, 76, 105, 116, 116, 108, 101, 32, 70, 114, 105, 101, 110, 100) - val sampleHex = "5361792048656c6c6f20746f204d79204c6974746c6520467269656e64".toUpperCase + import HexCodecSpec._ "codec" should { @@ -33,9 +41,19 @@ class HexCodecSpec extends Specification { } "correctly generate a string from an array of bytes" in { - HexCodec.encode(sampleArray) === sampleHex + } + + "correctly generate a byte array from the PG output" in { + + val input = "\\x53617920" + val bytes = Array[Byte](83, 97, 121, 32) + HexCodec.decode(input, 2) === bytes + + } + "correctly encode to hex using the PostgreSQL format" in { + HexCodec.encode(sampleArray, HexStartChars) === (HexStart + sampleHex) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 16f44b0b..736731a4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.mysql.binary import java.nio.charset.Charset import com.github.mauricio.async.db.mysql.binary.encoder._ -import org.jboss.netty.buffer.ChannelBuffer +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util.{BitMap, ChannelUtils} import org.joda.time._ @@ -28,6 +28,7 @@ class BinaryRowEncoder( charset : Charset ) { def encode( values : Seq[Any] ) : ChannelBuffer = { + val bitMapBuffer = ChannelUtils.packetBuffer(values.length) val buffer = ChannelUtils.packetBuffer() val bitMap = BitMap.forSize(values.length) @@ -44,7 +45,9 @@ class BinaryRowEncoder( charset : Charset ) { index += 1 } - buffer + bitMap.write(bitMapBuffer) + + ChannelBuffers.wrappedBuffer( bitMapBuffer, buffer ) } private def encoderFor( v : Any ) : BinaryEncoder = { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala index 64a93564..bfaed46e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala @@ -18,21 +18,26 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.column.ColumnEncoderDecoder import com.github.mauricio.async.db.postgresql.exceptions.ByteArrayFormatNotSupportedException +import com.github.mauricio.async.db.util.{Log, HexCodec} -class ByteArrayEncoderDecoder extends ColumnEncoderDecoder { +object ByteArrayEncoderDecoder extends ColumnEncoderDecoder { - var useHex = true + final val log = Log.getByName(this.getClass.getName) + final val HexStart = "\\x" + final val HexStartChars = HexStart.toCharArray - override def decode(value: String): Any = { - - if (value.startsWith("\\\\x")) { + override def decode(value: String): Array[Byte] = { + if (value.startsWith(HexStart)) { + HexCodec.decode(value, 2) } else { throw new ByteArrayFormatNotSupportedException() } } - override def encode(value: Any): String = ??? + override def encode(value: Any): String = { + HexCodec.encode(value.asInstanceOf[Array[Byte]], HexStartChars) + } } 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 e534eff3..ac0b9511 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 @@ -82,6 +82,7 @@ class PostgreSQLColumnDecoderRegistry extends ColumnDecoderRegistry { case NameArray => new ArrayDecoder(StringEncoderDecoder) case UUIDArray => new ArrayDecoder(StringEncoderDecoder) case XMLArray => new ArrayDecoder(StringEncoderDecoder) + case ByteA => ByteArrayEncoderDecoder 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 59801d36..0d9db82e 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 @@ -29,8 +29,6 @@ object PostgreSQLColumnEncoderRegistry { class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { - private final val byteArrayEncoder = new ByteArrayEncoderDecoder() - private val classesSequence_ : List[(Class[_], (ColumnEncoder, Int))] = List( classOf[Int] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), classOf[java.lang.Integer] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), @@ -64,8 +62,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[java.sql.Timestamp] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.util.Calendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.util.GregorianCalendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), - classOf[Array[Byte]] -> ( this.byteArrayEncoder -> ColumnTypes.ByteA ), - classOf[Byte] -> ( this.byteArrayEncoder -> ColumnTypes.ByteA ) + classOf[Array[Byte]] -> ( ByteArrayEncoderDecoder -> ColumnTypes.ByteA ) ) private final val classesSequence = (classOf[LocalTime] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time)) :: diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/SingleByteEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/SingleByteEncoderDecoder.scala new file mode 100644 index 00000000..c111be00 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/SingleByteEncoderDecoder.scala @@ -0,0 +1,32 @@ +/* + * 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.column + +import com.github.mauricio.async.db.column.ColumnEncoderDecoder + +object SingleByteEncoderDecoder extends ColumnEncoderDecoder { + + override def encode(value: Any): String = { + val byte = value.asInstanceOf[Byte] + ByteArrayEncoderDecoder.encode(Array(byte)) + } + + override def decode(value: String): Any = { + ByteArrayEncoderDecoder.decode(value)(0) + } + +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala index 9a3404e3..ede0b165 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -18,12 +18,18 @@ package com.github.mauricio.async.db.postgresql.encoders import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.{Log, ChannelUtils} import com.github.mauricio.async.db.column.ColumnEncoderRegistry import java.nio.charset.Charset +object PreparedStatementEncoderHelper { + final val log = Log.get[PreparedStatementEncoderHelper] +} + trait PreparedStatementEncoderHelper { + import PreparedStatementEncoderHelper.log + def writeExecutePortal( statementIdBytes: Array[Byte], values: Seq[Any], @@ -50,7 +56,9 @@ trait PreparedStatementEncoderHelper { if (value == null) { bindBuffer.writeInt(-1) } else { - encoder.encode(value, bindBuffer) + val content = encoder.encode(value).getBytes(charset) + bindBuffer.writeInt(content.length) + bindBuffer.writeBytes( content ) } } 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 b12582f5..edd9b7b5 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 @@ -26,9 +26,17 @@ import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +import org.jboss.netty.buffer.ChannelBuffers +import com.github.mauricio.async.db.util.{Log, HexCodecSpec} + +object PostgreSQLConnectionSpec { + val log = Log.get[PostgreSQLConnectionSpec] +} class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { + import PostgreSQLConnectionSpec.log + val create = """create temp table type_test_table ( bigserial_column bigserial not null, smallint_column smallint not null, @@ -147,9 +155,9 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { } } - + "select rows that has duplicate column names" in { - + withHandler { handler => val result = executeQuery(handler, "SELECT 1 COL, 2 COL") @@ -160,7 +168,7 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { row(1) === 2 } - + } "execute a prepared statement" in { @@ -343,17 +351,65 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { } must throwA[QueryMustNotBeNullOrEmptyException] } - + "execute multiple prepared statements" in { withHandler { handler => executeDdl(handler, this.preparedStatementCreate) - for (i <- 0 until 500) + for (i <- 0 until 1000) executePreparedStatement(handler, this.preparedStatementInsert) ok - } } - + } + + "load data from a bytea column" in { + + val create = """create temp table file_samples ( + id bigserial not null, + content bytea not null, + constraint bigserial_column_pkey primary key (id) + )""" + + val insert = "insert into file_samples (content) values ( E'\\\\x5361792048656c6c6f20746f204d79204c6974746c6520467269656e64' ) " + val select = "select * from file_samples" + + withHandler { + handler => + executeDdl(handler, create) + executeQuery(handler, insert) + val rows = executeQuery(handler, select).rows.get + + rows(0)("content").asInstanceOf[Array[Byte]] === HexCodecSpec.sampleArray + + } + + } + + "send data to a bytea column" in { + + val create = """create temp table file_samples ( + id bigserial not null, + content bytea not null, + constraint bigserial_column_pkey primary key (id) + )""" + + val insert = "insert into file_samples (content) values ( ? ) " + val select = "select * from file_samples" + + withHandler { + handler => + + executeDdl(handler, create) + log.debug("executed create") + executePreparedStatement(handler, insert, Array( HexCodecSpec.sampleArray )) + log.debug("executed prepared statement") + val rows = executeQuery(handler, select).rows.get + + rows(0)("content").asInstanceOf[Array[Byte]] === HexCodecSpec.sampleArray + } + + } + } } From c08eb3d4abac33b942ef7905231c311b2f74b52c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 May 2013 12:22:25 -0300 Subject: [PATCH 087/357] Fixing compilation issue --- .../db/postgresql/PostgreSQLConnectionSpec.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 edd9b7b5..ffebc1c2 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 @@ -18,16 +18,15 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.column.{TimestampEncoderDecoder, TimeEncoderDecoder, DateEncoderDecoder} import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException -import com.github.mauricio.async.db.postgresql.exceptions.{InsufficientParametersException, QueryMustNotBeNullOrEmptyException, GenericDatabaseException} +import com.github.mauricio.async.db.postgresql.exceptions.{QueryMustNotBeNullOrEmptyException, GenericDatabaseException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} +import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ -import org.jboss.netty.buffer.ChannelBuffers -import com.github.mauricio.async.db.util.{Log, HexCodecSpec} object PostgreSQLConnectionSpec { val log = Log.get[PostgreSQLConnectionSpec] @@ -37,6 +36,8 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { import PostgreSQLConnectionSpec.log + final val sampleArray = Array[Byte](83, 97, 121, 32, 72, 101, 108, 108, 111, 32, 116, 111, 32, 77, 121, 32, 76, 105, 116, 116, 108, 101, 32, 70, 114, 105, 101, 110, 100) + val create = """create temp table type_test_table ( bigserial_column bigserial not null, smallint_column smallint not null, @@ -379,7 +380,7 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { executeQuery(handler, insert) val rows = executeQuery(handler, select).rows.get - rows(0)("content").asInstanceOf[Array[Byte]] === HexCodecSpec.sampleArray + rows(0)("content").asInstanceOf[Array[Byte]] === sampleArray } @@ -401,11 +402,11 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { executeDdl(handler, create) log.debug("executed create") - executePreparedStatement(handler, insert, Array( HexCodecSpec.sampleArray )) + executePreparedStatement(handler, insert, Array( sampleArray )) log.debug("executed prepared statement") val rows = executeQuery(handler, select).rows.get - rows(0)("content").asInstanceOf[Array[Byte]] === HexCodecSpec.sampleArray + rows(0)("content").asInstanceOf[Array[Byte]] === sampleArray } } From 6ce4e76fa449dd7a12a986facebd85a42d3e8ec3 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 May 2013 16:08:38 -0300 Subject: [PATCH 088/357] Blow up if message size is negative --- .../db}/exceptions/NegativeMessageSizeException.scala | 4 +--- .../async/db/mysql/codec/MySQLFrameDecoder.scala | 10 +++++++++- .../async/db/postgresql/codec/MessageDecoder.scala | 3 ++- .../async/db/postgresql/MessageDecoderSpec.scala | 3 ++- 4 files changed, 14 insertions(+), 6 deletions(-) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/exceptions/NegativeMessageSizeException.scala (86%) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/NegativeMessageSizeException.scala similarity index 86% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/NegativeMessageSizeException.scala index 09123d33..35d09a1f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/NegativeMessageSizeException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/NegativeMessageSizeException.scala @@ -14,9 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.exceptions - -import com.github.mauricio.async.db.exceptions.DatabaseException +package com.github.mauricio.async.db.exceptions class NegativeMessageSizeException( code : Byte, size : Int ) extends DatabaseException( "Message of type %d had negative size %s".format(code, size) ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index d1eebfd2..6df37d71 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.mysql.codec -import com.github.mauricio.async.db.exceptions.{BufferNotFullyConsumedException, ParserNotAvailableException} +import com.github.mauricio.async.db.exceptions._ import com.github.mauricio.async.db.mysql.decoder._ import com.github.mauricio.async.db.mysql.message.server.{BinaryRowMessage, ColumnProcessingFinishedMessage, PreparedStatementPrepareResponse, ServerMessage} import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt @@ -28,6 +28,9 @@ import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.frame.FrameDecoder import com.github.mauricio.async.db.mysql.message.client.PreparedStatementPrepareMessage import com.github.mauricio.async.db.mysql.MySQLHelper +import com.github.mauricio.async.db.mysql.message.server.ColumnProcessingFinishedMessage +import com.github.mauricio.async.db.mysql.message.server.PreparedStatementPrepareResponse +import com.github.mauricio.async.db.mysql.message.server.BinaryRowMessage object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] @@ -66,12 +69,17 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { buffer.markReaderIndex() val size = read3BytesInt(buffer) + val sequence = buffer.readUnsignedByte() if (buffer.readableBytes() >= size) { val messageType = buffer.getByte(buffer.readerIndex()) + if ( size < 0 ) { + throw new NegativeMessageSizeException(messageType, size) + } + val slice = buffer.readSlice(size) //val dump = MySQLHelper.dumpAsHex(slice, slice.readableBytes()) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala index e14f54d8..4bc82ff8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.postgresql.codec -import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} +import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException} import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParsersRegistry} import com.github.mauricio.async.db.util.Log @@ -24,6 +24,7 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{ChannelHandlerContext, Channel} import org.jboss.netty.handler.codec.frame.FrameDecoder +import com.github.mauricio.async.db.exceptions.NegativeMessageSizeException object MessageDecoder { val log = Log.get[MessageDecoder] diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index 86f69da3..3773c153 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -17,11 +17,12 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.postgresql.codec.MessageDecoder -import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException, NegativeMessageSizeException} +import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException} import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ErrorMessage} import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification +import com.github.mauricio.async.db.exceptions.NegativeMessageSizeException class MessageDecoderSpec extends Specification { From 248639b53f2ea03cf13297c16eb45fe40ded72f2 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 May 2013 23:23:40 -0300 Subject: [PATCH 089/357] More tests --- .../async/db/util/ChannelWrapper.scala | 4 + .../db/mysql/message/server/OkMessage.scala | 7 +- .../mysql/codec/MySQLMessageDecoderSpec.scala | 111 ++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index 678f4092..38d8381b 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -104,4 +104,8 @@ class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { buffer.writeBytes(bytes) } + def writePacketLength( sequence : Int = 0 ) { + ChannelUtils.writePacketLength(buffer, sequence ) + } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala index 911d0631..97dc2c9b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/OkMessage.scala @@ -16,5 +16,10 @@ package com.github.mauricio.async.db.mysql.message.server -case class OkMessage( affectedRows : Long, lastInsertId : Long, statusFlags : Int, warnings : Int, message : String ) +case class OkMessage( + affectedRows : Long, + lastInsertId : Long, + statusFlags : Int, + warnings : Int, + message : String ) extends ServerMessage( ServerMessage.Ok ) \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala new file mode 100644 index 00000000..6d1c9aee --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala @@ -0,0 +1,111 @@ +/* + * 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.mysql.codec + +import com.github.mauricio.async.db.mysql.message.server._ +import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import org.jboss.netty.handler.codec.embedder.DecoderEmbedder +import org.jboss.netty.util.CharsetUtil +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.mysql.message.server.OkMessage + +class MySQLMessageDecoderSpec extends Specification { + + final val charset = CharsetUtil.UTF_8 + + "decoder" should { + + "decode an OK message correctly" in { + + /* +1 [00] the OK header +lenenc-int affected rows +lenenc-int last-insert-id + if capabilities & CLIENT_PROTOCOL_41 { +2 status_flags +2 warnings + } elseif capabilities & CLIENT_TRANSACTIONS { +2 status_flags + } +string[EOF] info + */ + + + val buffer = ChannelUtils.packetBuffer() + buffer.writeByte(0) + buffer.writeLength(10) + buffer.writeLength(15) + buffer.writeShort(5) + buffer.writeShort(6) + buffer.writeBytes( "this is a test".getBytes(charset) ) + buffer.writePacketLength() + + val decoder = this.createPipeline() + + decoder.offer(buffer) + + val ok = decoder.peek().asInstanceOf[OkMessage] + ok.affectedRows === 10 + ok.lastInsertId === 15 + ok.message === "this is a test" + ok.statusFlags === 5 + ok.warnings === 6 + } + + "decode an error message" in { + + /* +1 [ff] the ERR header +2 error code + if capabilities & CLIENT_PROTOCOL_41 { +string[1] '#' the sql-state marker +string[5] sql-state + } +string[EOF] error-message + */ + + val content = "this is the error message" + + val buffer = ChannelUtils.packetBuffer() + buffer.writeByte(0xff) + buffer.writeShort(27) + buffer.writeByte('H') + buffer.writeBytes( "ZAWAY".getBytes(charset) ) + buffer.writeBytes(content.getBytes(charset)) + buffer.writePacketLength() + + val decoder = createPipeline() + + decoder.offer(buffer) + + val error = decoder.peek().asInstanceOf[ErrorMessage] + + error.errorCode === 27 + error.errorMessage === content + error.sqlState === "HZAWAY" + + + } + + } + + def createPipeline() : DecoderEmbedder[ServerMessage] = { + new DecoderEmbedder[ServerMessage](new MySQLFrameDecoder(charset)) + } + +} From 9497ccfaa5166de92cd04ec86842ff0ced65da79 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 May 2013 23:36:26 -0300 Subject: [PATCH 090/357] Trying out travis-ci support --- .travis.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4f5764d3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: scala +scala: + - 2.10.1 +jdk: + - oraclejdk7 + - openjdk7 + - openjdk6 +services: + - postgresql + - mysql +before_script: + - mysql -u root -e 'create database mysql_async_tests;' + - psql -c 'create database netty_driver_test;' -U postgres \ No newline at end of file From 22b2cea11abf9277fd3f60f82e7766df310431fc Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 08:32:47 -0300 Subject: [PATCH 091/357] Adding more tests to frame decoder --- .travis.yml | 5 +- .../db/mysql/codec/MySQLFrameDecoder.scala | 78 ++++----- .../mysql/codec/MySQLFrameDecoderSpec.scala | 158 ++++++++++++++++++ .../mysql/codec/MySQLMessageDecoderSpec.scala | 111 ------------ 4 files changed, 193 insertions(+), 159 deletions(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala delete mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala diff --git a/.travis.yml b/.travis.yml index 4f5764d3..ae5ea650 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,7 @@ services: - mysql before_script: - mysql -u root -e 'create database mysql_async_tests;' - - psql -c 'create database netty_driver_test;' -U postgres \ No newline at end of file + - psql -c 'create database netty_driver_test;' -U postgres +notifications: + email: + - linhares.mauricio@gmail.com \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 6df37d71..661ab8be 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -18,19 +18,14 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.exceptions._ import com.github.mauricio.async.db.mysql.decoder._ -import com.github.mauricio.async.db.mysql.message.server.{BinaryRowMessage, ColumnProcessingFinishedMessage, PreparedStatementPrepareResponse, ServerMessage} +import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import com.github.mauricio.async.db.util.{PrintUtils, Log} +import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.frame.FrameDecoder -import com.github.mauricio.async.db.mysql.message.client.PreparedStatementPrepareMessage -import com.github.mauricio.async.db.mysql.MySQLHelper -import com.github.mauricio.async.db.mysql.message.server.ColumnProcessingFinishedMessage -import com.github.mauricio.async.db.mysql.message.server.PreparedStatementPrepareResponse -import com.github.mauricio.async.db.mysql.message.server.BinaryRowMessage object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] @@ -38,8 +33,6 @@ object MySQLFrameDecoder { class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { - import MySQLFrameDecoder.log - private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) private final val okDecoder = new OkDecoder(charset) @@ -47,30 +40,26 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { private final val rowDecoder = new ResultSetRowDecoder(charset) private final val preparedStatementPrepareDecoder = new PreparedStatementPrepareResponseDecoder() - private var processingColumns = false - private var processingParams = false - private var isInQuery = false - private var isPreparedStatementPrepare = false - private var isPreparedStatementExecute = false - private var isPreparedStatementExecuteRows = false + private[codec] var processingColumns = false + private[codec] var processingParams = false + private[codec] var isInQuery = false + private[codec] var isPreparedStatementPrepare = false + private[codec] var isPreparedStatementExecute = false + private[codec] var isPreparedStatementExecuteRows = false - private var totalParams = 0L - private var processedParams = 0L - private var totalColumns = 0L - private var processedColumns = 0L + private[codec] var totalParams = 0L + private[codec] var processedParams = 0L + private[codec] var totalColumns = 0L + private[codec] var processedColumns = 0L def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { if (buffer.readableBytes() > 4) { - //val requestDump = MySQLHelper.dumpAsHex(buffer, buffer.readableBytes()) - //log.debug(s"Server message\n${requestDump}") - //PrintUtils.printArray( "any message", buffer) - buffer.markReaderIndex() val size = read3BytesInt(buffer) - val sequence = buffer.readUnsignedByte() + val sequence = buffer.readUnsignedByte() // we have to read this if (buffer.readableBytes() >= size) { @@ -82,11 +71,6 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val slice = buffer.readSlice(size) - //val dump = MySQLHelper.dumpAsHex(slice, slice.readableBytes()) - //log.debug(s"Message type $messageType - message size - $size - sequence - $sequence\n$dump") - - // removing initial kind byte so that we can switch - // on known messages but add it back if this is a query process slice.readByte() val decoder = messageType match { @@ -182,24 +166,6 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { return null } - def preparedStatementPrepareStarted() { - this.processingParams = true - this.processingColumns = true - this.isPreparedStatementPrepare = true - this.queryProcessStarted() - } - - def preparedStatementExecuteStarted() { - this.queryProcessStarted() - this.isPreparedStatementExecute = true - this.processingParams = false - } - - def queryProcessStarted() { - this.isInQuery = true - this.processingColumns = true - } - private def decodeQueryResult(slice: ChannelBuffer): AnyRef = { if (this.totalColumns == 0) { this.totalColumns = slice.readBinaryLength @@ -225,6 +191,24 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { } } + def preparedStatementPrepareStarted() { + this.processingParams = true + this.processingColumns = true + this.isPreparedStatementPrepare = true + this.queryProcessStarted() + } + + def preparedStatementExecuteStarted() { + this.queryProcessStarted() + this.isPreparedStatementExecute = true + this.processingParams = false + } + + def queryProcessStarted() { + this.isInQuery = true + this.processingColumns = true + } + private def clear { this.isPreparedStatementPrepare = false this.isPreparedStatementExecute = false diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala new file mode 100644 index 00000000..635aecd1 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -0,0 +1,158 @@ +/* + * 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.mysql.codec + +import com.github.mauricio.async.db.mysql.message.server._ +import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import org.jboss.netty.handler.codec.embedder.DecoderEmbedder +import org.jboss.netty.util.CharsetUtil +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.mysql.message.server.OkMessage +import org.jboss.netty.buffer.ChannelBuffer + +class MySQLFrameDecoderSpec extends Specification { + + final val charset = CharsetUtil.UTF_8 + + "decoder" should { + + "decode an OK message correctly" in { + + val buffer = createOkPacket() + + val decoder = this.createPipeline() + + decoder.offer(buffer) + + val ok = decoder.peek().asInstanceOf[OkMessage] + ok.affectedRows === 10 + ok.lastInsertId === 15 + ok.message === "this is a test" + ok.statusFlags === 5 + ok.warnings === 6 + } + + "decode an error message" in { + + val content = "this is the error message" + + val buffer = createErrorPacket(content) + + val decoder = createPipeline() + + decoder.offer(buffer) + + val error = decoder.peek().asInstanceOf[ErrorMessage] + + error.errorCode === 27 + error.errorMessage === content + error.sqlState === "HZAWAY" + + } + + "on a query process it should correctly send an OK" in { + + val decoder = new MySQLFrameDecoder(charset) + val embedder = new DecoderEmbedder[ServerMessage](decoder) + + decoder.queryProcessStarted() + + decoder.isInQuery must beTrue + decoder.processingColumns must beTrue + + val buffer = createOkPacket() + + embedder.offer(buffer) + embedder.peek().asInstanceOf[OkMessage].message === "this is a test" + + decoder.isInQuery must beFalse + decoder.processingColumns must beFalse + } + + "on query process it should correctly send an error" in { + + val decoder = new MySQLFrameDecoder(charset) + val embedder = new DecoderEmbedder[ServerMessage](decoder) + + decoder.queryProcessStarted() + + decoder.isInQuery must beTrue + decoder.processingColumns must beTrue + + val content = "this is a crazy error" + + val buffer = createErrorPacket(content) + + embedder.offer(buffer) + embedder.peek().asInstanceOf[ErrorMessage].errorMessage === content + + decoder.isInQuery must beFalse + decoder.processingColumns must beFalse + + } + + "on query process it should correctly handle a result set" in { + + val decoder = new MySQLFrameDecoder(charset) + val embedder = new DecoderEmbedder[ServerMessage](decoder) + + decoder.queryProcessStarted() + + decoder.totalColumns === 0 + + val columnCountBuffer = ChannelUtils.packetBuffer() + columnCountBuffer.writeLength(2) + + embedder.offer(columnCountBuffer) + + decoder.totalColumns === 2 + + + } + + } + + def createPipeline(): DecoderEmbedder[ServerMessage] = { + new DecoderEmbedder[ServerMessage](new MySQLFrameDecoder(charset)) + } + + def createOkPacket() : ChannelBuffer = { + val buffer = ChannelUtils.packetBuffer() + buffer.writeByte(0) + buffer.writeLength(10) + buffer.writeLength(15) + buffer.writeShort(5) + buffer.writeShort(6) + buffer.writeBytes("this is a test".getBytes(charset)) + buffer.writePacketLength() + buffer + } + + def createErrorPacket(content : String) : ChannelBuffer = { + val buffer = ChannelUtils.packetBuffer() + buffer.writeByte(0xff) + buffer.writeShort(27) + buffer.writeByte('H') + buffer.writeBytes("ZAWAY".getBytes(charset)) + buffer.writeBytes(content.getBytes(charset)) + buffer.writePacketLength() + buffer + } + + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala deleted file mode 100644 index 6d1c9aee..00000000 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLMessageDecoderSpec.scala +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.mysql.codec - -import com.github.mauricio.async.db.mysql.message.server._ -import com.github.mauricio.async.db.util.ChannelUtils -import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import org.jboss.netty.handler.codec.embedder.DecoderEmbedder -import org.jboss.netty.util.CharsetUtil -import org.specs2.mutable.Specification -import com.github.mauricio.async.db.mysql.message.server.OkMessage - -class MySQLMessageDecoderSpec extends Specification { - - final val charset = CharsetUtil.UTF_8 - - "decoder" should { - - "decode an OK message correctly" in { - - /* -1 [00] the OK header -lenenc-int affected rows -lenenc-int last-insert-id - if capabilities & CLIENT_PROTOCOL_41 { -2 status_flags -2 warnings - } elseif capabilities & CLIENT_TRANSACTIONS { -2 status_flags - } -string[EOF] info - */ - - - val buffer = ChannelUtils.packetBuffer() - buffer.writeByte(0) - buffer.writeLength(10) - buffer.writeLength(15) - buffer.writeShort(5) - buffer.writeShort(6) - buffer.writeBytes( "this is a test".getBytes(charset) ) - buffer.writePacketLength() - - val decoder = this.createPipeline() - - decoder.offer(buffer) - - val ok = decoder.peek().asInstanceOf[OkMessage] - ok.affectedRows === 10 - ok.lastInsertId === 15 - ok.message === "this is a test" - ok.statusFlags === 5 - ok.warnings === 6 - } - - "decode an error message" in { - - /* -1 [ff] the ERR header -2 error code - if capabilities & CLIENT_PROTOCOL_41 { -string[1] '#' the sql-state marker -string[5] sql-state - } -string[EOF] error-message - */ - - val content = "this is the error message" - - val buffer = ChannelUtils.packetBuffer() - buffer.writeByte(0xff) - buffer.writeShort(27) - buffer.writeByte('H') - buffer.writeBytes( "ZAWAY".getBytes(charset) ) - buffer.writeBytes(content.getBytes(charset)) - buffer.writePacketLength() - - val decoder = createPipeline() - - decoder.offer(buffer) - - val error = decoder.peek().asInstanceOf[ErrorMessage] - - error.errorCode === 27 - error.errorMessage === content - error.sqlState === "HZAWAY" - - - } - - } - - def createPipeline() : DecoderEmbedder[ServerMessage] = { - new DecoderEmbedder[ServerMessage](new MySQLFrameDecoder(charset)) - } - -} From b16f5b0525ab119a96d05683fd04eeb4aab612d0 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 08:42:36 -0300 Subject: [PATCH 092/357] Removing jacoco from plugins list --- build.sbt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.sbt b/build.sbt index 2e2fd1f6..e69de29b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +0,0 @@ -import de.johoop.jacoco4sbt._ -import JacocoPlugin._ - - - -seq(jacoco.settings : _*) \ No newline at end of file From 296a59d741f84749096b636b5e1ddd7ec44fe4a0 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 09:41:27 -0300 Subject: [PATCH 093/357] Adding db config --- .travis.yml | 11 +++++++++-- .../async/db/postgresql/DatabaseTestHelper.scala | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ae5ea650..cdd233b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,21 @@ scala: - 2.10.1 jdk: - oraclejdk7 - - openjdk7 - - openjdk6 services: - postgresql - mysql before_script: - mysql -u root -e 'create database mysql_async_tests;' + - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO ‘mysql_async’@'localhost’ IDENTIFIED BY ‘root’ WITH GRANT OPTION;" - psql -c 'create database netty_driver_test;' -U postgres + - psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres + - psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres + - psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres + - sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf + - sudo echo "host all postgres_md5 127.0.0.1/32 md5" > /etc/postgresql/9.1/main/pg_hba.conf + - sudo echo "host all postgres_cleartext 127.0.0.1/32 password" > /etc/postgresql/9.1/main/pg_hba.conf + - sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" > /etc/postgresql/9.1/main/pg_hba.conf + notifications: email: - linhares.mauricio@gmail.com \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index 562cc3c2..2872dd85 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -32,7 +32,7 @@ trait DatabaseTestHelper { def databaseName = Some("netty_driver_test") - def databasePort = 5433 + def databasePort = 5432 def defaultConfiguration = new Configuration( port = databasePort, From 4746e0dbd80a7f85bc1b9ef5e61cb8d3c742c2ea Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 09:53:25 -0300 Subject: [PATCH 094/357] Fixing broken spec and updating travis ci data --- .travis.yml | 11 +---------- .../mysql/codec/MySQLFrameDecoderSpec.scala | 1 + .../postgresql/pool/ConnectionPoolSpec.scala | 2 ++ script/prepare_build.sh | 19 +++++++++++++++++++ 4 files changed, 23 insertions(+), 10 deletions(-) create mode 100755 script/prepare_build.sh diff --git a/.travis.yml b/.travis.yml index cdd233b0..1b7b3c26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,16 +7,7 @@ services: - postgresql - mysql before_script: - - mysql -u root -e 'create database mysql_async_tests;' - - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO ‘mysql_async’@'localhost’ IDENTIFIED BY ‘root’ WITH GRANT OPTION;" - - psql -c 'create database netty_driver_test;' -U postgres - - psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres - - psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres - - psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres - - sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf - - sudo echo "host all postgres_md5 127.0.0.1/32 md5" > /etc/postgresql/9.1/main/pg_hba.conf - - sudo echo "host all postgres_cleartext 127.0.0.1/32 password" > /etc/postgresql/9.1/main/pg_hba.conf - - sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" > /etc/postgresql/9.1/main/pg_hba.conf + - ./script/prepare_build.sh notifications: email: diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index 635aecd1..8619cc13 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -117,6 +117,7 @@ class MySQLFrameDecoderSpec extends Specification { val columnCountBuffer = ChannelUtils.packetBuffer() columnCountBuffer.writeLength(2) + columnCountBuffer.writePacketLength() embedder.offer(columnCountBuffer) 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 e7f12443..49b8c8f1 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 @@ -29,6 +29,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { withPool{ pool => executeQuery(pool, "SELECT 8").rows.get(0)(0) === 8 + Thread.sleep(1000) pool.availables.size === 1 } @@ -38,6 +39,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { withPool{ pool => executePreparedStatement(pool, "SELECT 8").rows.get(0)(0) === 8 + Thread.sleep(1000) pool.availables.size === 1 } } diff --git a/script/prepare_build.sh b/script/prepare_build.sh new file mode 100755 index 00000000..a774b30d --- /dev/null +++ b/script/prepare_build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh + +echo "Preparing MySQL configs" +mysql -u root -e 'create database mysql_async_tests;' +mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO ‘mysql_async’@'localhost’ IDENTIFIED BY ‘root’ WITH GRANT OPTION"; + +echo "preparing postgresql configs" + +psql -c 'create database netty_driver_test;' -U postgres +psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres +psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres +psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres + +sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf +sudo echo "host all postgres_md5 127.0.0.1/32 md5" > /etc/postgresql/9.1/main/pg_hba.conf +sudo echo "host all postgres_cleartext 127.0.0.1/32 password" > /etc/postgresql/9.1/main/pg_hba.conf +sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" > /etc/postgresql/9.1/main/pg_hba.conf + +cat "/etc/postgresql/9.1/main/pg_hba.conf" \ No newline at end of file From fe1737e5a560e8edc654f3c842645f51d5417e67 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 09:56:29 -0300 Subject: [PATCH 095/357] Fixing MySQL command --- script/prepare_build.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/script/prepare_build.sh b/script/prepare_build.sh index a774b30d..93f9ec34 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -2,7 +2,7 @@ echo "Preparing MySQL configs" mysql -u root -e 'create database mysql_async_tests;' -mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO ‘mysql_async’@'localhost’ IDENTIFIED BY ‘root’ WITH GRANT OPTION"; +mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async'@'localhost' IDENTIFIED BY 'root' WITH GRANT OPTION"; echo "preparing postgresql configs" @@ -12,6 +12,10 @@ psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRAN psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf + +echo "pg_hba.conf goes as follows" +cat "/etc/postgresql/9.1/main/pg_hba.conf" + sudo echo "host all postgres_md5 127.0.0.1/32 md5" > /etc/postgresql/9.1/main/pg_hba.conf sudo echo "host all postgres_cleartext 127.0.0.1/32 password" > /etc/postgresql/9.1/main/pg_hba.conf sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" > /etc/postgresql/9.1/main/pg_hba.conf From 22aff5f68dab3c2e6f5fee342dcbb60019002651 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 10:01:43 -0300 Subject: [PATCH 096/357] Updating pg_hba.conf again --- script/prepare_build.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 93f9ec34..9bd9fcda 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -16,8 +16,10 @@ sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf echo "pg_hba.conf goes as follows" cat "/etc/postgresql/9.1/main/pg_hba.conf" -sudo echo "host all postgres_md5 127.0.0.1/32 md5" > /etc/postgresql/9.1/main/pg_hba.conf -sudo echo "host all postgres_cleartext 127.0.0.1/32 password" > /etc/postgresql/9.1/main/pg_hba.conf -sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" > /etc/postgresql/9.1/main/pg_hba.conf +sudo echo "host all postgres 127.0.0.1/32 trust" > /etc/postgresql/9.1/main/pg_hba.con +sudo echo "host all postgres_md5 127.0.0.1/32 md5" >> /etc/postgresql/9.1/main/pg_hba.conf +sudo echo "host all postgres_cleartext 127.0.0.1/32 password" >> /etc/postgresql/9.1/main/pg_hba.conf +sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" >> /etc/postgresql/9.1/main/pg_hba.conf +echo "pg_hba.conf is now like" cat "/etc/postgresql/9.1/main/pg_hba.conf" \ No newline at end of file From 04fcb10ac366dd5f5975e6084fdb8fa4f1122176 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 10:03:43 -0300 Subject: [PATCH 097/357] typo on pg_hba.conf --- script/prepare_build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 9bd9fcda..71a758ec 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -16,7 +16,7 @@ sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf echo "pg_hba.conf goes as follows" cat "/etc/postgresql/9.1/main/pg_hba.conf" -sudo echo "host all postgres 127.0.0.1/32 trust" > /etc/postgresql/9.1/main/pg_hba.con +sudo echo "host all postgres 127.0.0.1/32 trust" > /etc/postgresql/9.1/main/pg_hba.conf sudo echo "host all postgres_md5 127.0.0.1/32 md5" >> /etc/postgresql/9.1/main/pg_hba.conf sudo echo "host all postgres_cleartext 127.0.0.1/32 password" >> /etc/postgresql/9.1/main/pg_hba.conf sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" >> /etc/postgresql/9.1/main/pg_hba.conf From 6993c9c0b3515d06119c047f5ca00d6a8e365f07 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 10:18:30 -0300 Subject: [PATCH 098/357] Restarting PostgreSQL after updating pg_hba.conf --- script/prepare_build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 71a758ec..3b3a3546 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -22,4 +22,6 @@ sudo echo "host all postgres_cleartext 127.0.0.1/32 pass sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" >> /etc/postgresql/9.1/main/pg_hba.conf echo "pg_hba.conf is now like" -cat "/etc/postgresql/9.1/main/pg_hba.conf" \ No newline at end of file +cat "/etc/postgresql/9.1/main/pg_hba.conf" + +sudo /etc/init.d/postgresql restart \ No newline at end of file From 37f0fd1c35fa047e73342ce92971597d42573347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Tue, 14 May 2013 10:42:31 -0300 Subject: [PATCH 099/357] Updating README [ci skip] --- README.markdown | 78 +++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/README.markdown b/README.markdown index c099b79b..26845748 100644 --- a/README.markdown +++ b/README.markdown @@ -1,12 +1,18 @@ -# postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10 +# postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala [![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) -The main goal of this project is to implement a performant and fully functional async PostgreSQL driver. This project -has no interest in JDBC, it's supposed to be a clean room implementation for people interested in talking directly -to PostgreSQL. +The main goal for this project is to implement simple, async, performant and reliable database drivers for +PostgreSQL andMySQL in Scala. This is not supposed to be a JDBC replacement, these drivers aim to cover the common +process of "send a statement, get a response" that you usually see in applications out there. So it's unlikely +there will be support for updating result sets live or things like that. -[PostgreSQL protocol information and definition can be found here](http://www.postgresql.org/docs/devel/static/protocol.html) +This project always returns `JodaTime` when dealing with date types and not the Java Date class. -Include at your SBT project with: +If you want information specific to the drivers, check the [PostgreSQL README](postgresql-async/README.md) and the +[MySQL README](mysql-async/README.md). + +## Include them as dependencies + +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.1.1" @@ -22,27 +28,21 @@ Or Maven: ``` -This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql.org/) driver under the -`com.github.mauricio.async.db.postgresql.util` package consisting of the `ParseURL` class. - -## What can it do now? +And if you're into MySQL: -- connect to a database with or without authentication (supports MD5 and cleartext authentication methods) -- receive database parameters -- receive database notices -- execute direct queries (without portals/prepared statements) -- portals/prepared statements -- parses most of the basic PostgreSQL types, other types are parsed as string -- date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects -- all work is done using the new `scala.concurrent.Future` and `scala.concurrent.Promise` objects +```scala +"com.github.mauricio" %% "mysql-async" % "x.x.x" +``` -## What is missing? +Or Maven: -- more authentication mechanisms -- benchmarks -- more tests (run the `jacoco:cover` sbt task and see where you can improve) -- timeout handler for initial handshare and queries -- implement byte array support +```xml + + com.github.mauricio + mysql-async_2.10 + x.x.x + +``` ## What are the design goals? @@ -52,7 +52,14 @@ This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql - easy to use, call a method, get a future or a callback and be happy - never, ever, block - all features covered by tests -- next to zero dependencies (it currently depends on Netty and SFL4J only) +- next to zero dependencies (it currently depends on Netty, JodaTime and SFL4J only) + +## What is missing? + +- more authentication mechanisms +- benchmarks +- more tests (run the `jacoco:cover` sbt task and see where you can improve) +- timeout handler for initial handshare and queries ## How can you help? @@ -67,8 +74,9 @@ This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql ### Connection Represents a connection to the database. This is the **root** object you will be using in your application. You will -find two classes that implement this trait, `DatabaseConnectionHandler` and `ConnectionPool`. The different between -them is that `DatabaseConnectionHandler` is a single connection and `ConnectionPool` represents a pool of connections. +find three classes that implement this trait, `PostgreSQLConnection`, `MySQLConnection` and `ConnectionPool`. +The different between them is that `ConnectionPool` is, as the name implies, a pool of connections and you +need to give it an connection factory so it can create connections and manage them. To create both you will need a `Configuration` object with your database details. You can create one manually or create one from a JDBC or Heroku database URL using the `URLParser` object. @@ -83,7 +91,7 @@ query returns any rows. It's an IndexedSeq\[Array\[Any]], every item is a row returned by the database. The database types are returned as Scala objects that fit the original type, so `smallint` becomes a `Short`, `numeric` becomes `BigDecimal`, `varchar` becomes -`String` and so on. You can find the whole default transformation list at the `DefaultColumnDecoderRegistry` class. +`String` and so on. You can find the whole default transformation list at the project specific documentation. ### Prepared statements @@ -108,14 +116,14 @@ as prepared statement parameters and they will be encoded to their respective Po Remember that parameters are positional the order they show up at query should be the same as the one in the array or sequence given to the method call. -## Example usage +## Example usage (for PostgreSQL, but it looks almost the same on MySQL) You can find a small Play 2 app using it [here](https://github.com/mauricio/postgresql-async-app) and a blog post about it [here](http://mauricio.github.io/2013/04/29/async-database-access-with-postgresql-play-scala-and-heroku.html). In short, what you would usually do is: ```scala -import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler +import com.github.mauricio.async.db.postgresql.PostgreSQLConnection import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext import com.github.mauricio.async.db.util.URLParser import com.github.mauricio.async.db.{RowData, QueryResult, Connection} @@ -127,7 +135,7 @@ object BasicExample { def main(args: Array[String]) { val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?username=postgres&password=somepassword") - val connection: Connection = new DatabaseConnectionHandler(configuration) + val connection: Connection = new PostgreSQLConnection(configuration) Await.result(connection.connect, 5 seconds) @@ -153,17 +161,17 @@ object BasicExample { } ``` -First, create a `DatabaseConnectionHandler`, connect it to the database, execute queries (this object is not thread +First, create a `PostgreSQLConnection`, connect it to the database, execute queries (this object is not thread safe, so you must execute only one query at a time) and work with the futures it returns. Once you are done, call disconnect and the connection is closed. You can also use the `ConnectionPool` provided by the driver to simplify working with database connections in your app. Check the blog post above for more details and the project's ScalaDocs. -## Supported Scala/Java types and their destination types on PostgreSQL - +## Contributors +* @fwbrasil ## Licence -This project is freely available under the Apache 2 licence, use it at your own risk. \ No newline at end of file +This project is freely available under the Apache 2 licence, fork, fix and send back :) From d4a2b396ce0a0638f603f33339be420407c627dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Tue, 14 May 2013 11:23:16 -0300 Subject: [PATCH 100/357] Updating PostgreSQL readme [ci skip] --- postgresql-async/README.md | 190 ++++++++++--------------------------- 1 file changed, 52 insertions(+), 138 deletions(-) diff --git a/postgresql-async/README.md b/postgresql-async/README.md index c099b79b..c15425f6 100644 --- a/postgresql-async/README.md +++ b/postgresql-async/README.md @@ -6,22 +6,6 @@ to PostgreSQL. [PostgreSQL protocol information and definition can be found here](http://www.postgresql.org/docs/devel/static/protocol.html) -Include at your SBT project with: - -```scala -"com.github.mauricio" %% "postgresql-async" % "0.1.1" -``` - -Or Maven: - -```xml - - com.github.mauricio - postgresql-async_2.10 - 0.1.1 - -``` - This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql.org/) driver under the `com.github.mauricio.async.db.postgresql.util` package consisting of the `ParseURL` class. @@ -35,6 +19,7 @@ This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql - parses most of the basic PostgreSQL types, other types are parsed as string - date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects - all work is done using the new `scala.concurrent.Future` and `scala.concurrent.Promise` objects +- support for Byte arrays if using PostgreSQL >= 9.0 ## What is missing? @@ -42,128 +27,57 @@ This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql - benchmarks - more tests (run the `jacoco:cover` sbt task and see where you can improve) - timeout handler for initial handshare and queries -- implement byte array support - -## What are the design goals? - -- fast, fast and faster -- small memory footprint -- avoid copying data as much as possible (we're always trying to use wrap and slice on buffers) -- easy to use, call a method, get a future or a callback and be happy -- never, ever, block -- all features covered by tests -- next to zero dependencies (it currently depends on Netty and SFL4J only) - -## How can you help? - -- checkout the source code -- find bugs, find places where performance can be improved -- check the **What is missing** piece -- check the [issues page](https://github.com/mauricio/postgresql-async/issues) for bugs or new features -- send a pull request with specs - -## Main public interface - -### Connection - -Represents a connection to the database. This is the **root** object you will be using in your application. You will -find two classes that implement this trait, `DatabaseConnectionHandler` and `ConnectionPool`. The different between -them is that `DatabaseConnectionHandler` is a single connection and `ConnectionPool` represents a pool of connections. - -To create both you will need a `Configuration` object with your database details. You can create one manually or -create one from a JDBC or Heroku database URL using the `URLParser` object. - -### QueryResult - -It's the output of running a statement against the database (either using `sendQuery` or `sendPreparedStatement`). -This object will contain the amount of rows, status message and the possible `ResultSet` (Option\[ResultSet]) if the -query returns any rows. - -### ResultSet - -It's an IndexedSeq\[Array\[Any]], every item is a row returned by the database. The database types are returned as Scala -objects that fit the original type, so `smallint` becomes a `Short`, `numeric` becomes `BigDecimal`, `varchar` becomes -`String` and so on. You can find the whole default transformation list at the `DefaultColumnDecoderRegistry` class. - -### Prepared statements - -Databases support prepared or precompiled statements. These statements allow the database to precompile the query -on the first execution and reuse this compiled representation on future executions, this makes it faster and also allows -for safer data escaping when dealing with user provided data. - -To execute a prepared statement you grab a connection and: - -```scala -val connection : Connection = ... -val future = connection.sendPreparedStatement( "SELECT * FROM products WHERE products.name = ?", Array( "Dominion" ) ) -``` - -The `?` (question mark) in the query is a parameter placeholder, it allows you to set a value in that place in the -query without having to escape stuff yourself. The driver itself will make sure this parameter is delivered to the -database in a safe way so you don't have to worry about SQL injection attacks. - -The basic numbers, Joda Time date, time, timestamp objects, strings and arrays of these objects are all valid values -as prepared statement parameters and they will be encoded to their respective PostgreSQL types. - -Remember that parameters are positional the order they show up at query should be the same as the one in the array or -sequence given to the method call. - -## Example usage - -You can find a small Play 2 app using it [here](https://github.com/mauricio/postgresql-async-app) and a blog post about -it [here](http://mauricio.github.io/2013/04/29/async-database-access-with-postgresql-play-scala-and-heroku.html). - -In short, what you would usually do is: -```scala -import com.github.mauricio.async.db.postgresql.DatabaseConnectionHandler -import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext -import com.github.mauricio.async.db.util.URLParser -import com.github.mauricio.async.db.{RowData, QueryResult, Connection} -import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} - -object BasicExample { - - def main(args: Array[String]) { - - val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?username=postgres&password=somepassword") - val connection: Connection = new DatabaseConnectionHandler(configuration) - - Await.result(connection.connect, 5 seconds) - - val future: Future[QueryResult] = connection.sendQuery("SELECT 0") - - val mapResult: Future[Any] = future.map(queryResult => queryResult.rows match { - case Some(resultSet) => { - val row : RowData = resultSet.head - row(0) - } - case None => -1 - } - ) - - val result = Await.result( mapResult, 5 seconds ) - - println(result) - - connection.disconnect - - } - -} -``` - -First, create a `DatabaseConnectionHandler`, connect it to the database, execute queries (this object is not thread -safe, so you must execute only one query at a time) and work with the futures it returns. Once you are done, call -disconnect and the connection is closed. - -You can also use the `ConnectionPool` provided by the driver to simplify working with database connections in your app. -Check the blog post above for more details and the project's ScalaDocs. +- implement byte array support for PostgreSQL <= 8 ## Supported Scala/Java types and their destination types on PostgreSQL - - -## Licence - -This project is freely available under the Apache 2 licence, use it at your own risk. \ No newline at end of file +All types also support their array versions, but they are returned as `IndexedSeq` of the type and not +pure `Array` types. + +PostgreSQL type | Scala/Java type +--- | --- | --- +boolean | Boolean +smallint | Short +integer (or serial) | Int +bigint (or bigserial) | Long +numeric | BigDecimal +real | Float +double | Double +text | String +varchar | String +bpchar | String +timestamp | LocalDateTime +timestamp_with_timezone | DateTime +date | LocalDate +time | LocalTime +bytea | Array[Byte] (PostgreSQL 9.0 and above only) + +All other types are returned as String. + +Now from Scala/Java types to PostgreSQL types (when using prepared +statements): + +Scala/Java type | PostgreSQL type +--- | --- | --- +Boolean | boolean +Short | smallint +Int | integer +Long | bigint +Float | float +Double | double +BigInteger | numeric +BigDecimal | numeric +String | varchar +Array[Byte] | bytea (PostgreSQL 9.0 and above only) +java.util.Date | timestamp_with_timezone +java.sql.Timestamp | timestamp_with_timezone +java.sql.Date | date +java.sql.Time | time +LocalDate | date +LocalDateTime | timestamp +DateTime | timestamp_with_timezone +LocalTime | time + +Array types are encoded with the kind of object they hold and not the array type itself. Java `Collection` and +Scala `Traversable` objects are also assumed to be arrays of the types they hold and will be sent to PostgreSQL +like that. From a50e2e1c99311394dff01c7c0175dd8372fed8c0 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 11:24:36 -0300 Subject: [PATCH 101/357] More docs updates [ci skip] --- README.markdown | 11 ++++--- .../column/LocalDateTimeEncoderDecoder.scala | 31 +++++++++++++++++ .../async/db/column/SQLTimeEncoder.scala | 33 +++++++++++++++++++ .../db/column/TimestampEncoderDecoder.scala | 2 +- .../PostgreSQLColumnEncoderRegistry.scala | 2 +- 5 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/column/SQLTimeEncoder.scala diff --git a/README.markdown b/README.markdown index 26845748..604f9f0e 100644 --- a/README.markdown +++ b/README.markdown @@ -1,11 +1,12 @@ -# postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala [![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) +# [![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 The main goal for this project is to implement simple, async, performant and reliable database drivers for -PostgreSQL andMySQL in Scala. This is not supposed to be a JDBC replacement, these drivers aim to cover the common -process of "send a statement, get a response" that you usually see in applications out there. So it's unlikely +PostgreSQL and MySQL in Scala. This is not supposed to be a JDBC replacement, these drivers aim to cover the common +process of _send a statement, get a response_ that you usually see in applications out there. So it's unlikely there will be support for updating result sets live or things like that. -This project always returns `JodaTime` when dealing with date types and not the Java Date class. +This project always returns [JodaTime](http://joda-time.sourceforge.net/) when dealing with date types and not the +`java.util.Date` class. If you want information specific to the drivers, check the [PostgreSQL README](postgresql-async/README.md) and the [MySQL README](mysql-async/README.md). @@ -66,7 +67,7 @@ Or Maven: - checkout the source code - find bugs, find places where performance can be improved - check the **What is missing** piece -- check the [issues page](https://github.com/mauricio/postgresql-async/issues) for bugs or new features +- check the [issues page](issues) for bugs or new features - send a pull request with specs ## Main public interface diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala new file mode 100644 index 00000000..a2bacd0a --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala @@ -0,0 +1,31 @@ +/* + * 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 org.joda.time.format.DateTimeFormatterBuilder +import org.joda.time.LocalDateTime + +object LocalDateTimeEncoderDecoder extends ColumnEncoderDecoder { + + private val format = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss") + .toFormatter + + override def encode(value: Any): String = format.print(value.asInstanceOf[LocalDateTime]) + + override def decode(value: String): LocalDateTime = format.parseLocalDateTime(value) +} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/SQLTimeEncoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/SQLTimeEncoder.scala new file mode 100644 index 00000000..b075754d --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/SQLTimeEncoder.scala @@ -0,0 +1,33 @@ +/* + * 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 org.joda.time.format.DateTimeFormatterBuilder +import org.joda.time.LocalTime + +object SQLTimeEncoder extends ColumnEncoder { + + final private val format = new DateTimeFormatterBuilder() + .appendPattern("HH:mm:ss") + .toFormatter + + override def encode(value: Any): String = { + val time = value.asInstanceOf[java.sql.Time] + + format.print( new LocalTime(time.getTime) ) + } +} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala index 669bee40..99ceb4d2 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala @@ -38,7 +38,7 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { def formatter = format - override def decode(value: String): ReadableDateTime = { + override def decode(value: String): DateTime = { formatter.parseDateTime(value) } 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 0d9db82e..aa06cb71 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 @@ -58,7 +58,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[java.util.Date] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.sql.Date] -> ( DateEncoderDecoder -> ColumnTypes.Date ), - classOf[java.sql.Time] -> ( TimeEncoderDecoder.Instance -> ColumnTypes.Time ), + classOf[java.sql.Time] -> ( SQLTimeEncoder -> ColumnTypes.Time ), classOf[java.sql.Timestamp] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.util.Calendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.util.GregorianCalendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), From 01300de94912d0c13d54ecd1bd0e5920e3533f30 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 12:20:14 -0300 Subject: [PATCH 102/357] More changes to documentation [ci skip] --- README.markdown | 18 ++++- mysql-async/README.md | 66 +++++++++++++++++++ .../db/mysql/binary/decoder/TimeDecoder.scala | 2 +- .../binary/decoder/TimestampDecoder.scala | 2 +- 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 mysql-async/README.md diff --git a/README.markdown b/README.markdown index 604f9f0e..8913ab27 100644 --- a/README.markdown +++ b/README.markdown @@ -45,6 +45,22 @@ Or Maven: ``` +## Database connections and encodings + +**READ THIS NOW** + +Both clients will let you set the database encoding for something else. Unless you are 1000% sure of what you are doing, +**DO NOT** change the default encoding (currently, UTF-8). Some people assume the connection encoding is the **database** +or **columns** encoding but **IT IS NOT**, this is just the connection encoding that is used between client and servers +doing communication. + +When you change the encoding of the **connection** you are not affecting the your database's encoding and your columns +**WILL NOT** be stored with the connection encoding. If the connection and database/column encoding is different, your +database will automatically translate from the connection encoding to the correct encoding and all your data will be +safely stored at your database/column encoding. + +So, just don't touch it and be happy. + ## What are the design goals? - fast, fast and faster @@ -171,7 +187,7 @@ Check the blog post above for more details and the project's ScalaDocs. ## Contributors -* @fwbrasil +* [fwbrasil](https://github.com/fwbrasil) ## Licence diff --git a/mysql-async/README.md b/mysql-async/README.md new file mode 100644 index 00000000..76ee87ce --- /dev/null +++ b/mysql-async/README.md @@ -0,0 +1,66 @@ +# mysql-async - an asyncm Netty based, MySQL driver written in Scala 2.10 + +This is the MySQL part of the async driver collection. As the PostgreSQL version, it is not supposed to be a JDBC +replacement, but a simpler solution for those that need something that queries and then returns rows. + +You can find more information about the MySQL network protocol [here](http://dev.mysql.com/doc/internals/en/client-server-protocol.html). + +## What can it do now? + +* connect do databases with the **mysql_native_password** method (that's the usual way) +* execute common statements +* execute prepared statements +* supports MySQL servers from 4.1 and above +* supports most available database types + +## Supported types + +One important thing to take into account here is that `time` in MySQL is not exactly a time in hours, minutes, seconds. +It's a period/duration and it can be expressed days too (you could, for instance, say that a time is +__-120d 19:27:30.000 001__. As much as this does not make much sense, that is how it was implemented at the database +and as a driver we need to stay true to it, so, while you **can** send `java.sql.Time` and `LocalTime` objects to the +database, when reading these values you will always receive a `scala.concurrent.Duration` object since it is the closest +thing we have to what a `time` value in MySQL means. + +MySQL type | Scala/Java type +--- | --- | --- +date | LocalDate +datetime | LocalDateTime +new_date | LocalDate +timestamp | LocalDateTime +tinyint | Byte +smallint | Short +year | Short +float | Float +double | Double +int24 | Int +mediumint | Int +bigint | Long +numeric | BigDecimal +new_decimal | BigDecimal +decimal | BigDecimal +string | String +var_string | String +varcgar | String +time | scala.concurrent.Duration + +Now when you're using prepared statements: + +Scala/Java type | MySQL type +--- | --- | --- +Byte | tinyint +Short | smallint +Int | mediumint +Float | float +Double | double +BigDecimal | numeric +BigDecimal | decimal +LocalDate | date +DateTime | timestamp +scala.concurrent.Duration | time +java.sql.Date | date +java.util.Date | timestamp +java.sql.Timestamp | timestamp +java.sql.Time | time +String | string +Array[Byte] | blob \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala index f952955f..e4322631 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala @@ -48,7 +48,7 @@ object TimeDecoder extends BinaryDecoder { buffer.readUnsignedByte().hours + buffer.readUnsignedByte().minutes + buffer.readUnsignedByte().seconds + - buffer.readUnsignedInt().millis + (buffer.readUnsignedInt().millis * 1000 ) if ( isNegative ) { duration.neg() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala index af6ad974..feda5602 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala @@ -35,7 +35,7 @@ object TimestampDecoder extends BinaryDecoder { .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), 0) case 11 => new LocalDateTime() .withDate(buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte()) - .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedInt().toInt) + .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedInt().toInt * 1000) } } } \ No newline at end of file From 5acda539b143fc9ff16ad970ea4e06da7db6078b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 14 May 2013 17:36:38 -0300 Subject: [PATCH 103/357] Updating ColumnDecoder to accept a buffer so we don't force byte arrays to be turned to strings --- README.markdown | 2 +- .../async/db/column/ColumnDecoder.scala | 11 ++- .../db/column/ColumnDecoderRegistry.scala | 5 +- .../async/db/general/MutableResultSet.scala | 19 +---- mysql-async/README.md | 1 + .../db/mysql/binary/BinaryRowEncoder.scala | 77 ++++++++++++------- .../db/mysql/binary/decoder/TimeDecoder.scala | 2 +- .../mysql/binary/encoder/BooleanEncoder.scala | 30 ++++++++ .../binary/encoder/ByteArrayEncoder.scala | 30 ++++++++ .../binary/encoder/DurationEncoder.scala | 63 +++++++++++++++ ...teEncoder.scala => LocalDateEncoder.scala} | 2 +- .../binary/encoder/LocalTimeEncoder.scala | 49 ++++++++++++ .../mysql/binary/encoder/SQLDateEncoder.scala | 2 +- .../mysql/binary/encoder/SQLTimeEncoder.scala | 28 +++++++ ...ncoder.scala => SQLTimestampEncoder.scala} | 2 +- .../column/MySQLColumnDecoderRegistry.scala | 6 +- .../mysql/decoder/ResultSetRowDecoder.scala | 3 +- .../message/server/ResultSetRowMessage.scala | 19 ++--- .../db/postgresql/column/ColumnTypes.scala | 1 + .../PostgreSQLColumnDecoderRegistry.scala | 67 ++++++++++------ 20 files changed, 334 insertions(+), 85 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala rename mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/{DateEncoder.scala => LocalDateEncoder.scala} (95%) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala rename mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/{TimestampEncoder.scala => SQLTimestampEncoder.scala} (94%) diff --git a/README.markdown b/README.markdown index 8913ab27..8bcd443e 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -# [![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 +# [![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 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 diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala index e1878882..37e819a5 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala @@ -16,8 +16,17 @@ package com.github.mauricio.async.db.column +import org.jboss.netty.buffer.ChannelBuffer +import java.nio.charset.Charset + trait ColumnDecoder { - def decode(value: String): Any = value + def decode(value: ChannelBuffer, charset : Charset): Any = { + val bytes = new Array[Byte](value.readableBytes()) + value.readBytes(bytes) + decode(new String(bytes, charset)) + } + + def decode( value : String ) : Any } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala index 23b29823..1bcc740f 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala @@ -16,8 +16,11 @@ package com.github.mauricio.async.db.column +import org.jboss.netty.buffer.ChannelBuffer +import java.nio.charset.Charset + trait ColumnDecoderRegistry { - def decode(kind: Int, value: String) : Any + def decode(kind: Int, value: ChannelBuffer, charset : Charset) : Any } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index 37a15014..1354663f 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -44,7 +44,7 @@ class MutableResultSet[T <: ColumnData]( override def apply(idx: Int): RowData = this.rows(idx) - def addRawRow(row: Array[ChannelBuffer]) { + def addRawRow(row: Seq[ChannelBuffer]) { val realRow = new ArrayRowData(columnTypes.size, this.rows.size, this.columnMapping) realRow.indices.foreach { @@ -52,22 +52,7 @@ class MutableResultSet[T <: ColumnData]( realRow(index) = if (row(index) == null) { null } else { - this.decoder.decode( this.columnTypes(index).dataType, row(index).toString(charset) ) - } - } - - this.rows += realRow - } - - def addRawRow( row : Seq[String] ) { - val realRow = new ArrayRowData(columnTypes.size, this.rows.size, this.columnMapping) - - realRow.indices.foreach { - index => - realRow(index) = if (row(index) == null) { - null - } else { - this.decoder.decode( this.columnTypes(index).dataType, row(index) ) + this.decoder.decode( this.columnTypes(index).dataType, row(index), charset ) } } diff --git a/mysql-async/README.md b/mysql-async/README.md index 76ee87ce..855cf193 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -43,6 +43,7 @@ string | String var_string | String varcgar | String time | scala.concurrent.Duration +blob | Array[Byte] Now when you're using prepared statements: diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 736731a4..9861b039 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -25,6 +25,35 @@ import org.joda.time._ class BinaryRowEncoder( charset : Charset ) { private final val stringEncoder = new StringEncoder(charset) + private final val encoders = Map[Class[_],BinaryEncoder]( + classOf[String] -> this.stringEncoder, + classOf[BigInt] -> this.stringEncoder, + classOf[BigDecimal] -> this.stringEncoder, + classOf[java.math.BigDecimal] -> this.stringEncoder, + classOf[java.math.BigInteger] -> this.stringEncoder, + classOf[Byte] -> ByteEncoder, + classOf[java.lang.Byte] -> ByteEncoder, + classOf[Short] -> ShortEncoder, + classOf[java.lang.Short] -> ShortEncoder, + classOf[Int] -> IntegerEncoder, + classOf[java.lang.Integer] -> IntegerEncoder, + classOf[Long] -> LongEncoder, + classOf[java.lang.Long] -> LongEncoder, + classOf[Float] -> FloatEncoder, + classOf[java.lang.Float] -> FloatEncoder, + classOf[Double] -> DoubleEncoder, + classOf[java.lang.Double] -> DoubleEncoder, + classOf[LocalDateTime] -> LocalDateTimeEncoder, + classOf[DateTime] -> DateTimeEncoder, + classOf[LocalDate] -> LocalDateEncoder, + classOf[java.util.Date] -> JavaDateEncoder, + classOf[java.sql.Timestamp] -> SQLTimestampEncoder, + classOf[java.sql.Date] -> SQLDateEncoder, + classOf[java.sql.Time] -> SQLTimeEncoder, + classOf[scala.concurrent.duration.FiniteDuration] -> DurationEncoder, + classOf[Array[Byte]] -> ByteArrayEncoder, + classOf[Boolean] -> BooleanEncoder + ) def encode( values : Seq[Any] ) : ChannelBuffer = { @@ -52,32 +81,28 @@ class BinaryRowEncoder( charset : Charset ) { private def encoderFor( v : Any ) : BinaryEncoder = { - v match { - case value : String => this.stringEncoder - case integer : BigInt => this.stringEncoder - case integerJava : java.math.BigInteger => this.stringEncoder - case decimal : BigDecimal => this.stringEncoder - case decimalJava : java.math.BigDecimal => this.stringEncoder - case value : Byte => ByteEncoder - case v : java.lang.Byte => ByteEncoder - case value : Short => ShortEncoder - case v : java.lang.Short => ShortEncoder - case value : Int => IntegerEncoder - case v : java.lang.Integer => IntegerEncoder - case value : Long => LongEncoder - case v : java.lang.Long => LongEncoder - case value : Float => FloatEncoder - case v : java.lang.Float => FloatEncoder - case value : Double => DoubleEncoder - case v : java.lang.Double => DoubleEncoder - case v : ReadableDateTime => DateTimeEncoder - case v : ReadableInstant => ReadableInstantEncoder - case v : LocalDateTime => LocalDateTimeEncoder - case v : java.sql.Timestamp => TimestampEncoder - case d : java.sql.Date => SQLDateEncoder - case v : java.util.Calendar => CalendarEncoder - case v : java.util.Date => JavaDateEncoder - case d : LocalDate => DateEncoder + this.encoders.get(v.getClass) match { + case Some(encoder) => encoder + case None => { + v match { + case v : CharSequence => this.stringEncoder + case v : BigInt => this.stringEncoder + case v : java.math.BigInteger => this.stringEncoder + case v : BigDecimal => this.stringEncoder + case v : java.math.BigDecimal => this.stringEncoder + case v : ReadableDateTime => DateTimeEncoder + case v : ReadableInstant => ReadableInstantEncoder + case v : LocalDateTime => LocalDateTimeEncoder + case v : java.sql.Timestamp => SQLTimestampEncoder + case v : java.sql.Date => SQLDateEncoder + case v : java.util.Calendar => CalendarEncoder + case v : LocalDate => LocalDateEncoder + case v : LocalTime => LocalTimeEncoder + case v : java.sql.Time => SQLTimeEncoder + case v : scala.concurrent.duration.Duration => DurationEncoder + case v : java.util.Date => JavaDateEncoder + } + } } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala index e4322631..73ba6339 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala @@ -48,7 +48,7 @@ object TimeDecoder extends BinaryDecoder { buffer.readUnsignedByte().hours + buffer.readUnsignedByte().minutes + buffer.readUnsignedByte().seconds + - (buffer.readUnsignedInt().millis * 1000 ) + buffer.readUnsignedInt().micros if ( isNegative ) { duration.neg() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala new file mode 100644 index 00000000..3f9df6dd --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala @@ -0,0 +1,30 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer + +object BooleanEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val boolean = value.asInstanceOf[Boolean] + if ( boolean ) { + buffer.writeByte(1) + } else { + buffer.writeByte(0) + } + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala new file mode 100644 index 00000000..6a57951d --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala @@ -0,0 +1,30 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper + +object ByteArrayEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val bytes = value.asInstanceOf[Array[Byte]] + + buffer.writeLength(bytes.length) + buffer.writeBytes(bytes) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala new file mode 100644 index 00000000..35f3251f --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala @@ -0,0 +1,63 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import scala.concurrent.duration._ + +object DurationEncoder extends BinaryEncoder { + + private final val Zero = 0.seconds + + def encode(value: Any, buffer: ChannelBuffer) { + val duration = value.asInstanceOf[Duration] + + val days = duration.toDays + val hoursDuration = duration - days.days + val hours = hoursDuration.toHours + val minutesDuration = hoursDuration - hours.hours + val minutes = minutesDuration.toMinutes + val secondsDuration = minutesDuration - minutes.minutes + val seconds = secondsDuration.toSeconds + val microsDuration = secondsDuration - seconds.seconds + val micros = microsDuration.toMicros + + val hasMicros = micros != 0 + + if ( hasMicros ) { + buffer.writeByte(12) + } else { + buffer.writeByte(8) + } + + if (duration > Zero) { + buffer.writeByte(0) + } else { + buffer.writeByte(1) + } + + buffer.writeInt(days.asInstanceOf[Int]) + buffer.writeByte(hours.asInstanceOf[Int]) + buffer.writeByte(minutes.asInstanceOf[Int]) + buffer.writeByte(seconds.asInstanceOf[Int]) + + if ( hasMicros ) { + buffer.writeInt(micros.asInstanceOf[Int]) + } + + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala similarity index 95% rename from mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala rename to mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala index 4cc9515d..00d55600 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala @@ -20,7 +20,7 @@ import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.LocalDate import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException -object DateEncoder extends BinaryEncoder { +object LocalDateEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[LocalDate] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala new file mode 100644 index 00000000..f1d95d6e --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala @@ -0,0 +1,49 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.LocalTime + +object LocalTimeEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val time = value.asInstanceOf[LocalTime] + val hasMillis = time.getMillisOfSecond != 0 + + if ( hasMillis ) { + buffer.writeByte(12) + } else { + buffer.writeByte(8) + } + + if ( time.getMillisOfDay > 0 ) { + buffer.writeByte(0) + } else { + buffer.writeByte(1) + } + + buffer.writeInt(0) + + buffer.writeByte(time.getHourOfDay) + buffer.writeByte(time.getMinuteOfHour) + buffer.writeByte(time.getSecondOfMinute) + + if ( hasMillis ) { + buffer.writeInt(time.getMillisOfSecond * 1000) + } + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala index b404a486..ead9dc3e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala @@ -23,6 +23,6 @@ object SQLDateEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[java.sql.Date] - DateEncoder.encode(new LocalDate(date), buffer) + LocalDateEncoder.encode(new LocalDate(date), buffer) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala new file mode 100644 index 00000000..bead9f5e --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala @@ -0,0 +1,28 @@ +/* + * 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.mysql.binary.encoder + +import org.jboss.netty.buffer.ChannelBuffer +import org.joda.time.LocalTime + +object SQLTimeEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { + val sqlTime = value.asInstanceOf[java.sql.Time].getTime + val time = new LocalTime( sqlTime ) + LocalTimeEncoder.encode(time, buffer) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala similarity index 94% rename from mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala rename to mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala index dfe6bd05..50b9ad52 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/TimestampEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.DateTime -object TimestampEncoder extends BinaryEncoder { +object SQLTimestampEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[java.sql.Timestamp] DateTimeEncoder.encode(new DateTime(date), buffer) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala index 0c4ba848..27c2a382 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -18,6 +18,8 @@ package com.github.mauricio.async.db.mysql.column import com.github.mauricio.async.db.column._ import scala.annotation.switch +import org.jboss.netty.buffer.ChannelBuffer +import java.nio.charset.Charset object MySQLColumnDecoderRegistry { final val Instance = new MySQLColumnDecoderRegistry() @@ -25,8 +27,8 @@ object MySQLColumnDecoderRegistry { class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { - def decode(kind: Int, value: String): Any = { - this.decoderFor(kind).decode(value) + override def decode(kind: Int, value: ChannelBuffer, charset: Charset): Any = { + decoderFor(kind).decode(value, charset) } def decoderFor( kind : Int ) : ColumnDecoder = { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala index a2b3ef7b..b66abfc2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala @@ -38,7 +38,8 @@ class ResultSetRowDecoder( charset : Charset ) extends MessageDecoder { if ( buffer.getByte(buffer.readerIndex()) == NULL ) { row += null } else { - row += buffer.readLengthEncodedString(charset) + val length = buffer.readBinaryLength.asInstanceOf[Int] + row += buffer.readSlice(length) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala index 5d3fec8b..b798aad6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala @@ -18,23 +18,24 @@ package com.github.mauricio.async.db.mysql.message.server import scala.collection.mutable import scala.collection.mutable.ArrayBuffer +import org.jboss.netty.buffer.ChannelBuffer class ResultSetRowMessage extends ServerMessage( ServerMessage.Row ) - with mutable.Buffer[String] + with mutable.Buffer[ChannelBuffer] { - private val buffer = new ArrayBuffer[String]() + private val buffer = new ArrayBuffer[ChannelBuffer]() def length: Int = buffer.length - def apply(idx: Int): String = buffer(idx) + def apply(idx: Int): ChannelBuffer = buffer(idx) - def update(n: Int, newelem: String) { + def update(n: Int, newelem: ChannelBuffer) { buffer.update(n, newelem) } - def +=(elem: String): this.type = { + def +=(elem: ChannelBuffer): this.type = { this.buffer += elem this } @@ -43,19 +44,19 @@ class ResultSetRowMessage this.buffer.clear() } - def +=:(elem: String): this.type = { + def +=:(elem: ChannelBuffer): this.type = { this.buffer.+=:(elem) this } - def insertAll(n: Int, elems: Traversable[String]) { + def insertAll(n: Int, elems: Traversable[ChannelBuffer]) { this.buffer.insertAll(n, elems) } - def remove(n: Int): String = { + def remove(n: Int): ChannelBuffer = { this.buffer.remove(n) } - override def iterator: Iterator[String] = this.buffer.iterator + override def iterator: Iterator[ChannelBuffer] = this.buffer.iterator } \ No newline at end of file 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 2a7e8d43..675ad32f 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 @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.postgresql.column object ColumnTypes { final val Bigserial = 20 + final val BigserialArray = 1016 final val Char = 18 final val CharArray = 1002 final val Smallint = 21 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 ac0b9511..df40394b 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 @@ -19,69 +19,90 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.column._ import com.github.mauricio.async.db.postgresql.column.ColumnTypes._ import scala.annotation.switch +import java.nio.charset.Charset +import org.jboss.netty.util.CharsetUtil +import org.jboss.netty.buffer.ChannelBuffer object PostgreSQLColumnDecoderRegistry { val Instance = new PostgreSQLColumnDecoderRegistry() } -class PostgreSQLColumnDecoderRegistry extends ColumnDecoderRegistry { - - def decode(kind: Int, value: String) : Any = decoderFor(kind).decode(value) +class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) extends ColumnDecoderRegistry { + + private final val stringArrayDecoder = new ArrayDecoder(StringEncoderDecoder) + private final val booleanArrayDecoder = new ArrayDecoder(BooleanEncoderDecoder) + private final val charArrayDecoder = new ArrayDecoder(CharEncoderDecoder) + private final val longArrayDecoder = new ArrayDecoder(LongEncoderDecoder) + private final val shortArrayDecoder = new ArrayDecoder(ShortEncoderDecoder) + private final val integerArrayDecoder = new ArrayDecoder(IntegerEncoderDecoder) + private final val bigDecimalArrayDecoder = new ArrayDecoder(BigDecimalEncoderDecoder) + private final val floatArrayDecoder = new ArrayDecoder(FloatEncoderDecoder) + private final val doubleArrayDecoder = new ArrayDecoder(DoubleEncoderDecoder) + private final val timestampArrayDecoder = new ArrayDecoder(TimestampEncoderDecoder.Instance) + private final val timestampWithTimezoneArrayDecoder = new ArrayDecoder(TimestampWithTimezoneEncoderDecoder) + private final val dateArrayDecoder = new ArrayDecoder(DateEncoderDecoder) + private final val timeArrayDecoder = new ArrayDecoder(TimeEncoderDecoder.Instance) + private final val timeWithTimestampArrayDecoder = new ArrayDecoder(TimeWithTimezoneEncoderDecoder) + + override def decode(kind: Int, value: ChannelBuffer, charset: Charset): Any = { + decoderFor(kind).decode(value, charset) + } def decoderFor(kind: Int): ColumnDecoder = { (kind : @switch) match { case Boolean => BooleanEncoderDecoder - case BooleanArray => new ArrayDecoder(BooleanEncoderDecoder) + case BooleanArray => this.booleanArrayDecoder case ColumnTypes.Char => CharEncoderDecoder - case CharArray => new ArrayDecoder(CharEncoderDecoder) + case CharArray => this.charArrayDecoder case Bigserial => LongEncoderDecoder + case BigserialArray => this.longArrayDecoder case Smallint => ShortEncoderDecoder - case SmallintArray => new ArrayDecoder(ShortEncoderDecoder) + case SmallintArray => this.shortArrayDecoder case ColumnTypes.Integer => IntegerEncoderDecoder - case IntegerArray => new ArrayDecoder(IntegerEncoderDecoder) + case IntegerArray => this.integerArrayDecoder case ColumnTypes.Numeric => BigDecimalEncoderDecoder - case NumericArray => new ArrayDecoder(BigDecimalEncoderDecoder) + case NumericArray => this.bigDecimalArrayDecoder case Real => FloatEncoderDecoder - case RealArray => new ArrayDecoder(FloatEncoderDecoder) + case RealArray => this.floatArrayDecoder case ColumnTypes.Double => DoubleEncoderDecoder - case DoubleArray => new ArrayDecoder(DoubleEncoderDecoder) + case DoubleArray => this.doubleArrayDecoder case Text => StringEncoderDecoder - case TextArray => new ArrayDecoder(StringEncoderDecoder) + case TextArray => this.stringArrayDecoder case Varchar => StringEncoderDecoder - case VarcharArray => new ArrayDecoder(StringEncoderDecoder) + case VarcharArray => this.stringArrayDecoder case Bpchar => StringEncoderDecoder - case BpcharArray => new ArrayDecoder(StringEncoderDecoder) + case BpcharArray => this.stringArrayDecoder case Timestamp => TimestampEncoderDecoder.Instance - case TimestampArray => new ArrayDecoder(TimestampEncoderDecoder.Instance) + case TimestampArray => this.timestampArrayDecoder case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder - case TimestampWithTimezoneArray => new ArrayDecoder(TimestampWithTimezoneEncoderDecoder) + case TimestampWithTimezoneArray => this.timestampWithTimezoneArrayDecoder case Date => DateEncoderDecoder - case DateArray => new ArrayDecoder(DateEncoderDecoder) + case DateArray => this.dateArrayDecoder case Time => TimeEncoderDecoder.Instance - case TimeArray => new ArrayDecoder(TimeEncoderDecoder.Instance) + case TimeArray => this.timeArrayDecoder case TimeWithTimezone => TimeWithTimezoneEncoderDecoder - case TimeWithTimezoneArray => new ArrayDecoder(TimeWithTimezoneEncoderDecoder) + case TimeWithTimezoneArray => this.timeWithTimestampArrayDecoder - case OIDArray => new ArrayDecoder(StringEncoderDecoder) - case MoneyArray => new ArrayDecoder(StringEncoderDecoder) - case NameArray => new ArrayDecoder(StringEncoderDecoder) - case UUIDArray => new ArrayDecoder(StringEncoderDecoder) - case XMLArray => new ArrayDecoder(StringEncoderDecoder) + case OIDArray => this.stringArrayDecoder + case MoneyArray => this.stringArrayDecoder + case NameArray => this.stringArrayDecoder + case UUIDArray => this.stringArrayDecoder + case XMLArray => this.stringArrayDecoder case ByteA => ByteArrayEncoderDecoder case _ => StringEncoderDecoder From 8e16a9b114ca1cc8fdf51659946628f3ec304eb7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 15 May 2013 00:11:36 -0300 Subject: [PATCH 104/357] Query workflow spec --- .../mysql/column/ByteArrayColumnDecoder.scala | 34 ++++++++++ .../column/MySQLColumnDecoderRegistry.scala | 1 + .../mysql/codec/MySQLFrameDecoderSpec.scala | 66 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala new file mode 100644 index 00000000..ffd4cf16 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala @@ -0,0 +1,34 @@ +/* + * 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.mysql.column + +import com.github.mauricio.async.db.column.ColumnDecoder +import org.jboss.netty.buffer.ChannelBuffer +import java.nio.charset.Charset + +object ByteArrayColumnDecoder extends ColumnDecoder { + + override def decode(value: ChannelBuffer, charset: Charset): Any = { + val bytes = new Array[Byte](value.readableBytes()) + value.readBytes(bytes) + value + } + + def decode(value: String): Any = { + throw new UnsupportedOperationException("This method should never be called for byte arrays") + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala index 27c2a382..f180d647 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -52,6 +52,7 @@ class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { case ColumnTypes.FIELD_TYPE_VAR_STRING => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_VARCHAR => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_YEAR => ShortEncoderDecoder + case ColumnTypes.FIELD_TYPE_BLOB => ByteArrayColumnDecoder case _ => StringEncoderDecoder } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index 8619cc13..5cad43b0 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -24,6 +24,7 @@ import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification import com.github.mauricio.async.db.mysql.message.server.OkMessage import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes class MySQLFrameDecoderSpec extends Specification { @@ -123,6 +124,40 @@ class MySQLFrameDecoderSpec extends Specification { decoder.totalColumns === 2 + val columnId = createColumnPacket("id", ColumnTypes.FIELD_TYPE_LONG) + val columnName = createColumnPacket("name", ColumnTypes.FIELD_TYPE_VARCHAR) + + embedder.offer(columnId) + + embedder.poll().asInstanceOf[ColumnDefinitionMessage].name === "id" + + decoder.processedColumns === 1 + + embedder.offer(columnName) + + embedder.poll().asInstanceOf[ColumnDefinitionMessage].name === "name" + + decoder.processedColumns === 2 + + embedder.offer(this.createEOFPacket()) + + embedder.poll().asInstanceOf[ColumnProcessingFinishedMessage].eofMessage.flags === 8765 + + decoder.processingColumns must beFalse + + val row = ChannelUtils.packetBuffer() + row.writeLenghtEncodedString("1", charset) + row.writeLenghtEncodedString("some name", charset) + row.writePacketLength() + + embedder.offer(row) + + embedder.poll().isInstanceOf[ResultSetRowMessage] must beTrue + + embedder.offer(this.createEOFPacket()) + + decoder.isInQuery must beFalse + } @@ -155,5 +190,36 @@ class MySQLFrameDecoderSpec extends Specification { buffer } + def createColumnPacket( name : String, columnType : Int ) : ChannelBuffer = { + val buffer = ChannelUtils.packetBuffer() + buffer.writeLenghtEncodedString("def", charset) + buffer.writeLenghtEncodedString("some_schema", charset) + buffer.writeLenghtEncodedString("some_table", charset) + buffer.writeLenghtEncodedString("some_table", charset) + buffer.writeLenghtEncodedString(name, charset) + buffer.writeLenghtEncodedString(name, charset) + buffer.writeLength(12) + buffer.writeShort(0x03) + buffer.writeInt(10) + buffer.writeByte(columnType) + buffer.writeShort(76) + buffer.writeByte(0) + buffer.writeShort(56) + buffer.writePacketLength() + buffer + } + + def createEOFPacket() : ChannelBuffer = { + val buffer = ChannelUtils.packetBuffer() + buffer.writeByte(0xfe) + buffer.writeShort(879) + buffer.writeShort(8765) + + buffer.writePacketLength() + + buffer + } + + } From 3cd7df323d1d3d784a1f6be0582d91b26c278877 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 16 May 2013 13:00:34 -0300 Subject: [PATCH 105/357] Fixed encoder for prepared statements on MySQL, still needs more specs --- README.markdown | 5 +- ...ConnectionStillRunningQueryException.scala | 4 +- .../mauricio/async/db/util/ChannelUtils.scala | 6 +- .../mauricio/async/db/mysql/MySQLHelper.java | 3 +- .../async/db/mysql/MySQLConnection.scala | 27 ++++-- .../db/mysql/binary/BinaryRowDecoder.scala | 13 +-- .../db/mysql/binary/BinaryRowEncoder.scala | 30 +++++-- .../mysql/binary/encoder/BinaryEncoder.scala | 2 + .../mysql/binary/encoder/BooleanEncoder.scala | 3 + .../binary/encoder/ByteArrayEncoder.scala | 4 + .../db/mysql/binary/encoder/ByteEncoder.scala | 9 ++ .../binary/encoder/CalendarEncoder.scala | 4 + .../binary/encoder/DateTimeEncoder.scala | 6 +- .../mysql/binary/encoder/DoubleEncoder.scala | 3 + .../binary/encoder/DurationEncoder.scala | 3 + .../mysql/binary/encoder/FloatEncoder.scala | 3 + .../mysql/binary/encoder/IntegerEncoder.scala | 3 + .../binary/encoder/JavaDateEncoder.scala | 3 + .../binary/encoder/LocalDateEncoder.scala | 3 + .../binary/encoder/LocalDateTimeEncoder.scala | 3 + .../binary/encoder/LocalTimeEncoder.scala | 3 + .../db/mysql/binary/encoder/LongEncoder.scala | 3 + .../encoder/ReadableInstantEncoder.scala | 3 + .../mysql/binary/encoder/SQLDateEncoder.scala | 3 + .../mysql/binary/encoder/SQLTimeEncoder.scala | 3 + .../binary/encoder/SQLTimestampEncoder.scala | 5 +- .../mysql/binary/encoder/ShortEncoder.scala | 3 + .../mysql/binary/encoder/StringEncoder.scala | 7 +- .../mysql/codec/MySQLConnectionHandler.scala | 60 ++++++++----- .../db/mysql/codec/MySQLFrameDecoder.scala | 78 +++++++++------- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 11 ++- .../async/db/mysql/column/ColumnTypes.scala | 32 ++++++- ...amAndColumnProcessingFinishedDecoder.scala | 26 ++++++ .../PreparedStatementExecuteEncoder.scala | 13 ++- .../PreparedStatementExecuteMessage.scala | 7 +- .../client/PreparedStatementMessage.scala | 2 +- .../server/ColumnDefinitionMessage.scala | 9 +- ...amAndColumnProcessingFinishedMessage.scala | 20 +++++ .../mysql/message/server/ServerMessage.scala | 7 +- .../async/db/mysql/ConnectionHelper.scala | 15 ++++ .../db/mysql/PreparedStatementsSpec.scala | 88 ++++++++++++++++++- .../mysql/codec/MySQLFrameDecoderSpec.scala | 2 - .../db/postgresql/PostgreSQLConnection.scala | 1 + .../SingleThreadedAsyncObjectPoolSpec.scala | 2 +- project/Build.scala | 1 - 45 files changed, 448 insertions(+), 93 deletions(-) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/exceptions/ConnectionStillRunningQueryException.scala (87%) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamAndColumnProcessingFinishedMessage.scala diff --git a/README.markdown b/README.markdown index 8bcd443e..bd4430b1 100644 --- a/README.markdown +++ b/README.markdown @@ -54,11 +54,14 @@ Both clients will let you set the database encoding for something else. Unless y or **columns** encoding but **IT IS NOT**, this is just the connection encoding that is used between client and servers doing communication. -When you change the encoding of the **connection** you are not affecting the your database's encoding and your columns +When you change the encoding of the **connection** you are not affecting your database's encoding and your columns **WILL NOT** be stored with the connection encoding. If the connection and database/column encoding is different, your database will automatically translate from the connection encoding to the correct encoding and all your data will be safely stored at your database/column encoding. +This is as long as you are using the correct string types, BLOB columns will not be translated since they're supposed +to hold a stream of bytes. + So, just don't touch it and be happy. ## What are the design goals? diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionStillRunningQueryException.scala similarity index 87% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionStillRunningQueryException.scala index a184fb9a..aaf028d6 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/ConnectionStillRunningQueryException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionStillRunningQueryException.scala @@ -14,9 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.exceptions - -import com.github.mauricio.async.db.exceptions.DatabaseException +package com.github.mauricio.async.db.exceptions class ConnectionStillRunningQueryException( connectionCount : Long, readyForQuery : Boolean ) extends DatabaseException ( "[%s] - There is a query still being run here - readyForQuery -> %s".format( diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala index f387de59..adf7662b 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -116,11 +116,15 @@ object ChannelUtils { } def packetBuffer( estimate : Int = 1024 ) : ChannelBuffer = { - val buffer = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, estimate) + val buffer = mysqlBuffer(estimate) buffer.writeInt(0) buffer } + def mysqlBuffer( estimate : Int = 1024 ) : ChannelBuffer = { + ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, estimate) + } + } diff --git a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java index c53ce4eb..b53f0d80 100644 --- a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java +++ b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java @@ -4,7 +4,8 @@ public class MySQLHelper { - public static final String dumpAsHex(ChannelBuffer buffer, int length) { + public static final String dumpAsHex(ChannelBuffer buffer) { + int length = buffer.readableBytes(); byte[] byteBuffer = new byte[length]; buffer.markReaderIndex(); diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 97b38d83..304ae9d7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -31,9 +31,12 @@ import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure import scala.util.Success +import java.util.concurrent.atomic.AtomicLong +import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException object MySQLConnection { - val log = Log.get[MySQLConnection] + final val log = Log.get[MySQLConnection] + final val Counter = new AtomicLong() } class MySQLConnection( @@ -50,6 +53,7 @@ class MySQLConnection( // validate that this charset is supported charsetMapper.toInt(configuration.charset) + private final val connectionCount = MySQLConnection.Counter.incrementAndGet() private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, columnDecoderRegistry) @@ -149,9 +153,11 @@ class MySQLConnection( } def sendQuery(query: String): Future[QueryResult] = { - this.queryPromise = Promise[QueryResult] + this.validateIsReadyForQuery() + val promise = Promise[QueryResult] + this.queryPromise = promise this.connectionHandler.write(new QueryMessage(query)) - this.queryPromise.future + promise.future } private def failQueryPromise(t: Throwable) { @@ -175,7 +181,7 @@ class MySQLConnection( } - private def isQuerying: Boolean = this.queryPromise != null + private def isQuerying: Boolean = this.queryPromise != null && !this.queryPromise.isCompleted def onResultSet(resultSet: ResultSet, message: EOFMessage) { if (this.isQuerying) { @@ -197,8 +203,17 @@ class MySQLConnection( def isConnected: Boolean = this.connectionHandler.isConnected def sendPreparedStatement(query: String, values: Seq[Any]): Future[QueryResult] = { - this.queryPromise = Promise[QueryResult] + this.validateIsReadyForQuery() + val promise = Promise[QueryResult] + this.queryPromise = promise this.connectionHandler.write(new PreparedStatementMessage(query, values)) - this.queryPromise.future + promise.future } + + private def validateIsReadyForQuery() { + if ( this.queryPromise != null && !this.queryPromise.isCompleted ) { + throw new ConnectionStillRunningQueryException(this.connectionCount, false ) + } + } + } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index f0b76a99..a29fee31 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -24,6 +24,7 @@ import com.github.mauricio.async.db.util._ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import scala.collection.mutable.ArrayBuffer +import com.github.mauricio.async.db.mysql.MySQLHelper object BinaryRowDecoder { final val log = Log.get[BinaryRowDecoder] @@ -39,14 +40,14 @@ class BinaryRowDecoder(charset: Charset) { def decode(buffer: ChannelBuffer, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { - //log.debug("columns are {}", columns) + log.debug("columns are {}", columns) - //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer, buffer.readableBytes())) - //PrintUtils.printArray("bitmap", buffer) + log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer)) + PrintUtils.printArray("bitmap", buffer) val bitMap = BitMap.fromBuffer( columns.size + 7 + 2, buffer ) - //log.debug("bitmap is {}", bitMap) + log.debug("bitmap is {}", bitMap) val row = new ArrayBuffer[Any](columns.size) @@ -57,14 +58,14 @@ class BinaryRowDecoder(charset: Charset) { } else { val decoder = decoderFor(columns(index - BitMapOffset).columnType) - //log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") + log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") row += decoder.decode(buffer) } } }) - //log.debug("values are {}", row) + log.debug("values are {}", row) if (buffer.readableBytes() != 0) { throw new BufferNotFullyConsumedException(buffer) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 9861b039..7ae93b06 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -19,11 +19,18 @@ package com.github.mauricio.async.db.mysql.binary import java.nio.charset.Charset import com.github.mauricio.async.db.mysql.binary.encoder._ import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import com.github.mauricio.async.db.util.{BitMap, ChannelUtils} +import com.github.mauricio.async.db.util._ import org.joda.time._ +import scala.Some + +object BinaryRowEncoder { + final val log = Log.get[BinaryRowEncoder] +} class BinaryRowEncoder( charset : Charset ) { + import BinaryRowEncoder.log + private final val stringEncoder = new StringEncoder(charset) private final val encoders = Map[Class[_],BinaryEncoder]( classOf[String] -> this.stringEncoder, @@ -57,26 +64,37 @@ class BinaryRowEncoder( charset : Charset ) { def encode( values : Seq[Any] ) : ChannelBuffer = { - val bitMapBuffer = ChannelUtils.packetBuffer(values.length) - val buffer = ChannelUtils.packetBuffer() - val bitMap = BitMap.forSize(values.length) + val bitMapBuffer = ChannelUtils.mysqlBuffer(values.length) + val parameterTypesBuffer = ChannelUtils.mysqlBuffer(values.size * 2) + val parameterValuesBuffer = ChannelUtils.mysqlBuffer() + val bitMap = new BitMap( new Array[Byte]( (values.size + 7) / 8 ) ) var index = 0 + var allNulls = true while ( index < values.length ) { val value = values(index) if ( value == null ) { bitMap.set(index) } else { + allNulls = false val encoder = encoderFor(value) - encoder.encode(value, buffer) + parameterTypesBuffer.writeShort(encoder.encodesTo) + encoder.encode(value, parameterValuesBuffer) } index += 1 } bitMap.write(bitMapBuffer) + if ( allNulls ) { + bitMapBuffer.writeByte(0) + } else { + bitMapBuffer.writeByte(1) + } + + log.debug(s"bitMap $bitMap types size ${parameterTypesBuffer.readableBytes()} parameterValues ${parameterValuesBuffer.readableBytes()}") - ChannelBuffers.wrappedBuffer( bitMapBuffer, buffer ) + ChannelBuffers.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) } private def encoderFor( v : Any ) : BinaryEncoder = { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala index b5c74373..607e59ac 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala @@ -22,4 +22,6 @@ trait BinaryEncoder { def encode( value : Any, buffer : ChannelBuffer ) + def encodesTo : Int + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala index 3f9df6dd..fc6c8901 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes object BooleanEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { @@ -27,4 +28,6 @@ object BooleanEncoder extends BinaryEncoder { buffer.writeByte(0) } } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TINY } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala index 6a57951d..e98516bc 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import com.github.mauricio.async.db.mysql.column.ColumnTypes object ByteArrayEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { @@ -27,4 +28,7 @@ object ByteArrayEncoder extends BinaryEncoder { buffer.writeLength(bytes.length) buffer.writeBytes(bytes) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_BLOB + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala index 6e9051ec..8ceac33e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala @@ -17,9 +17,18 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.util.Log object ByteEncoder extends BinaryEncoder { + + private final val log = Log.getByName(this.getClass.getName) + def encode(value: Any, buffer: ChannelBuffer) { + log.debug("Received value {}", value) buffer.writeByte(value.asInstanceOf[Byte]) + log.debug("wrote byte {}", buffer.getByte(buffer.writerIndex() - 1)) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TINY } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala index 8f94e94d..021c36f1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala @@ -19,10 +19,14 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import java.util.Calendar import org.joda.time.DateTime +import com.github.mauricio.async.db.mysql.column.ColumnTypes object CalendarEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val calendar = value.asInstanceOf[Calendar] DateTimeEncoder.encode(new DateTime(calendar), buffer) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala index 7ad2f2c3..6ed7062b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time._ import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException +import com.github.mauricio.async.db.mysql.column.ColumnTypes object DateTimeEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { @@ -31,7 +32,10 @@ object DateTimeEncoder extends BinaryEncoder { buffer.writeByte(instant.getHourOfDay) buffer.writeByte(instant.getMinuteOfHour) buffer.writeByte(instant.getSecondOfMinute) - buffer.writeInt(instant.getMillisOfDay) + buffer.writeInt(instant.getMillisOfDay * 1000) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala index ea97cb99..7c9b2899 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala @@ -17,9 +17,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes object DoubleEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { buffer.writeDouble(value.asInstanceOf[Double]) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_DOUBLE } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala index 35f3251f..cd7a4b4e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import scala.concurrent.duration._ +import com.github.mauricio.async.db.mysql.column.ColumnTypes object DurationEncoder extends BinaryEncoder { @@ -60,4 +61,6 @@ object DurationEncoder extends BinaryEncoder { } } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIME } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala index 98c574f7..c65591b6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala @@ -17,9 +17,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes object FloatEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { buffer.writeFloat(value.asInstanceOf[Float]) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_FLOAT } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala index f412520c..c7237223 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala @@ -17,9 +17,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes object IntegerEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { buffer.writeInt(value.asInstanceOf[Int]) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_LONG } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala index 9ff477a5..680ecccd 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala @@ -18,10 +18,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.DateTime +import com.github.mauricio.async.db.mysql.column.ColumnTypes object JavaDateEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[java.util.Date] DateTimeEncoder.encode( new DateTime(date), buffer ) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala index 00d55600..a16cf463 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.LocalDate import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException +import com.github.mauricio.async.db.mysql.column.ColumnTypes object LocalDateEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { @@ -30,4 +31,6 @@ object LocalDateEncoder extends BinaryEncoder { buffer.writeByte(date.getDayOfMonth) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_DATE } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala index 82191d90..7d207eec 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala @@ -18,10 +18,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.{DateTimeZone, LocalDateTime} +import com.github.mauricio.async.db.mysql.column.ColumnTypes object LocalDateTimeEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[LocalDateTime] DateTimeEncoder.encode(date.toDateTime( DateTimeZone.UTC ), buffer) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala index f1d95d6e..b4b73fce 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.LocalTime +import com.github.mauricio.async.db.mysql.column.ColumnTypes object LocalTimeEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { @@ -46,4 +47,6 @@ object LocalTimeEncoder extends BinaryEncoder { buffer.writeInt(time.getMillisOfSecond * 1000) } } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIME } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala index 9c64f2fa..d0182b45 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala @@ -17,9 +17,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes object LongEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { buffer.writeLong(value.asInstanceOf[Long]) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_LONGLONG } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala index da1e43bb..20f8f0a7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala @@ -18,10 +18,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.{DateTime, ReadableInstant} +import com.github.mauricio.async.db.mysql.column.ColumnTypes object ReadableInstantEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[ReadableInstant] DateTimeEncoder.encode(new DateTime(date.getMillis), buffer) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala index ead9dc3e..68d6173a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.LocalDate +import com.github.mauricio.async.db.mysql.column.ColumnTypes object SQLDateEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { @@ -25,4 +26,6 @@ object SQLDateEncoder extends BinaryEncoder { LocalDateEncoder.encode(new LocalDate(date), buffer) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_DATE } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala index bead9f5e..cc53820e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.LocalTime +import com.github.mauricio.async.db.mysql.column.ColumnTypes object SQLTimeEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { @@ -25,4 +26,6 @@ object SQLTimeEncoder extends BinaryEncoder { val time = new LocalTime( sqlTime ) LocalTimeEncoder.encode(time, buffer) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIME } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala index 50b9ad52..16991ec3 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala @@ -18,10 +18,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import org.joda.time.DateTime +import com.github.mauricio.async.db.mysql.column.ColumnTypes object SQLTimestampEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[java.sql.Timestamp] - DateTimeEncoder.encode(new DateTime(date), buffer) + DateTimeEncoder.encode(new DateTime(date.getTime), buffer) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala index ff05bc19..9746c874 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala @@ -17,9 +17,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes object ShortEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { buffer.writeShort(value.asInstanceOf[Short]) } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_SHORT } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala index ca8fe44f..463c9180 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala @@ -19,9 +19,14 @@ package com.github.mauricio.async.db.mysql.binary.encoder import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.column.ColumnTypes class StringEncoder( charset : Charset ) extends BinaryEncoder { + def encode(value: Any, buffer: ChannelBuffer) { buffer.writeLenghtEncodedString(value.toString, charset) } -} + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_VARCHAR + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 14c833e9..1314709c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -61,6 +61,7 @@ class MySQLConnectionHandler( private final val connectionPromise = Promise[MySQLConnectionHandler] private final val decoder = new MySQLFrameDecoder(configuration.charset) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) + private final val currentParameters = new ArrayBuffer[ColumnDefinitionMessage]() private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() private final val parsedStatements = new HashMap[String,PreparedStatementHolder]() private final val binaryRowDecoder = new BinaryRowDecoder(configuration.charset) @@ -97,6 +98,8 @@ class MySQLConnectionHandler( override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) { + log.debug("Message received {}", e.getMessage) + e.getMessage match { case m: ServerMessage => { (m.kind: @switch) match { @@ -133,19 +136,7 @@ class MySQLConnectionHandler( this.currentColumns += message } case ServerMessage.ColumnDefinitionFinished => { - this.currentQuery = new MutableResultSet[ColumnData]( - this.currentColumns.map( c => new ColumnData(c.name, c.columnType) ), - configuration.charset, - columnDecoderRegistry - ) - - if ( this.currentPreparedStatementHolder != null ) { - this.parsedStatements.put( this.currentPreparedStatementHolder.statement, this.currentPreparedStatementHolder ) - this.executePreparedStatement( this.currentPreparedStatementHolder.statementId, this.currentPreparedStatement.values ) - this.currentPreparedStatementHolder = null - this.currentPreparedStatement = null - } - + this.onColumnDefinitionFinished() } case ServerMessage.PreparedStatementPrepareResponse => { this.onPreparedStatementPrepareResponse(m.asInstanceOf[PreparedStatementPrepareResponse]) @@ -159,6 +150,9 @@ class MySQLConnectionHandler( } case ServerMessage.ParamProcessingFinished => { } + case ServerMessage.ParamAndColumnProcessingFinished => { + this.onColumnDefinitionFinished() + } } } } @@ -192,13 +186,17 @@ class MySQLConnectionHandler( } def write( message : PreparedStatementMessage ) { + + this.currentColumns.clear() + this.currentParameters.clear() + + this.currentPreparedStatement = message + this.parsedStatements.get(message.statement) match { case Some( item ) => { - this.currentColumns.clear() - this.executePreparedStatement(item.statementId, message.values) + this.executePreparedStatement(item.statementId, item.columns.size, message.values, item.parameters) } case None => { - this.currentPreparedStatement = message decoder.preparedStatementPrepareStarted() this.currentContext.getChannel.write( new PreparedStatementPrepareMessage(message.statement) ) } @@ -217,6 +215,7 @@ class MySQLConnectionHandler( private def clearQueryState { this.currentColumns.clear() + this.currentParameters.clear() this.currentQuery = null } @@ -228,15 +227,36 @@ class MySQLConnectionHandler( } } - private def executePreparedStatement( statementId : Array[Byte], values : Seq[Any] ) { - decoder.preparedStatementExecuteStarted() + private def executePreparedStatement( statementId : Array[Byte], columnsCount : Int, values : Seq[Any], parameters : Seq[ColumnDefinitionMessage] ) { + log.debug("Sending execute prepared statement") + decoder.preparedStatementExecuteStarted(columnsCount, parameters.size) this.currentColumns.clear() - this.currentContext.getChannel.write(new PreparedStatementExecuteMessage( statementId, values )) + this.currentParameters.clear() + this.currentContext.getChannel.write(new PreparedStatementExecuteMessage( statementId, values, parameters )) } private def onPreparedStatementPrepareResponse( message : PreparedStatementPrepareResponse ) { - log.debug("Received prepare response from server {}", message) this.currentPreparedStatementHolder = new PreparedStatementHolder( this.currentPreparedStatement.statement, message) } + def onColumnDefinitionFinished() { + this.currentQuery = new MutableResultSet[ColumnData]( + this.currentColumns.map( c => new ColumnData(c.name, c.columnType) ), + configuration.charset, + columnDecoderRegistry + ) + + if ( this.currentPreparedStatementHolder != null ) { + this.parsedStatements.put( this.currentPreparedStatementHolder.statement, this.currentPreparedStatementHolder ) + this.executePreparedStatement( + this.currentPreparedStatementHolder.statementId, + this.currentPreparedStatementHolder.columns.size, + this.currentPreparedStatement.values, + this.currentPreparedStatementHolder.parameters + ) + this.currentPreparedStatementHolder = null + this.currentPreparedStatement = null + } + } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 661ab8be..52f6fb7f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -26,6 +26,7 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.frame.FrameDecoder +import com.github.mauricio.async.db.mysql.MySQLHelper object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] @@ -33,6 +34,8 @@ object MySQLFrameDecoder { class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { + import MySQLFrameDecoder.log + private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) private final val okDecoder = new OkDecoder(charset) @@ -52,6 +55,8 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { private[codec] var totalColumns = 0L private[codec] var processedColumns = 0L + private var hasReadColumnsCount = false + def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { if (buffer.readableBytes() > 4) { @@ -65,12 +70,16 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val messageType = buffer.getByte(buffer.readerIndex()) - if ( size < 0 ) { + if (size < 0) { throw new NegativeMessageSizeException(messageType, size) } val slice = buffer.readSlice(size) + //val dump = MySQLHelper.dumpAsHex(slice) + + //log.debug(s"Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) + slice.readByte() val decoder = messageType match { @@ -83,7 +92,11 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { if (this.processingParams && this.totalParams > 0) { this.processingParams = false - ParamProcessingFinishedDecoder + if (this.totalColumns == 0) { + ParamAndColumnProcessingFinishedDecoder + } else { + ParamProcessingFinishedDecoder + } } else { if (this.processingColumns) { this.processingColumns = false @@ -99,7 +112,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { if (this.isPreparedStatementPrepare) { this.preparedStatementPrepareDecoder } else { - if ( this.isPreparedStatementExecuteRows ) { + if (this.isPreparedStatementExecuteRows) { null } else { this.clear @@ -131,25 +144,23 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val result = decoder.decode(slice) result match { - case m : PreparedStatementPrepareResponse => { + case m: PreparedStatementPrepareResponse => { + this.hasReadColumnsCount = true this.totalColumns = m.columnsCount this.totalParams = m.paramsCount } - case m : ColumnProcessingFinishedMessage if this.isPreparedStatementPrepare => { + case m: ParamAndColumnProcessingFinishedMessage => { this.clear } - case m : ColumnProcessingFinishedMessage if this.isPreparedStatementExecute => { + case m: ColumnProcessingFinishedMessage if this.isPreparedStatementPrepare => { + this.clear + } + case m: ColumnProcessingFinishedMessage if this.isPreparedStatementExecute => { this.isPreparedStatementExecuteRows = true } case _ => } - if (result.isInstanceOf[PreparedStatementPrepareResponse]) { - val message = result.asInstanceOf[PreparedStatementPrepareResponse] - this.totalColumns = message.columnsCount - this.totalParams = message.paramsCount - } - if (slice.readableBytes() != 0) { throw new BufferNotFullyConsumedException(slice) } @@ -167,39 +178,44 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { } private def decodeQueryResult(slice: ChannelBuffer): AnyRef = { - if (this.totalColumns == 0) { + if (!hasReadColumnsCount) { + this.hasReadColumnsCount = true this.totalColumns = slice.readBinaryLength return null - } else { + } - if (this.totalParams != this.processedParams) { - this.processedParams += 1 - this.columnDecoder.decode(slice) + if (this.processingParams && this.totalParams != this.processedParams) { + this.processedParams += 1 + return this.columnDecoder.decode(slice) + } + + + if (this.totalColumns == this.processedColumns) { + if (this.isPreparedStatementExecute) { + new BinaryRowMessage(slice.readSlice(slice.readableBytes())) } else { - if (this.totalColumns == this.processedColumns) { - if ( this.isPreparedStatementExecute ) { - new BinaryRowMessage(slice.readSlice(slice.readableBytes())) - } else { - this.rowDecoder.decode(slice) - } - } else { - this.processedColumns += 1 - this.columnDecoder.decode(slice) - } + this.rowDecoder.decode(slice) } - + } else { + this.processedColumns += 1 + this.columnDecoder.decode(slice) } + } def preparedStatementPrepareStarted() { + this.queryProcessStarted() + this.hasReadColumnsCount = true this.processingParams = true this.processingColumns = true this.isPreparedStatementPrepare = true - this.queryProcessStarted() } - def preparedStatementExecuteStarted() { + def preparedStatementExecuteStarted(columnsCount: Int, paramsCount: Int) { this.queryProcessStarted() + this.hasReadColumnsCount = false + this.totalColumns = columnsCount + this.totalParams = paramsCount this.isPreparedStatementExecute = true this.processingParams = false } @@ -207,6 +223,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { def queryProcessStarted() { this.isInQuery = true this.processingColumns = true + this.hasReadColumnsCount = false } private def clear { @@ -219,6 +236,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { this.processedColumns = 0 this.totalParams = 0 this.processedParams = 0 + this.hasReadColumnsCount = false } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index b867cf0b..3db55bf2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -26,6 +26,8 @@ import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import scala.annotation.switch +import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder +import com.github.mauricio.async.db.mysql.MySQLHelper object MySQLOneToOneEncoder { val log = Log.get[MySQLOneToOneEncoder] @@ -33,10 +35,13 @@ object MySQLOneToOneEncoder { class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends OneToOneEncoder { + import MySQLOneToOneEncoder.log + private final val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private final val queryEncoder = new QueryMessageEncoder(charset) + private final val rowEncoder = new BinaryRowEncoder(charset) private final val prepareEncoder = new PreparedStatementPrepareEncoder(charset) - private final val executeEncoder = new PreparedStatementExecuteEncoder() + private final val executeEncoder = new PreparedStatementExecuteEncoder(rowEncoder) private var sequence = 1 @@ -69,6 +74,10 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten ChannelUtils.writePacketLength(result, sequence) + //val dump = MySQLHelper.dumpAsHex(result) + + //log.debug("response dump for message {} is \n{}", msg, dump) + sequence += 1 result diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala index 89d5b3e5..3228b501 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala @@ -14,7 +14,6 @@ * under the License. */ - package com.github.mauricio.async.db.mysql.column object ColumnTypes { @@ -75,4 +74,35 @@ object ColumnTypes { final val FIELD_TYPE_YEAR = 13 + val Mapping = Map( + FIELD_TYPE_BIT -> "bit", + FIELD_TYPE_BLOB -> "blob", + FIELD_TYPE_DATE -> "date", + FIELD_TYPE_DATETIME -> "datetime", + FIELD_TYPE_DECIMAL -> "decimal", + FIELD_TYPE_DOUBLE -> "double", + FIELD_TYPE_ENUM -> "enum", + FIELD_TYPE_FLOAT -> "float", + FIELD_TYPE_GEOMETRY -> "geometry", + FIELD_TYPE_INT24 -> "int64", + FIELD_TYPE_LONG -> "integer", + FIELD_TYPE_LONGLONG -> "long", + FIELD_TYPE_LONG_BLOB -> "long_blob", + FIELD_TYPE_MEDIUM_BLOB -> "medium_blob", + FIELD_TYPE_NEW_DECIMAL -> "new_decimal", + FIELD_TYPE_NEWDATE -> "new_date", + FIELD_TYPE_NULL -> "null", + FIELD_TYPE_NUMERIC -> "numeric", + FIELD_TYPE_SET -> "set", + FIELD_TYPE_SHORT -> "short", + FIELD_TYPE_STRING -> "string", + FIELD_TYPE_TIME -> "time", + FIELD_TYPE_TIMESTAMP -> "timestamp", + FIELD_TYPE_TINY -> "tiny", + FIELD_TYPE_TINY_BLOB -> "tiny_blob", + FIELD_TYPE_VAR_STRING -> "var_string", + FIELD_TYPE_VARCHAR -> "varchar", + FIELD_TYPE_YEAR -> "year" + ) + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala new file mode 100644 index 00000000..e45be4cd --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala @@ -0,0 +1,26 @@ +/* + * 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.mysql.decoder + +import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.message.server.{ParamAndColumnProcessingFinishedMessage, ServerMessage} + +object ParamAndColumnProcessingFinishedDecoder extends MessageDecoder { + def decode(buffer: ChannelBuffer): ServerMessage = { + new ParamAndColumnProcessingFinishedMessage(EOFMessageDecoder.decode(buffer)) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala index c2313946..7c952443 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala @@ -16,11 +16,12 @@ package com.github.mauricio.async.db.mysql.encoder +import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementExecuteMessage, ClientMessage} -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils +import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -class PreparedStatementExecuteEncoder extends MessageEncoder { +class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends MessageEncoder { def encode(message: ClientMessage): ChannelBuffer = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] @@ -31,7 +32,13 @@ class PreparedStatementExecuteEncoder extends MessageEncoder { buffer.writeByte(0x00) // no cursor buffer.writeInt(1) - buffer + if ( m.parameters.isEmpty ) { + buffer + } else { + val parametersBuffer = rowEncoder.encode(m.values) + ChannelBuffers.wrappedBuffer(buffer, parametersBuffer) + } + } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala index 48097659..805ef51e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala @@ -16,5 +16,10 @@ package com.github.mauricio.async.db.mysql.message.client -case class PreparedStatementExecuteMessage ( statementId : Array[Byte], values : Seq[Any] ) +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage + +case class PreparedStatementExecuteMessage ( + statementId : Array[Byte], + values : Seq[Any], + parameters : Seq[ColumnDefinitionMessage] ) extends ClientMessage( ClientMessage.PreparedStatementExecute ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala index 780ce8e6..0e52dad6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala @@ -16,5 +16,5 @@ package com.github.mauricio.async.db.mysql.message.client -case class PreparedStatementMessage ( statement : String, values : Seq[Any] ) +case class PreparedStatementMessage ( statement : String, values : Seq[Any]) extends ClientMessage( ClientMessage.PreparedStatement ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala index 64d5f2bd..dbff414b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -16,6 +16,8 @@ package com.github.mauricio.async.db.mysql.message.server +import com.github.mauricio.async.db.mysql.column.ColumnTypes + case class ColumnDefinitionMessage( catalog: String, schema: String, @@ -29,4 +31,9 @@ case class ColumnDefinitionMessage( flags: Short, decimals: Byte ) - extends ServerMessage(ServerMessage.ColumnDefinition) + extends ServerMessage(ServerMessage.ColumnDefinition) { + + override def toString: String = { + s"${this.getClass.getSimpleName}($name,${ColumnTypes.Mapping(columnType)},$table})" + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamAndColumnProcessingFinishedMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamAndColumnProcessingFinishedMessage.scala new file mode 100644 index 00000000..92d41d47 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ParamAndColumnProcessingFinishedMessage.scala @@ -0,0 +1,20 @@ +/* + * 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.mysql.message.server + +case class ParamAndColumnProcessingFinishedMessage ( eofMessage : EOFMessage ) + extends ServerMessage( ServerMessage.ParamAndColumnProcessingFinished ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala index 8543b455..ad3e58fc 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ServerMessage.scala @@ -31,9 +31,10 @@ object ServerMessage { final val ColumnDefinition = 100 final val ColumnDefinitionFinished = 101 final val ParamProcessingFinished = 102 - final val Row = 103 - final val BinaryRow = 104 - final val PreparedStatementPrepareResponse = 105 + final val ParamAndColumnProcessingFinished = 103 + final val Row = 104 + final val BinaryRow = 105 + final val PreparedStatementPrepareResponse = 106 } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index 3b4df97e..40413fd9 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -52,6 +52,21 @@ trait ConnectionHelper { |(-100, 32766, 8388607, 2147483647, 9223372036854775807, 450.764491, 14.7, 87650.9876) """.stripMargin + val preparedInsertTableNumericColumns = + """ + |insert into numbers ( + |number_tinyint, + |number_smallint, + |number_mediumint, + |number_int, + |number_bigint, + |number_decimal, + |number_float, + |number_double + |) values + |(?, ?, ?, ?, ?, ?, ?, ?) + """.stripMargin + val createTableTimeColumns = """CREATE TEMPORARY TABLE posts ( id INT NOT NULL AUTO_INCREMENT, diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 3c895927..59671481 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -109,7 +109,7 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { timestamp.getMinuteOfHour === 14 timestamp.getSecondOfMinute === 7 - result("created_at_time") === Duration( 3, TimeUnit.HOURS ) + Duration( 14, TimeUnit.MINUTES ) + Duration( 7, TimeUnit.SECONDS ) + result("created_at_time") === Duration(3, TimeUnit.HOURS) + Duration(14, TimeUnit.MINUTES) + Duration(7, TimeUnit.SECONDS) val year = result("created_at_year").asInstanceOf[Short] @@ -118,6 +118,92 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } + "it should be able to bind statement values to the prepared statement" in { + + withConnection { + connection => + val insert = + """ + |insert into numbers ( + |number_tinyint, + |number_smallint, + |number_mediumint, + |number_int, + |number_bigint, + |number_decimal, + |number_float, + |number_double + |) values + |( + |?, + |?, + |?, + |?, + |?, + |?, + |?, + |?) + """.stripMargin + + + val byte: Byte = 10 + val short: Short = 679 + val mediumInt = 778 + val int = 875468 + val bigInt = BigInt(100007654) + val bigDecimal = BigDecimal("198.657809") + val double = 98.765 + val float = 432.8F + + executeQuery(connection, this.createTableNumericColumns) + executePreparedStatement(connection, + insert, + byte, + short, + mediumInt, + int, + bigInt, + bigDecimal, + float, + double) + + val row = executePreparedStatement(connection, "SELECT * FROM numbers").rows.get(0) + + row("number_tinyint") === byte + row("number_smallint") === short + row("number_mediumint") === mediumInt + row("number_int") === int + row("number_bigint") === bigInt + row("number_decimal") === bigDecimal + row("number_float") === float + row("number_double") === double + + } + + } + + "bind parameters on a prepared statement" in { + + val create = """CREATE TEMPORARY TABLE posts ( + | id INT NOT NULL AUTO_INCREMENT, + | some_text VARCHAR(255) not null, + | primary key (id) )""".stripMargin + + val insert = "insert into posts (some_text) values (?)" + val select = "select * from posts" + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, "this is some text here") + val row = executePreparedStatement(connection, select).rows.get(0) + + row("id") === 1 + row("some_text") === "this is some text here" + + } + } + } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index 5cad43b0..f1d92728 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -157,8 +157,6 @@ class MySQLFrameDecoderSpec extends Specification { embedder.offer(this.createEOFPacket()) decoder.isInQuery must beFalse - - } } 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 fd626a5c..2d1e0d3b 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 @@ -30,6 +30,7 @@ import messages.frontend._ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.concurrent.{ExecutionContext, Future, Promise} +import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] 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 baa81c6e..b1d8d85d 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 @@ -24,7 +24,7 @@ import org.specs2.mutable.Specification import scala.concurrent.Await import scala.concurrent.duration._ import scala.language.postfixOps -import com.github.mauricio.async.db.postgresql.exceptions.ConnectionStillRunningQueryException +import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestHelper { diff --git a/project/Build.scala b/project/Build.scala index e5711e55..48a3d75c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -70,7 +70,6 @@ object Configuration { Opts.compile.encoding("UTF8") :+ Opts.compile.deprecation :+ Opts.compile.unchecked - :+ Opts.compile.explaintypes :+ "-feature" , scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), From 367099615d7fb499fe255334c356711dbae616b3 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 17 May 2013 00:26:27 -0300 Subject: [PATCH 106/357] Finished prepared statement support on MySQL --- .travis.yml | 2 ++ .../db/mysql/binary/BinaryRowEncoder.scala | 2 -- .../binary/encoder/DateTimeEncoder.scala | 13 ++----- .../binary/encoder/DurationEncoder.scala | 14 +------- .../binary/encoder/LocalDateTimeEncoder.scala | 18 ++++++++-- .../binary/encoder/LocalTimeEncoder.scala | 10 +----- .../encoder/ReadableInstantEncoder.scala | 4 +-- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 4 +-- .../db/mysql/PreparedStatementsSpec.scala | 35 +++++++++++++++++++ 9 files changed, 60 insertions(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b7b3c26..15c97117 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ scala: - 2.10.1 jdk: - oraclejdk7 + - openjdk7 + - openjdk6 services: - postgresql - mysql diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 7ae93b06..7fe72b34 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -92,8 +92,6 @@ class BinaryRowEncoder( charset : Charset ) { bitMapBuffer.writeByte(1) } - log.debug(s"bitMap $bitMap types size ${parameterTypesBuffer.readableBytes()} parameterValues ${parameterValuesBuffer.readableBytes()}") - ChannelBuffers.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala index 6ed7062b..ec4c9142 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala @@ -16,24 +16,15 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import com.github.mauricio.async.db.mysql.column.ColumnTypes import org.jboss.netty.buffer.ChannelBuffer import org.joda.time._ -import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException -import com.github.mauricio.async.db.mysql.column.ColumnTypes object DateTimeEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val instant = value.asInstanceOf[ReadableDateTime] - buffer.writeByte(11) - buffer.writeShort(instant.getYear) - buffer.writeByte(instant.getMonthOfYear) - buffer.writeByte(instant.getDayOfMonth) - buffer.writeByte(instant.getHourOfDay) - buffer.writeByte(instant.getMinuteOfHour) - buffer.writeByte(instant.getSecondOfMinute) - buffer.writeInt(instant.getMillisOfDay * 1000) - + LocalDateTimeEncoder.encode(new LocalDateTime(instant.getMillis), buffer) } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala index cd7a4b4e..785f4fe4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala @@ -34,16 +34,8 @@ object DurationEncoder extends BinaryEncoder { val minutes = minutesDuration.toMinutes val secondsDuration = minutesDuration - minutes.minutes val seconds = secondsDuration.toSeconds - val microsDuration = secondsDuration - seconds.seconds - val micros = microsDuration.toMicros - val hasMicros = micros != 0 - - if ( hasMicros ) { - buffer.writeByte(12) - } else { - buffer.writeByte(8) - } + buffer.writeByte(8) if (duration > Zero) { buffer.writeByte(0) @@ -56,10 +48,6 @@ object DurationEncoder extends BinaryEncoder { buffer.writeByte(minutes.asInstanceOf[Int]) buffer.writeByte(seconds.asInstanceOf[Int]) - if ( hasMicros ) { - buffer.writeInt(micros.asInstanceOf[Int]) - } - } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIME diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala index 7d207eec..fc52b69d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala @@ -17,13 +17,25 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer -import org.joda.time.{DateTimeZone, LocalDateTime} +import org.joda.time._ import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.util.Log object LocalDateTimeEncoder extends BinaryEncoder { + + private final val log = Log.getByName(this.getClass.getName) + def encode(value: Any, buffer: ChannelBuffer) { - val date = value.asInstanceOf[LocalDateTime] - DateTimeEncoder.encode(date.toDateTime( DateTimeZone.UTC ), buffer) + val instant = value.asInstanceOf[LocalDateTime] + + buffer.writeByte(7) + buffer.writeShort(instant.getYear) + buffer.writeByte(instant.getMonthOfYear) + buffer.writeByte(instant.getDayOfMonth) + buffer.writeByte(instant.getHourOfDay) + buffer.writeByte(instant.getMinuteOfHour) + buffer.writeByte(instant.getSecondOfMinute) + } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala index b4b73fce..ff49b32b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala @@ -23,13 +23,8 @@ import com.github.mauricio.async.db.mysql.column.ColumnTypes object LocalTimeEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val time = value.asInstanceOf[LocalTime] - val hasMillis = time.getMillisOfSecond != 0 - if ( hasMillis ) { - buffer.writeByte(12) - } else { - buffer.writeByte(8) - } + buffer.writeByte(8) if ( time.getMillisOfDay > 0 ) { buffer.writeByte(0) @@ -43,9 +38,6 @@ object LocalTimeEncoder extends BinaryEncoder { buffer.writeByte(time.getMinuteOfHour) buffer.writeByte(time.getSecondOfMinute) - if ( hasMillis ) { - buffer.writeInt(time.getMillisOfSecond * 1000) - } } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIME diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala index 20f8f0a7..303b4383 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala @@ -17,13 +17,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer -import org.joda.time.{DateTime, ReadableInstant} +import org.joda.time._ import com.github.mauricio.async.db.mysql.column.ColumnTypes object ReadableInstantEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[ReadableInstant] - DateTimeEncoder.encode(new DateTime(date.getMillis), buffer) + LocalDateTimeEncoder.encode(new LocalDate(date.getMillis), buffer) } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 3db55bf2..5128bbad 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -74,9 +74,9 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten ChannelUtils.writePacketLength(result, sequence) - //val dump = MySQLHelper.dumpAsHex(result) + val dump = MySQLHelper.dumpAsHex(result) - //log.debug("response dump for message {} is \n{}", msg, dump) + log.debug("response dump for message {} is \n{}", msg, dump) sequence += 1 diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 59671481..01cfc22f 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -20,6 +20,8 @@ import org.specs2.mutable.Specification import org.joda.time._ import scala.concurrent.duration.Duration import java.util.concurrent.TimeUnit +import java.sql.Timestamp +import java.util.Date class PreparedStatementsSpec extends Specification with ConnectionHelper { @@ -204,6 +206,39 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } } + "bind timestamp parameters to a table" in { + + val insert = + """ + |insert into posts (created_at_date, created_at_datetime, created_at_timestamp, created_at_time, created_at_year) + |values ( ?, ?, ?, ?, ? ) + """.stripMargin + + val date = new LocalDate(2011, 9, 8) + val dateTime = new LocalDateTime(2012, 5, 27, 15, 29, 55) + val timestamp = new Timestamp(dateTime.toDateTime.getMillis) + val time = Duration( 3, TimeUnit.HOURS ) + Duration( 5, TimeUnit.MINUTES ) + Duration(10, TimeUnit.SECONDS) + val year = 2012 + + withConnection { + connection => + executeQuery(connection, this.createTableTimeColumns) + executePreparedStatement(connection, insert, date, dateTime, timestamp, time, year) + val rows = executePreparedStatement(connection, "select * from posts where created_at_year > ?", 2011).rows.get + + rows.length === 1 + val row = rows(0) + + row("created_at_date") === date + row("created_at_timestamp") === new LocalDateTime( timestamp.getTime ) + row("created_at_time") === time + row("created_at_year") === year + row("created_at_datetime") === dateTime + + + } + } + } } From c232dc88dffde4b41d916e1d37ada5b15fa88d2c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 17 May 2013 17:48:35 -0300 Subject: [PATCH 107/357] Connection factory for MySQL and more specs --- README.markdown | 18 +++ .../ConnectionNotConnectedException.scala | 6 + .../pool/SingleThreadedAsyncObjectPool.scala | 3 +- mysql-async/README.md | 28 ++-- .../async/db/mysql/MySQLConnection.scala | 30 ++-- .../async/db/mysql/column/ColumnTypes.scala | 2 +- .../server/ColumnDefinitionMessage.scala | 6 +- .../mysql/pool/MySQLConnectionFactory.scala | 128 ++++++++++++++++++ .../async/db/mysql/util/CharsetMapper.scala | 17 ++- .../async/db/mysql/ConnectionHelper.scala | 22 ++- .../db/mysql/PreparedStatementsSpec.scala | 8 +- .../pool/MySQLConnectionFactorySpec.scala | 105 ++++++++++++++ ...cala => PostgreSQLConnectionFactory.scala} | 8 +- .../postgresql/pool/ConnectionPoolSpec.scala | 2 +- .../SingleThreadedAsyncObjectPoolSpec.scala | 2 +- 15 files changed, 343 insertions(+), 42 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionNotConnectedException.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala rename postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/{ConnectionObjectFactory.scala => PostgreSQLConnectionFactory.scala} (91%) diff --git a/README.markdown b/README.markdown index bd4430b1..3b51cfab 100644 --- a/README.markdown +++ b/README.markdown @@ -64,6 +64,24 @@ to hold a stream of bytes. So, just don't touch it and be happy. +## Prepared statements gotcha + +If you have used JDBC before, you might have heard that prepared statements are the best thing on earth when takling +to databases. This isn't exactly true all the time (as you can see on [this presentation](http://www.youtube.com/watch?v=kWOAHIpmLAI) +by [@tenderlove](http://github.com/tenderlove)) and there is a memory cost in keeping prepared statements. + +Prepared statements are tied to a connection, they are not database-wide, so, if you generate your queries dinamically +all the time you might eventually blow up your connection memory and your database memory. + +Why? + +Because when you create a prepared statement, locally, the connection keeps the prepared statement description in memory. +This can be the returned columns information, input parameters information, query text, query identifier that will be +used to execute the query and other flags. This also causes a data structure to be created at your server **for every +connection**. + +So, prepared statements are awesome, but are not free. Use them judiciously. + ## What are the design goals? - fast, fast and faster diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionNotConnectedException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionNotConnectedException.scala new file mode 100644 index 00000000..31ef5f45 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionNotConnectedException.scala @@ -0,0 +1,6 @@ +package com.github.mauricio.async.db.exceptions + +import com.github.mauricio.async.db.Connection + +class ConnectionNotConnectedException( val connection : Connection ) + extends DatabaseException( "The connection %s is not connected to the database".format(connection) ) \ No newline at end of file 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 b17778f2..2b28bec0 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 @@ -31,7 +31,7 @@ object SingleThreadedAsyncObjectPool { /** * * Implements an [[com.github.mauricio.async.db.pool.AsyncObjectPool]] using a single thread from a - * fixed executor service with a single thread as an event loop to cause all calls to be sequential. + * fixed executor service as an event loop to cause all calls to be sequential. * * Once you are done with this object remember to call it's close method to clean up the thread pool and * it's objects as this might prevent your application from ending. @@ -99,6 +99,7 @@ class SingleThreadedAsyncObjectPool[T]( this.addBack(item, promise) } case Failure(e) => { + this.checkouts -= item promise.failure(e) } } diff --git a/mysql-async/README.md b/mysql-async/README.md index 855cf193..8d175f33 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -13,14 +13,23 @@ You can find more information about the MySQL network protocol [here](http://dev * supports MySQL servers from 4.1 and above * supports most available database types +## Gotchas + +* `unsigned` types are not supported, their behaviour when using this driver is undefined. +* MySQL truncates millis in `datetime`, `timestamp` and `time` fields. If your date has millis, + they will be gone ([docs here](http://dev.mysql.com/doc/refman/5.0/en/fractional-seconds.html)) +* Timezone support is rather complicated ([see here](http://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html)), + avoid using timezones in MySQL. This driver just stores the dates as they are and won't perform any computation + or calculation. I'd recommend using only `datetime` fields and avoid `timestamp` fields as much as possible. +* `time` in MySQL is not exactly a time in hours, minutes, seconds. It's a period/duration and it can be expressed in + days too (you could, for instance, say that a time is __-120d 19:27:30.000 001__). As much as this does not make much + sense, that is how it was implemented at the database and as a driver we need to stay true to it, so, while you + **can** send `java.sql.Time` and `LocalTime` objects to the database, when reading these values you will always + receive a `scala.concurrent.Duration` object since it is the closest thing we have to what a `time` value in MySQL means. + ## Supported types -One important thing to take into account here is that `time` in MySQL is not exactly a time in hours, minutes, seconds. -It's a period/duration and it can be expressed days too (you could, for instance, say that a time is -__-120d 19:27:30.000 001__. As much as this does not make much sense, that is how it was implemented at the database -and as a driver we need to stay true to it, so, while you **can** send `java.sql.Time` and `LocalTime` objects to the -database, when reading these values you will always receive a `scala.concurrent.Duration` object since it is the closest -thing we have to what a `time` value in MySQL means. +When you are receiving data from a `ResultSet`: MySQL type | Scala/Java type --- | --- | --- @@ -45,7 +54,7 @@ varcgar | String time | scala.concurrent.Duration blob | Array[Byte] -Now when you're using prepared statements: +Now when you're setting parameters for a prepared statement: Scala/Java type | MySQL type --- | --- | --- @@ -64,4 +73,7 @@ java.util.Date | timestamp java.sql.Timestamp | timestamp java.sql.Time | time String | string -Array[Byte] | blob \ No newline at end of file +Array[Byte] | blob + +You don't have to match exact values when sending parameters for your prepared statements, MySQL is usually smart +enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 304ae9d7..66fc59a2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -63,6 +63,10 @@ class MySQLConnection( private var queryPromise: Promise[QueryResult] = null private var connected = false + private var _lastException : Throwable = null + + def lastException : Throwable = this._lastException + def count : Long = this.connectionCount def connect: Future[Connection] = { this.connectionHandler.connect.onFailure { @@ -98,8 +102,20 @@ class MySQLConnection( override def exceptionCaught(throwable: Throwable) { log.error("Transport failure", throwable) - this.connectionPromise.tryFailure(throwable) - this.failQueryPromise(throwable) + setException(throwable) + } + + override def onError(message: ErrorMessage) { + log.error("Received an error message -> {}", message) + val exception = new MySQLException(message) + exception.fillInStackTrace() + this.setException(exception) + } + + private def setException( t : Throwable ) { + this._lastException = t + this.connectionPromise.tryFailure(t) + this.failQueryPromise(t) } override def onOk(message: OkMessage) { @@ -144,14 +160,6 @@ class MySQLConnection( )) } - override def onError(message: ErrorMessage) { - log.error("Received an error message -> {}", message) - val exception = new MySQLException(message) - exception.fillInStackTrace() - this.connectionPromise.tryFailure(exception) - this.failQueryPromise(exception) - } - def sendQuery(query: String): Future[QueryResult] = { this.validateIsReadyForQuery() val promise = Promise[QueryResult] @@ -181,7 +189,7 @@ class MySQLConnection( } - private def isQuerying: Boolean = this.queryPromise != null && !this.queryPromise.isCompleted + def isQuerying: Boolean = this.queryPromise != null && !this.queryPromise.isCompleted def onResultSet(resultSet: ResultSet, message: EOFMessage) { if (this.isQuerying) { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala index 3228b501..b8bc89f7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ColumnTypes.scala @@ -74,7 +74,7 @@ object ColumnTypes { final val FIELD_TYPE_YEAR = 13 - val Mapping = Map( + final val Mapping = Map( FIELD_TYPE_BIT -> "bit", FIELD_TYPE_BLOB -> "blob", FIELD_TYPE_DATE -> "date", diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala index dbff414b..64199e38 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.mysql.message.server import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.mysql.util.CharsetMapper case class ColumnDefinitionMessage( catalog: String, @@ -34,6 +35,9 @@ case class ColumnDefinitionMessage( extends ServerMessage(ServerMessage.ColumnDefinition) { override def toString: String = { - s"${this.getClass.getSimpleName}($name,${ColumnTypes.Mapping(columnType)},$table})" + val columnTypeName = ColumnTypes.Mapping.getOrElse(columnType, columnType) + val charsetName = CharsetMapper.DefaultCharsetsById.getOrElse(characterSet, characterSet) + + s"${this.getClass.getSimpleName}(name=$name,columnType=${columnTypeName},table=$table,charset=$charsetName})" } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala new file mode 100644 index 00000000..94aadfd8 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala @@ -0,0 +1,128 @@ +/* + * 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.mysql.pool + +import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.pool.ObjectFactory +import com.github.mauricio.async.db.mysql.MySQLConnection +import scala.util.Try +import scala.concurrent.Await +import scala.concurrent.duration._ +import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.exceptions.{ConnectionStillRunningQueryException, ConnectionNotConnectedException} + +object MySQLConnectionFactory { + final val log = Log.get[MySQLConnectionFactory] +} + +/** + * + * Connection pool factory for [[com.github.mauricio.async.db.mysql.MySQLConnection]] objects. + * + * @param configuration a valid configuration to connect to a MySQL server. + * + */ + +class MySQLConnectionFactory( configuration : Configuration ) extends ObjectFactory[MySQLConnection] { + + import MySQLConnectionFactory.log + + /** + * + * Creates a valid object to be used in the pool. This method can block if necessary to make sure a correctly built + * is created. + * + * @return + */ + def create: MySQLConnection = { + val connection = new MySQLConnection(configuration) + Await.result(connection.connect, 5.seconds ) + + connection + } + + /** + * + * This method should "close" and release all resources acquired by the pooled object. This object will not be used + * anymore so any cleanup necessary to remove it from memory should be made in this method. Implementors should not + * raise an exception under any circumstances, the factory should log and clean up the exception itself. + * + * @param item + */ + def destroy(item: MySQLConnection) { + try { + item.disconnect + } catch { + case e : Exception => { + log.error("Failed to close the connection", e) + } + } + } + + /** + * + * Validates that an object can still be used for it's purpose. This method should test the object to make sure + * it's still valid for clients to use. If you have a database connection, test if you are still connected, if you're + * accessing a file system, make sure you can still see and change the file. + * + * You decide how fast this method should return and what it will test, you should usually do something that's fast + * enough not to slow down the pool usage, since this call will be made whenever an object returns to the pool. + * + * If this object is not valid anymore, a [[scala.util.Failure]] should be returned, otherwise [[scala.util.Success]] + * should be the result of this call. + * + * @param item an object produced by this pool + * @return + */ + def validate(item: MySQLConnection): Try[MySQLConnection] = { + Try{ + + if ( !item.isConnected ) { + throw new ConnectionNotConnectedException(item) + } + + if (item.lastException != null) { + throw item.lastException + } + + if ( item.isQuerying ) { + throw new ConnectionStillRunningQueryException(item.count, false) + } + + item + } + } + + /** + * + * Does a full test on the given object making sure it's still valid. Different than validate, that's called whenever + * an object is given back to the pool and should usually be fast, this method will be called when objects are + * idle to make sure they don't "timeout" or become stale in anyway. + * + * For convenience, this method defaults to call **validate** but you can implement it in a different way if you + * would like to. + * + * @param item an object produced by this pool + * @return + */ + override def test(item: MySQLConnection): Try[MySQLConnection] = { + Try { + Await.result(item.sendQuery("SELECT 0"), 5.seconds) + item + } + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala index 416663a0..bf7d8f73 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala @@ -21,18 +21,23 @@ import java.nio.charset.Charset import org.jboss.netty.util.CharsetUtil object CharsetMapper { - val Instance = new CharsetMapper() -} - -class CharsetMapper( charsetsToIntComplement : Map[Charset,Int] = Map.empty[Charset,Int] ) { - private var charsetsToInt = Map[Charset,Int]( + final val DefaultCharsetsByCharset = Map[Charset,Int]( CharsetUtil.UTF_8 -> 83, CharsetUtil.US_ASCII -> 11, CharsetUtil.US_ASCII -> 65, CharsetUtil.ISO_8859_1 -> 3, CharsetUtil.ISO_8859_1 -> 69 - ) ++ charsetsToIntComplement + ) + + final val DefaultCharsetsById = DefaultCharsetsByCharset.map { pair => (pair._2, pair._1.name()) } + + final val Instance = new CharsetMapper() +} + +class CharsetMapper( charsetsToIntComplement : Map[Charset,Int] = Map.empty[Charset,Int] ) { + + private var charsetsToInt = CharsetMapper.DefaultCharsetsByCharset ++ charsetsToIntComplement def toInt( charset : Charset ) : Int = { charsetsToInt.getOrElse(charset, { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index 40413fd9..0c9e2cbd 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -17,7 +17,10 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.util.FutureUtils.await -import com.github.mauricio.async.db.{QueryResult, Configuration} +import com.github.mauricio.async.db._ +import com.github.mauricio.async.db.pool.{PoolConfiguration, ConnectionPool} +import com.github.mauricio.async.db.mysql.pool.MySQLConnectionFactory +import scala.Some trait ConnectionHelper { @@ -99,6 +102,19 @@ trait ConnectionHelper { database = Some("mysql_async_tests") ) + def withPool[T]( fn : (ConnectionPool[MySQLConnection]) => T ) : T = { + + val factory = new MySQLConnectionFactory(this.defaultConfiguration) + val pool = new ConnectionPool[MySQLConnection](factory, PoolConfiguration.Default) + + try { + fn(pool) + } finally { + await( pool.close ) + } + + } + def withConnection[T]( fn : (MySQLConnection) => T ) : T = { val connection = new MySQLConnection(this.defaultConfiguration) @@ -112,11 +128,11 @@ trait ConnectionHelper { } - def executeQuery( connection : MySQLConnection, query : String ) : QueryResult = { + def executeQuery( connection : Connection, query : String ) : QueryResult = { await( connection.sendQuery(query) ) } - def executePreparedStatement( connection : MySQLConnection, query : String, values : Any * ) : QueryResult = { + def executePreparedStatement( connection : Connection, query : String, values : Any * ) : QueryResult = { await( connection.sendPreparedStatement(query, values) ) } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 01cfc22f..568dce3f 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -16,12 +16,11 @@ package com.github.mauricio.async.db.mysql -import org.specs2.mutable.Specification +import java.sql.Timestamp +import java.util.concurrent.TimeUnit import org.joda.time._ +import org.specs2.mutable.Specification import scala.concurrent.duration.Duration -import java.util.concurrent.TimeUnit -import java.sql.Timestamp -import java.util.Date class PreparedStatementsSpec extends Specification with ConnectionHelper { @@ -235,7 +234,6 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { row("created_at_year") === year row("created_at_datetime") === dateTime - } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala new file mode 100644 index 00000000..bf8cd94d --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala @@ -0,0 +1,105 @@ +/* + * 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.mysql.pool + +import com.github.mauricio.async.db.mysql.ConnectionHelper +import com.github.mauricio.async.db.util.FutureUtils.await +import org.specs2.mutable.Specification +import scala.util.{Try, Failure} + +class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { + + val factory = new MySQLConnectionFactory(this.defaultConfiguration) + + "factory" should { + + "fail validation if a connection has errored" in { + + val connection = factory.create + val result = Try { + executeQuery(connection, "this is not sql") + } + + factory.validate(connection) match { + case Failure(e) => ok("connection sucessfully rejected") + } + + } + + "be able to provide connections to the pool" in { + withPool { + pool => + executeQuery(pool, "SELECT 0").rows.get(0)(0) === 0 + } + } + + "fail validation if a connection is disconnected" in { + + val connection = factory.create + + await(connection.disconnect) + + factory.validate(connection) match { + case Failure(e) => ok("Connection successfully rejected") + } + + } + + "fail validation if a connection is still waiting for a query" in { + val connection = factory.create + connection.sendQuery("SELECT SLEEP(5)") + + Thread.sleep(1000) + + factory.validate(connection) match { + case Failure(e) => ok("connection successfully rejected") + } + } + + "accept a good connection" in { + val connection = factory.create + + factory.validate(connection) match { + case scala.util.Success(c) => ok("connection successfully accepted") + } + } + + "test a valid connection and say it is ok" in { + + val connection = factory.create + + factory.test(connection) match { + case scala.util.Success(c) => ok("connection successfully accepted") + } + + } + + "fail test if a connection is disconnected" in { + + val connection = factory.create + + await(connection.disconnect) + + factory.test(connection) match { + case Failure(e) => ok("Connection successfully rejected") + } + + } + + } + +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala similarity index 91% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala rename to postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala index 77591c3b..5b326e20 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionObjectFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala @@ -26,8 +26,8 @@ import scala.concurrent.duration._ import scala.language.postfixOps import scala.util.{Success, Failure, Try} -object ConnectionObjectFactory { - val log = Log.get[ConnectionObjectFactory] +object PostgreSQLConnectionFactory { + val log = Log.get[PostgreSQLConnectionFactory] } /** @@ -37,9 +37,9 @@ object ConnectionObjectFactory { * @param configuration */ -class ConnectionObjectFactory( val configuration : Configuration ) extends ObjectFactory[PostgreSQLConnection] { +class PostgreSQLConnectionFactory( val configuration : Configuration ) extends ObjectFactory[PostgreSQLConnection] { - import ConnectionObjectFactory.log + import PostgreSQLConnectionFactory.log def create: PostgreSQLConnection = { val connection = new PostgreSQLConnection(configuration) 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 49b8c8f1..02295b16 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 @@ -55,7 +55,7 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { def withPool[R]( fn : (ConnectionPool[PostgreSQLConnection]) => R ) : R = { - val pool = new ConnectionPool( new ConnectionObjectFactory(defaultConfiguration), PoolConfiguration.Default ) + val pool = new ConnectionPool( new PostgreSQLConnectionFactory(defaultConfiguration), PoolConfiguration.Default ) try { fn(pool) } finally { 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 b1d8d85d..88f68d68 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 @@ -153,7 +153,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH maxQueueSize = maxQueueSize, validationInterval = validationInterval ) - val factory = new ConnectionObjectFactory(this.defaultConfiguration) + val factory = new PostgreSQLConnectionFactory(this.defaultConfiguration) val pool = new SingleThreadedAsyncObjectPool[PostgreSQLConnection](factory, poolConfiguration) try { From 42df04dba11f9afc680104a2c57567f0af53d53a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 18 May 2013 00:17:19 -0300 Subject: [PATCH 108/357] Correctly decoding TEXT columns when in text protocol --- .../async/db/general/ColumnData.scala | 9 +++-- .../async/db/general/MutableResultSet.scala | 19 +--------- .../pool/SingleThreadedAsyncObjectPool.scala | 1 + .../async/db/mysql/MySQLConnection.scala | 2 +- .../db/mysql/binary/BinaryRowDecoder.scala | 34 +++++++++++------ .../mysql/codec/MySQLConnectionHandler.scala | 35 +++++++++++------ .../db/mysql/codec/MySQLOneToOneEncoder.scala | 4 +- .../mysql/column/ByteArrayColumnDecoder.scala | 2 +- .../column/MySQLColumnDecoderRegistry.scala | 27 +++++++++---- .../server/ColumnDefinitionMessage.scala | 7 +++- .../async/db/mysql/util/CharsetMapper.scala | 2 + .../db/mysql/PreparedStatementsSpec.scala | 8 +++- .../mauricio/async/db/mysql/QuerySpec.scala | 24 ++++++++++++ .../pool/MySQLConnectionFactorySpec.scala | 38 +++++++++++++++++-- .../db/postgresql/PostgreSQLConnection.scala | 19 ++++++++-- .../backend/PostgreSQLColumnData.scala | 6 +-- .../db/general/MutableResultSetSpec.scala | 6 +-- 17 files changed, 173 insertions(+), 70 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala index 1d67cc70..1e544bd1 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala @@ -16,6 +16,9 @@ package com.github.mauricio.async.db.general -class ColumnData( - val name: String, - val dataType: Int) \ No newline at end of file +trait ColumnData { + + def name : String + def dataType : Int + +} \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index 1354663f..96f6ced9 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -28,9 +28,7 @@ object MutableResultSet { } class MutableResultSet[T <: ColumnData]( - val columnTypes: IndexedSeq[T], - charset: Charset, - decoder : ColumnDecoderRegistry) extends ResultSet { + val columnTypes: IndexedSeq[T]) extends ResultSet { private val rows = new ArrayBuffer[RowData]() private val columnMapping: Map[String, Int] = this.columnTypes.indices.map( @@ -44,21 +42,6 @@ class MutableResultSet[T <: ColumnData]( override def apply(idx: Int): RowData = this.rows(idx) - def addRawRow(row: Seq[ChannelBuffer]) { - val realRow = new ArrayRowData(columnTypes.size, this.rows.size, this.columnMapping) - - realRow.indices.foreach { - index => - realRow(index) = if (row(index) == null) { - null - } else { - this.decoder.decode( this.columnTypes(index).dataType, row(index), charset ) - } - } - - this.rows += realRow - } - def addRow( row : Seq[Any] ) { val realRow = new ArrayRowData( columnTypes.size, this.rows.size, this.columnMapping ) var x = 0 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 2b28bec0..84387cb0 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 @@ -100,6 +100,7 @@ class SingleThreadedAsyncObjectPool[T]( } case Failure(e) => { this.checkouts -= item + this.factory.destroy(item) promise.failure(e) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 66fc59a2..9da1957f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -42,7 +42,7 @@ object MySQLConnection { class MySQLConnection( configuration: Configuration, charsetMapper: CharsetMapper = CharsetMapper.Instance, - columnDecoderRegistry: ColumnDecoderRegistry = MySQLColumnDecoderRegistry.Instance + columnDecoderRegistry: MySQLColumnDecoderRegistry = MySQLColumnDecoderRegistry.Instance ) extends MySQLHandlerDelegate with Connection diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index a29fee31..10d0ad07 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -25,6 +25,8 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import scala.collection.mutable.ArrayBuffer import com.github.mauricio.async.db.mysql.MySQLHelper +import scala.annotation.switch +import com.github.mauricio.async.db.mysql.util.CharsetMapper object BinaryRowDecoder { final val log = Log.get[BinaryRowDecoder] @@ -40,14 +42,14 @@ class BinaryRowDecoder(charset: Charset) { def decode(buffer: ChannelBuffer, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { - log.debug("columns are {}", columns) + //log.debug("columns are {}", columns) - log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer)) - PrintUtils.printArray("bitmap", buffer) + //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer)) + //PrintUtils.printArray("bitmap", buffer) val bitMap = BitMap.fromBuffer( columns.size + 7 + 2, buffer ) - log.debug("bitmap is {}", bitMap) + //log.debug("bitmap is {}", bitMap) val row = new ArrayBuffer[Any](columns.size) @@ -56,16 +58,16 @@ class BinaryRowDecoder(charset: Charset) { if (isNull) { row += null } else { - val decoder = decoderFor(columns(index - BitMapOffset).columnType) + val decoder = decoderFor(columns(index - BitMapOffset)) - log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") + //log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") row += decoder.decode(buffer) } } }) - log.debug("values are {}", row) + //log.debug("values are {}", row) if (buffer.readableBytes() != 0) { throw new BufferNotFullyConsumedException(buffer) @@ -74,15 +76,25 @@ class BinaryRowDecoder(charset: Charset) { row } - def decoderFor(columnType: Int): BinaryDecoder = { - columnType match { + def decoderFor(column: ColumnDefinitionMessage): BinaryDecoder = { + + val columnType = column.columnType + + (columnType : @switch) match { case ColumnTypes.FIELD_TYPE_VARCHAR | ColumnTypes.FIELD_TYPE_VAR_STRING | - ColumnTypes.FIELD_TYPE_STRING => this.stringDecoder + ColumnTypes.FIELD_TYPE_STRING | + ColumnTypes.FIELD_TYPE_ENUM => this.stringDecoder case ColumnTypes.FIELD_TYPE_BLOB | ColumnTypes.FIELD_TYPE_LONG_BLOB | ColumnTypes.FIELD_TYPE_MEDIUM_BLOB | - ColumnTypes.FIELD_TYPE_TINY_BLOB => ByteArrayDecoder + ColumnTypes.FIELD_TYPE_TINY_BLOB => { + if ( column.characterSet == CharsetMapper.Binary ) { + ByteArrayDecoder + } else { + this.stringDecoder + } + } case ColumnTypes.FIELD_TYPE_LONGLONG => LongDecoder case ColumnTypes.FIELD_TYPE_LONG | ColumnTypes.FIELD_TYPE_INT24 => IntegerDecoder case ColumnTypes.FIELD_TYPE_YEAR | ColumnTypes.FIELD_TYPE_SHORT => ShortDecoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 1314709c..040bb431 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -18,22 +18,23 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.Configuration import com.github.mauricio.async.db.column.ColumnDecoderRegistry -import com.github.mauricio.async.db.general.{ColumnData, MutableResultSet} +import com.github.mauricio.async.db.general.MutableResultSet +import com.github.mauricio.async.db.mysql.binary.BinaryRowDecoder import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util.Log import java.net.InetSocketAddress +import java.nio.ByteOrder import org.jboss.netty.bootstrap.ClientBootstrap +import org.jboss.netty.buffer.HeapChannelBufferFactory import org.jboss.netty.channel._ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent.{ExecutionContext, Promise, Future} -import com.github.mauricio.async.db.mysql.binary.BinaryRowDecoder -import org.jboss.netty.buffer.HeapChannelBufferFactory -import java.nio.ByteOrder +import com.github.mauricio.async.db.mysql.column.MySQLColumnDecoderRegistry object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -43,7 +44,7 @@ class MySQLConnectionHandler( configuration: Configuration, charsetMapper: CharsetMapper, handlerDelegate: MySQLHandlerDelegate, - columnDecoderRegistry: ColumnDecoderRegistry + columnDecoderRegistry: MySQLColumnDecoderRegistry ) extends SimpleChannelHandler with LifeCycleAwareChannelHandler { @@ -68,7 +69,7 @@ class MySQLConnectionHandler( private var currentPreparedStatementHolder : PreparedStatementHolder = null private var currentPreparedStatement : PreparedStatementMessage = null - private var currentQuery : MutableResultSet[ColumnData] = null + private var currentQuery : MutableResultSet[ColumnDefinitionMessage] = null private var currentContext: ChannelHandlerContext = null def connect: Future[MySQLConnectionHandler] = { @@ -142,7 +143,21 @@ class MySQLConnectionHandler( this.onPreparedStatementPrepareResponse(m.asInstanceOf[PreparedStatementPrepareResponse]) } case ServerMessage.Row => { - this.currentQuery.addRawRow(m.asInstanceOf[ResultSetRowMessage]) + val message = m.asInstanceOf[ResultSetRowMessage] + val items = new Array[Any](message.size) + + var x = 0 + while ( x < message.size ) { + items(x) = if ( message(x) == null ) { + null + } else { + val columnDescription = this.currentQuery.columnTypes(x) + this.columnDecoderRegistry.decode(columnDescription, message(x), configuration.charset) + } + x += 1 + } + + this.currentQuery.addRow(items) } case ServerMessage.BinaryRow => { val message = m.asInstanceOf[BinaryRowMessage] @@ -240,10 +255,8 @@ class MySQLConnectionHandler( } def onColumnDefinitionFinished() { - this.currentQuery = new MutableResultSet[ColumnData]( - this.currentColumns.map( c => new ColumnData(c.name, c.columnType) ), - configuration.charset, - columnDecoderRegistry + this.currentQuery = new MutableResultSet[ColumnDefinitionMessage]( + this.currentColumns ) if ( this.currentPreparedStatementHolder != null ) { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 5128bbad..3db55bf2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -74,9 +74,9 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten ChannelUtils.writePacketLength(result, sequence) - val dump = MySQLHelper.dumpAsHex(result) + //val dump = MySQLHelper.dumpAsHex(result) - log.debug("response dump for message {} is \n{}", msg, dump) + //log.debug("response dump for message {} is \n{}", msg, dump) sequence += 1 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala index ffd4cf16..fb7bc3dc 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala @@ -25,7 +25,7 @@ object ByteArrayColumnDecoder extends ColumnDecoder { override def decode(value: ChannelBuffer, charset: Charset): Any = { val bytes = new Array[Byte](value.readableBytes()) value.readBytes(bytes) - value + bytes } def decode(value: String): Any = { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala index f180d647..f65c3f4e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -20,14 +20,24 @@ import com.github.mauricio.async.db.column._ import scala.annotation.switch import org.jboss.netty.buffer.ChannelBuffer import java.nio.charset.Charset +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage +import com.github.mauricio.async.db.mysql.util.CharsetMapper object MySQLColumnDecoderRegistry { final val Instance = new MySQLColumnDecoderRegistry() } -class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { +class MySQLColumnDecoderRegistry { + + def decode( columnType : ColumnDefinitionMessage , value: ChannelBuffer, charset: Charset): Any = { + + val kind = if ( columnType.dataType == ColumnTypes.FIELD_TYPE_BLOB && + columnType.characterSet != CharsetMapper.Binary ) { + ColumnTypes.FIELD_TYPE_STRING + } else { + columnType.dataType + } - override def decode(kind: Int, value: ChannelBuffer, charset: Charset): Any = { decoderFor(kind).decode(value, charset) } @@ -35,22 +45,23 @@ class MySQLColumnDecoderRegistry extends ColumnDecoderRegistry { (kind : @switch) match { case ColumnTypes.FIELD_TYPE_DATE => DateEncoderDecoder case ColumnTypes.FIELD_TYPE_DATETIME => TimestampEncoderDecoder.Instance - case ColumnTypes.FIELD_TYPE_DECIMAL => BigDecimalEncoderDecoder + case ColumnTypes.FIELD_TYPE_DECIMAL | + ColumnTypes.FIELD_TYPE_NEW_DECIMAL | + ColumnTypes.FIELD_TYPE_NUMERIC => BigDecimalEncoderDecoder case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleEncoderDecoder case ColumnTypes.FIELD_TYPE_FLOAT => FloatEncoderDecoder case ColumnTypes.FIELD_TYPE_INT24 => IntegerEncoderDecoder case ColumnTypes.FIELD_TYPE_LONG => IntegerEncoderDecoder case ColumnTypes.FIELD_TYPE_LONGLONG => LongEncoderDecoder - case ColumnTypes.FIELD_TYPE_NEW_DECIMAL => BigDecimalEncoderDecoder - case ColumnTypes.FIELD_TYPE_NUMERIC => BigDecimalEncoderDecoder case ColumnTypes.FIELD_TYPE_NEWDATE => DateEncoderDecoder case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder - case ColumnTypes.FIELD_TYPE_STRING => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_TIME => TimeDecoder case ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampEncoderDecoder.Instance case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder - case ColumnTypes.FIELD_TYPE_VAR_STRING => StringEncoderDecoder - case ColumnTypes.FIELD_TYPE_VARCHAR => StringEncoderDecoder + case ColumnTypes.FIELD_TYPE_VAR_STRING | + ColumnTypes.FIELD_TYPE_VARCHAR | + ColumnTypes.FIELD_TYPE_STRING | + ColumnTypes.FIELD_TYPE_ENUM => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_YEAR => ShortEncoderDecoder case ColumnTypes.FIELD_TYPE_BLOB => ByteArrayColumnDecoder case _ => StringEncoderDecoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala index 64199e38..e8bb5889 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql.message.server import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.util.CharsetMapper +import com.github.mauricio.async.db.general.ColumnData case class ColumnDefinitionMessage( catalog: String, @@ -32,7 +33,11 @@ case class ColumnDefinitionMessage( flags: Short, decimals: Byte ) - extends ServerMessage(ServerMessage.ColumnDefinition) { + extends ServerMessage(ServerMessage.ColumnDefinition) + with ColumnData +{ + + def dataType: Int = this.columnType override def toString: String = { val columnTypeName = ColumnTypes.Mapping.getOrElse(columnType, columnType) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala index bf7d8f73..01628f54 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala @@ -22,6 +22,8 @@ import org.jboss.netty.util.CharsetUtil object CharsetMapper { + final val Binary = 63 + final val DefaultCharsetsByCharset = Map[Charset,Int]( CharsetUtil.UTF_8 -> 83, CharsetUtil.US_ASCII -> 11, diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 568dce3f..55eb1da0 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -187,7 +187,7 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { val create = """CREATE TEMPORARY TABLE posts ( | id INT NOT NULL AUTO_INCREMENT, - | some_text VARCHAR(255) not null, + | some_text TEXT not null, | primary key (id) )""".stripMargin val insert = "insert into posts (some_text) values (?)" @@ -202,6 +202,12 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { row("id") === 1 row("some_text") === "this is some text here" + val queryRow = executeQuery(connection, select).rows.get(0) + + queryRow("id") === 1 + queryRow("some_text") === "this is some text here" + + } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 70caeb47..71a6264a 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -21,6 +21,7 @@ import org.joda.time.{ReadableDateTime, LocalTime, LocalDate} import org.specs2.mutable.Specification import scala.concurrent.duration.Duration import java.util.concurrent.TimeUnit +import org.jboss.netty.util.CharsetUtil class QuerySpec extends Specification with ConnectionHelper { @@ -118,6 +119,29 @@ class QuerySpec extends Specification with ConnectionHelper { } + "be able to read from a BLOB column when in text protocol" in { + val create = """CREATE TEMPORARY TABLE posts ( + | id INT NOT NULL AUTO_INCREMENT, + | some_bytes BLOB not null, + | primary key (id) )""".stripMargin + + val insert = "insert into posts (some_bytes) values (?)" + val select = "select * from posts" + val bytes = "this is some text here".getBytes(CharsetUtil.UTF_8) + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, bytes) + val row = executeQuery(connection, select).rows.get(0) + + row("id") === 1 + row("some_bytes") === bytes + + + } + } + } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala index bf8cd94d..3d6afbb9 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala @@ -19,7 +19,9 @@ package com.github.mauricio.async.db.mysql.pool import com.github.mauricio.async.db.mysql.ConnectionHelper import com.github.mauricio.async.db.util.FutureUtils.await import org.specs2.mutable.Specification -import scala.util.{Try, Failure} +import scala.util._ +import com.github.mauricio.async.db.exceptions.ConnectionNotConnectedException +import scala.util.Failure class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { @@ -36,10 +38,33 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { factory.validate(connection) match { case Failure(e) => ok("connection sucessfully rejected") + case Success(e) => failure("should not have come here") } } + "it should take a connection from the pool and the pool should not accept it back if it is broken" in { + withPool { + pool => + val connection = await(pool.take) + + pool.inUse.size === 1 + + await(connection.disconnect) + + try { + await(pool.giveBack(connection)) + } catch { + case e : ConnectionNotConnectedException => { + // all good + } + } + + pool.inUse.size === 0 + + } + } + "be able to provide connections to the pool" in { withPool { pool => @@ -55,18 +80,20 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { factory.validate(connection) match { case Failure(e) => ok("Connection successfully rejected") + case Success(c) => failure("should not have come here") } } "fail validation if a connection is still waiting for a query" in { val connection = factory.create - connection.sendQuery("SELECT SLEEP(5)") + connection.sendQuery("SELECT SLEEP(10)") Thread.sleep(1000) factory.validate(connection) match { case Failure(e) => ok("connection successfully rejected") + case Success(c) => failure("should not have come here") } } @@ -74,7 +101,8 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { val connection = factory.create factory.validate(connection) match { - case scala.util.Success(c) => ok("connection successfully accepted") + case Success(c) => ok("connection successfully accepted") + case Failure(e) => failure("should not have come here") } } @@ -83,7 +111,8 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { val connection = factory.create factory.test(connection) match { - case scala.util.Success(c) => ok("connection successfully accepted") + case Success(c) => ok("connection successfully accepted") + case Failure(e) => failure("should not have come here") } } @@ -96,6 +125,7 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { factory.test(connection) match { case Failure(e) => ok("Connection successfully rejected") + case Success(c) => failure("should not have come here") } } 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 2d1e0d3b..3d942476 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 @@ -128,7 +128,7 @@ class PostgreSQLConnection this.isParsed(query) match { case Some(holder) => { - this.currentQuery = Some(new MutableResultSet(holder.columnDatas, configuration.charset, this.decoderRegistry)) + this.currentQuery = Some(new MutableResultSet(holder.columnDatas)) write(new PreparedStatementExecuteMessage(holder.statementId, realQuery, values, this.encoderRegistry)) } case None => { @@ -198,11 +198,24 @@ class PostgreSQLConnection } override def onDataRow(m: DataRowMessage) { - this.currentQuery.get.addRawRow(m.values) + val items = new Array[Any](m.values.size) + var x = 0 + + while ( x < m.values.size ) { + items(x) = if ( m.values(x) == null ) { + null + } else { + val columnType = this.currentQuery.get.columnTypes(x) + this.decoderRegistry.decode(columnType.dataType, m.values(x), configuration.charset) + } + x += 1 + } + + this.currentQuery.get.addRow(items) } override def onRowDescription(m: RowDescriptionMessage) { - this.currentQuery = Option(new MutableResultSet(m.columnDatas, configuration.charset, this.decoderRegistry)) + this.currentQuery = Option(new MutableResultSet(m.columnDatas)) this.setColumnDatas(m.columnDatas) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala index 73a8b16a..aab7f147 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala @@ -19,10 +19,10 @@ package com.github.mauricio.async.db.postgresql.messages.backend import com.github.mauricio.async.db.general.ColumnData class PostgreSQLColumnData( - name: String, + val name: String, val tableObjectId: Int, val columnNumber: Int, - dataType: Int, + val dataType: Int, val dataTypeSize: Int, val dataTypeModifier: Int, - val fieldFormat: Int) extends ColumnData( name, dataType ) \ No newline at end of file + val fieldFormat: Int) extends ColumnData \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala index 083db18f..d2c655ed 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala @@ -55,10 +55,10 @@ class MutableResultSetSpec extends Specification { val text = "some data" val otherText = "some other data" - val resultSet = new MutableResultSet(columns, charset, decoder) + val resultSet = new MutableResultSet(columns) - resultSet.addRawRow( Array( toBuffer(1), toBuffer( text ) ) ) - resultSet.addRawRow( Array( toBuffer(2), toBuffer( otherText ) ) ) + resultSet.addRow( Array( 1, text ) ) + resultSet.addRow( Array( 2, otherText ) ) resultSet(0)(0) === 1 resultSet(0)("id") === 1 From e114fa2edd85f9c0671f49451cef10935c973ed8 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 18 May 2013 00:30:52 -0300 Subject: [PATCH 109/357] Updating docs [ci skip] --- CHANGELOG.md | 9 ++++++--- README.markdown | 8 ++++---- mysql-async/README.md | 7 ++++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5275a7..15aeec33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,19 @@ # Changelog -## 0.1.2 (unreleased) +## 0.2.0 - 2013-05-18 +* Implement MySQL support, should be able to execute common statements, prepared statements and login with password (#9) +* Concurrency problem for multiple queries - @fwbrasil - #18 +* Support prepared statement with more than 64 characters on PostgreSQL - @fwbrasil - #16 * Do not accept returned connections to pool that aren't ready for query - @fwbrasil - #15 * Multiple executions of a prepared statement that doesn't return rows fail - @fwbrasil - #13 * Optimize match/cases to `@switch` (#10) * Reimplement the PostgreSQLMD5Digest.java in Scala - (#8) -* Implement MySQL support, should be able to execute common statements and login with password (#9) ## 0.1.1 - 2013-04-30 -* Query promises fulfilled before cleaning up the query promise cause the futures to either hang forever or fail with a "query already running" message (#2) +* Query promises fulfilled before cleaning up the query promise cause the futures to either hang forever or fail with a + "query already running" message (#2) * Optimize MessageEncoder to use a match instead of a map (#3) * MessageDecoder should validate sizes and correctly handle negative or too large messages (#4) * Move generic pool classes to the com.github.mauricio.async.db package (#9) diff --git a/README.markdown b/README.markdown index 3b51cfab..a9e8e956 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.1.1" +"com.github.mauricio" %% "postgresql-async" % "0.2.0" ``` Or Maven: @@ -32,7 +32,7 @@ Or Maven: And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "x.x.x" +"com.github.mauricio" %% "mysql-async" % "0.2.0" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - x.x.x + 0.2.0 ``` @@ -66,7 +66,7 @@ So, just don't touch it and be happy. ## Prepared statements gotcha -If you have used JDBC before, you might have heard that prepared statements are the best thing on earth when takling +If you have used JDBC before, you might have heard that prepared statements are the best thing on earth when talking to databases. This isn't exactly true all the time (as you can see on [this presentation](http://www.youtube.com/watch?v=kWOAHIpmLAI) by [@tenderlove](http://github.com/tenderlove)) and there is a memory cost in keeping prepared statements. diff --git a/mysql-async/README.md b/mysql-async/README.md index 8d175f33..13067798 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -10,7 +10,7 @@ You can find more information about the MySQL network protocol [here](http://dev * connect do databases with the **mysql_native_password** method (that's the usual way) * execute common statements * execute prepared statements -* supports MySQL servers from 4.1 and above +* supports MySQL servers from 4.1 and above (should also work the same way when using MariaDB or other MySQL derived projects) * supports most available database types ## Gotchas @@ -50,8 +50,10 @@ new_decimal | BigDecimal decimal | BigDecimal string | String var_string | String -varcgar | String +varchar | String time | scala.concurrent.Duration +text | String +enum | String blob | Array[Byte] Now when you're setting parameters for a prepared statement: @@ -63,7 +65,6 @@ Short | smallint Int | mediumint Float | float Double | double -BigDecimal | numeric BigDecimal | decimal LocalDate | date DateTime | timestamp From 826b0bb22392caa492baca6db4de41702bd064da Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 18 May 2013 01:45:18 -0300 Subject: [PATCH 110/357] Updating project defintion files [ci skip] --- .../src/test/resources/logback.xml | 20 ++++++ .../db/mysql/ExecuteManyQueriesSpec.scala | 63 +++++++++++++++++++ project/Build.scala | 11 ++-- 3 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 db-async-common/src/test/resources/logback.xml create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala diff --git a/db-async-common/src/test/resources/logback.xml b/db-async-common/src/test/resources/logback.xml new file mode 100644 index 00000000..3c9d6579 --- /dev/null +++ b/db-async-common/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + [%level][%thread][%d][%c{5}] %msg%ex%n + + + + + target/common-async-tests.log + + [%level][%thread][%d][%c{5}] %msg%ex%n + + + + + + + + \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala new file mode 100644 index 00000000..77232f80 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala @@ -0,0 +1,63 @@ +/* + * 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.mysql + +import org.specs2.mutable.Specification + +class ExecuteManyQueriesSpec extends Specification with ConnectionHelper { + + "connection" should { + + "execute many queries one after the other" in { + + withConnection { + connection => + 1.until(500).foreach { + index => + val rows = executeQuery(connection, "SELECT 6578, 'this is some text'").rows.get + + rows.size === 1 + + val row = rows(0) + + row(0) === 6578 + row(1) === "this is some text" + } + } + + } + + "execute many prepared statements one after the other" in { + withConnection { + connection => + 1.until(500).foreach { + index => + val rows = executePreparedStatement(connection, "SELECT 6578, 'this is some text'").rows.get + + rows.size === 1 + + val row = rows(0) + + row(0) === 6578 + row(1) === "this is some text" + } + } + } + + } + +} diff --git a/project/Build.scala b/project/Build.scala index 48a3d75c..a73d5752 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -6,7 +6,6 @@ object ProjectBuild extends Build { val commonName = "db-async-common" val postgresqlName = "postgresql-async" val mysqlName = "mysql-async" - val commonVersion = "0.1.2-SNAPSHOT" lazy val root = Project( id = "db-async-base", @@ -20,7 +19,6 @@ object ProjectBuild extends Build { base = file(commonName), settings = Configuration.baseSettings ++ Seq( name := commonName, - version := commonVersion, libraryDependencies := Configuration.commonDependencies ) ) @@ -30,7 +28,6 @@ object ProjectBuild extends Build { base = file(postgresqlName), settings = Configuration.baseSettings ++ Seq( name := postgresqlName, - version := commonVersion, libraryDependencies ++= Configuration.implementationDependencies ) ) aggregate (common) dependsOn (common) @@ -40,7 +37,6 @@ object ProjectBuild extends Build { base = file(mysqlName), settings = Configuration.baseSettings ++ Seq( name := mysqlName, - version := commonVersion, libraryDependencies ++= Configuration.implementationDependencies ) ) aggregate (common) dependsOn (common) @@ -49,6 +45,8 @@ object ProjectBuild extends Build { object Configuration { + val commonVersion = "0.2.1-SNAPSHOT" + val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" val commonDependencies = Seq( @@ -59,11 +57,11 @@ object Configuration { "org.scala-lang" % "scala-library" % "2.10.1", "io.netty" % "netty" % "3.6.5.Final", specs2Dependency - ).map( d => d.withSources().withJavadoc() ) + ) val implementationDependencies = Seq( specs2Dependency - ).map( d => d.withSources().withJavadoc() ) + ) val baseSettings = Defaults.defaultSettings ++ Seq( scalacOptions := @@ -76,6 +74,7 @@ object Configuration { scalaVersion := "2.10.1", javacOptions := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8"), organization := "com.github.mauricio", + version := commonVersion, publishArtifact in Test := false, publishMavenStyle := true, pomIncludeRepository := { From 61700aefba795a0baa7ac96b7601d4271aa13fb1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 18 May 2013 10:27:19 -0300 Subject: [PATCH 111/357] Fixing initialization order for objects --- CHANGELOG.md | 2 +- README.markdown | 8 +++---- .../mauricio/async/db/Configuration.scala | 21 ++++++++++--------- project/Build.scala | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15aeec33..3dac72df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.2.0 - 2013-05-18 +## 0.2.1 - 2013-05-18 * Implement MySQL support, should be able to execute common statements, prepared statements and login with password (#9) * Concurrency problem for multiple queries - @fwbrasil - #18 diff --git a/README.markdown b/README.markdown index a9e8e956..d521f823 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.0" +"com.github.mauricio" %% "postgresql-async" % "0.2.1" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.1.1 + 0.2.1 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.0" +"com.github.mauricio" %% "mysql-async" % "0.2.1" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.0 + 0.2.1 ``` 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 9b54b835..449a2b16 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 @@ -21,10 +21,11 @@ import java.nio.charset.Charset import java.util.concurrent.ExecutorService import scala.{None, Option, Int} import scala.Predef._ +import org.jboss.netty.util.CharsetUtil object Configuration { + val DefaultCharset = CharsetUtil.UTF_8 val Default = new Configuration("postgres") - val DefaultCharset = Charset.forName("UTF-8") } /** @@ -48,13 +49,13 @@ object Configuration { * change it. */ -case class Configuration(val username: String, - val host: String = "localhost", - val port: Int = 5432, - val password: Option[String] = None, - val database: Option[String] = None, - val bossPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, - val workerPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, - val charset: Charset = Configuration.DefaultCharset, - val maximumMessageSize: Int = 16777216 +case class Configuration(username: String, + host: String = "localhost", + port: Int = 5432, + password: Option[String] = None, + database: Option[String] = None, + bossPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, + workerPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, + charset: Charset = Configuration.DefaultCharset, + maximumMessageSize: Int = 16777216 ) diff --git a/project/Build.scala b/project/Build.scala index a73d5752..6f7f88e1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.1-SNAPSHOT" + val commonVersion = "0.2.2-SNAPSHOT" val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" From 80feba908808401ddb93a82e2025644b9944823d Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 18 May 2013 11:40:58 -0300 Subject: [PATCH 112/357] Updating docs --- README.markdown | 8 ++++---- project/Build.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.markdown b/README.markdown index d521f823..40142c0f 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.1" +"com.github.mauricio" %% "postgresql-async" % "0.2.2" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.1 + 0.2.2 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.1" +"com.github.mauricio" %% "mysql-async" % "0.2.2" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.1 + 0.2.2 ``` diff --git a/project/Build.scala b/project/Build.scala index 6f7f88e1..ef1aea26 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.2-SNAPSHOT" + val commonVersion = "0.2.2" val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" From edca9d4e2691b99d32a9b7a6e57b335fa7c6405a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 18 May 2013 11:43:16 -0300 Subject: [PATCH 113/357] Updating version in Changelog [ci skip] --- CHANGELOG.md | 2 +- README.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dac72df..c9f6cc2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.2.1 - 2013-05-18 +## 0.2.2 - 2013-05-18 * Implement MySQL support, should be able to execute common statements, prepared statements and login with password (#9) * Concurrency problem for multiple queries - @fwbrasil - #18 diff --git a/README.markdown b/README.markdown index 40142c0f..d500557c 100644 --- a/README.markdown +++ b/README.markdown @@ -162,7 +162,7 @@ it [here](http://mauricio.github.io/2013/04/29/async-database-access-with-postgr In short, what you would usually do is: ```scala import com.github.mauricio.async.db.postgresql.PostgreSQLConnection -import com.github.mauricio.async.db.util.ExecutorServiceUtils.FixedExecutionContext +import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext import com.github.mauricio.async.db.util.URLParser import com.github.mauricio.async.db.{RowData, QueryResult, Connection} import scala.concurrent.duration._ From 64979f7f23a0e9ca3d953bf44d01b528d5511348 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Sun, 19 May 2013 22:49:44 -0300 Subject: [PATCH 114/357] added support for java.lang.Boolean - mysql driver --- .../mauricio/async/db/mysql/binary/BinaryRowEncoder.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 7fe72b34..5862d89f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -59,7 +59,8 @@ class BinaryRowEncoder( charset : Charset ) { classOf[java.sql.Time] -> SQLTimeEncoder, classOf[scala.concurrent.duration.FiniteDuration] -> DurationEncoder, classOf[Array[Byte]] -> ByteArrayEncoder, - classOf[Boolean] -> BooleanEncoder + classOf[Boolean] -> BooleanEncoder, + classOf[java.lang.Boolean] -> BooleanEncoder ) def encode( values : Seq[Any] ) : ChannelBuffer = { From 106ffe14a75bc01abb6bcc8eb82833ef0d735a32 Mon Sep 17 00:00:00 2001 From: Ian Forsey Date: Mon, 20 May 2013 14:38:52 +0200 Subject: [PATCH 115/357] Add link to fractional seconds support in MySQL 5.6.4+ --- mysql-async/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-async/README.md b/mysql-async/README.md index 13067798..c2d855a4 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -16,7 +16,7 @@ You can find more information about the MySQL network protocol [here](http://dev ## Gotchas * `unsigned` types are not supported, their behaviour when using this driver is undefined. -* MySQL truncates millis in `datetime`, `timestamp` and `time` fields. If your date has millis, +* Prior to version [5.6.4](http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html) MySQL truncates millis in `datetime`, `timestamp` and `time` fields. If your date has millis, they will be gone ([docs here](http://dev.mysql.com/doc/refman/5.0/en/fractional-seconds.html)) * Timezone support is rather complicated ([see here](http://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html)), avoid using timezones in MySQL. This driver just stores the dates as they are and won't perform any computation @@ -77,4 +77,4 @@ String | string Array[Byte] | blob You don't have to match exact values when sending parameters for your prepared statements, MySQL is usually smart -enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. \ No newline at end of file +enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. From 36d95e77dc1d1c01b772097e98acb4d9b92eea20 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 20 May 2013 12:13:16 -0300 Subject: [PATCH 116/357] Fixes #24 --- .../async/db/column/DateEncoderDecoder.scala | 1 + .../column/LocalDateTimeEncoderDecoder.scala | 10 ++++- .../binary/decoder/TimestampDecoder.scala | 2 +- .../db/mysql/binary/encoder/ByteEncoder.scala | 4 -- .../binary/encoder/CalendarEncoder.scala | 4 +- .../binary/encoder/DurationEncoder.scala | 14 ++++++- .../binary/encoder/JavaDateEncoder.scala | 4 +- .../binary/encoder/LocalDateTimeEncoder.scala | 18 +++++--- .../binary/encoder/LocalTimeEncoder.scala | 12 +++++- .../encoder/ReadableInstantEncoder.scala | 4 +- .../binary/encoder/SQLTimestampEncoder.scala | 4 +- .../mysql/codec/MySQLConnectionHandler.scala | 3 +- .../column/MySQLColumnDecoderRegistry.scala | 4 +- .../server/ColumnDefinitionMessage.scala | 2 +- .../db/mysql/PreparedStatementsSpec.scala | 42 +++++++++++++++++++ .../mauricio/async/db/mysql/QuerySpec.scala | 6 +-- 16 files changed, 104 insertions(+), 30 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala index 63c01c6e..7a56ee84 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala @@ -32,6 +32,7 @@ object DateEncoderDecoder extends ColumnEncoderDecoder { value match { case d: java.sql.Date => this.formatter.print(new LocalDate(d)) case d: ReadablePartial => this.formatter.print(d) + case d: LocalDate => this.formatter.print(d) case _ => throw new DateEncoderNotAvailableException(value) } } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala index a2bacd0a..27d50383 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala @@ -21,11 +21,17 @@ import org.joda.time.LocalDateTime object LocalDateTimeEncoderDecoder extends ColumnEncoderDecoder { + private val optional = new DateTimeFormatterBuilder() + .appendPattern(".SSSSSS").toParser + private val format = new DateTimeFormatterBuilder() .appendPattern("yyyy-MM-dd HH:mm:ss") + .appendOptional(optional) .toFormatter - override def encode(value: Any): String = format.print(value.asInstanceOf[LocalDateTime]) + override def encode(value: Any): String = + format.print(value.asInstanceOf[LocalDateTime]) - override def decode(value: String): LocalDateTime = format.parseLocalDateTime(value) + override def decode(value: String): LocalDateTime = + format.parseLocalDateTime(value) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala index feda5602..ae676986 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala @@ -35,7 +35,7 @@ object TimestampDecoder extends BinaryDecoder { .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), 0) case 11 => new LocalDateTime() .withDate(buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte()) - .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedInt().toInt * 1000) + .withTime(buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedByte(), buffer.readUnsignedInt().toInt / 1000) } } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala index 8ceac33e..87e87029 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala @@ -22,12 +22,8 @@ import com.github.mauricio.async.db.util.Log object ByteEncoder extends BinaryEncoder { - private final val log = Log.getByName(this.getClass.getName) - def encode(value: Any, buffer: ChannelBuffer) { - log.debug("Received value {}", value) buffer.writeByte(value.asInstanceOf[Byte]) - log.debug("wrote byte {}", buffer.getByte(buffer.writerIndex() - 1)) } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TINY diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala index 021c36f1..914fd044 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala @@ -18,13 +18,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer import java.util.Calendar -import org.joda.time.DateTime +import org.joda.time.{LocalDateTime, DateTime} import com.github.mauricio.async.db.mysql.column.ColumnTypes object CalendarEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val calendar = value.asInstanceOf[Calendar] - DateTimeEncoder.encode(new DateTime(calendar), buffer) + LocalDateTimeEncoder.encode(new LocalDateTime(calendar.getTimeInMillis), buffer) } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala index 785f4fe4..cd7a4b4e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala @@ -34,8 +34,16 @@ object DurationEncoder extends BinaryEncoder { val minutes = minutesDuration.toMinutes val secondsDuration = minutesDuration - minutes.minutes val seconds = secondsDuration.toSeconds + val microsDuration = secondsDuration - seconds.seconds + val micros = microsDuration.toMicros - buffer.writeByte(8) + val hasMicros = micros != 0 + + if ( hasMicros ) { + buffer.writeByte(12) + } else { + buffer.writeByte(8) + } if (duration > Zero) { buffer.writeByte(0) @@ -48,6 +56,10 @@ object DurationEncoder extends BinaryEncoder { buffer.writeByte(minutes.asInstanceOf[Int]) buffer.writeByte(seconds.asInstanceOf[Int]) + if ( hasMicros ) { + buffer.writeInt(micros.asInstanceOf[Int]) + } + } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIME diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala index 680ecccd..97518ee3 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala @@ -17,13 +17,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer -import org.joda.time.DateTime +import org.joda.time.{LocalDateTime, DateTime} import com.github.mauricio.async.db.mysql.column.ColumnTypes object JavaDateEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[java.util.Date] - DateTimeEncoder.encode( new DateTime(date), buffer ) + LocalDateTimeEncoder.encode(new LocalDateTime(date.getTime), buffer) } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala index fc52b69d..9e58d869 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala @@ -16,19 +16,23 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import com.github.mauricio.async.db.mysql.column.ColumnTypes import org.jboss.netty.buffer.ChannelBuffer import org.joda.time._ -import com.github.mauricio.async.db.mysql.column.ColumnTypes -import com.github.mauricio.async.db.util.Log object LocalDateTimeEncoder extends BinaryEncoder { - private final val log = Log.getByName(this.getClass.getName) - def encode(value: Any, buffer: ChannelBuffer) { val instant = value.asInstanceOf[LocalDateTime] - buffer.writeByte(7) + val hasMillis = instant.getMillisOfSecond != 0 + + if ( hasMillis ) { + buffer.writeByte(11) + } else { + buffer.writeByte(7) + } + buffer.writeShort(instant.getYear) buffer.writeByte(instant.getMonthOfYear) buffer.writeByte(instant.getDayOfMonth) @@ -36,6 +40,10 @@ object LocalDateTimeEncoder extends BinaryEncoder { buffer.writeByte(instant.getMinuteOfHour) buffer.writeByte(instant.getSecondOfMinute) + if ( hasMillis ) { + buffer.writeInt(instant.getMillisOfSecond * 1000) + } + } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala index ff49b32b..5e48535a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala @@ -24,7 +24,13 @@ object LocalTimeEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val time = value.asInstanceOf[LocalTime] - buffer.writeByte(8) + val hasMillis = time.getMillisOfSecond != 0 + + if ( hasMillis ) { + buffer.writeByte(12) + } else { + buffer.writeByte(8) + } if ( time.getMillisOfDay > 0 ) { buffer.writeByte(0) @@ -38,6 +44,10 @@ object LocalTimeEncoder extends BinaryEncoder { buffer.writeByte(time.getMinuteOfHour) buffer.writeByte(time.getSecondOfMinute) + if ( hasMillis ) { + buffer.writeInt(time.getMillisOfSecond * 1000) + } + } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIME diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala index 303b4383..03e1f270 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import com.github.mauricio.async.db.mysql.column.ColumnTypes import org.jboss.netty.buffer.ChannelBuffer import org.joda.time._ -import com.github.mauricio.async.db.mysql.column.ColumnTypes object ReadableInstantEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[ReadableInstant] - LocalDateTimeEncoder.encode(new LocalDate(date.getMillis), buffer) + LocalDateTimeEncoder.encode(new LocalDateTime(date.getMillis), buffer) } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala index 16991ec3..87afc738 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala @@ -17,13 +17,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder import org.jboss.netty.buffer.ChannelBuffer -import org.joda.time.DateTime +import org.joda.time.{LocalDateTime, DateTime} import com.github.mauricio.async.db.mysql.column.ColumnTypes object SQLTimestampEncoder extends BinaryEncoder { def encode(value: Any, buffer: ChannelBuffer) { val date = value.asInstanceOf[java.sql.Timestamp] - DateTimeEncoder.encode(new DateTime(date.getTime), buffer) + LocalDateTimeEncoder.encode(new LocalDateTime(date.getTime), buffer) } def encodesTo: Int = ColumnTypes.FIELD_TYPE_TIMESTAMP diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 040bb431..2cd9659a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -99,7 +99,7 @@ class MySQLConnectionHandler( override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) { - log.debug("Message received {}", e.getMessage) + //log.debug("Message received {}", e.getMessage) e.getMessage match { case m: ServerMessage => { @@ -243,7 +243,6 @@ class MySQLConnectionHandler( } private def executePreparedStatement( statementId : Array[Byte], columnsCount : Int, values : Seq[Any], parameters : Seq[ColumnDefinitionMessage] ) { - log.debug("Sending execute prepared statement") decoder.preparedStatementExecuteStarted(columnsCount, parameters.size) this.currentColumns.clear() this.currentParameters.clear() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala index f65c3f4e..03339ea1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala @@ -44,7 +44,8 @@ class MySQLColumnDecoderRegistry { def decoderFor( kind : Int ) : ColumnDecoder = { (kind : @switch) match { case ColumnTypes.FIELD_TYPE_DATE => DateEncoderDecoder - case ColumnTypes.FIELD_TYPE_DATETIME => TimestampEncoderDecoder.Instance + case ColumnTypes.FIELD_TYPE_DATETIME | + ColumnTypes.FIELD_TYPE_TIMESTAMP => LocalDateTimeEncoderDecoder case ColumnTypes.FIELD_TYPE_DECIMAL | ColumnTypes.FIELD_TYPE_NEW_DECIMAL | ColumnTypes.FIELD_TYPE_NUMERIC => BigDecimalEncoderDecoder @@ -56,7 +57,6 @@ class MySQLColumnDecoderRegistry { case ColumnTypes.FIELD_TYPE_NEWDATE => DateEncoderDecoder case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder case ColumnTypes.FIELD_TYPE_TIME => TimeDecoder - case ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampEncoderDecoder.Instance case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder case ColumnTypes.FIELD_TYPE_VAR_STRING | ColumnTypes.FIELD_TYPE_VARCHAR | diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala index e8bb5889..26802161 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -43,6 +43,6 @@ case class ColumnDefinitionMessage( val columnTypeName = ColumnTypes.Mapping.getOrElse(columnType, columnType) val charsetName = CharsetMapper.DefaultCharsetsById.getOrElse(characterSet, characterSet) - s"${this.getClass.getSimpleName}(name=$name,columnType=${columnTypeName},table=$table,charset=$charsetName})" + s"${this.getClass.getSimpleName}(name=$name,columnType=${columnTypeName},table=$table,charset=$charsetName,decimals=$decimals})" } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 55eb1da0..14ecb1f5 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -243,6 +243,48 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } } + "read a timestamp with microseconds" in { + + val create = + """CREATE TEMPORARY TABLE posts ( + id INT NOT NULL AUTO_INCREMENT, + created_at_timestamp TIMESTAMP(3) not null, + created_at_time TIME(3) not null, + primary key (id) + )""" + + val insert = + """INSERT INTO posts ( created_at_timestamp, created_at_time ) + | VALUES ( '2013-01-19 03:14:07.019', '03:14:07.019' )""".stripMargin + + val time = Duration(3, TimeUnit.HOURS ) + + Duration(14, TimeUnit.MINUTES) + + Duration(7, TimeUnit.SECONDS) + + Duration(19, TimeUnit.MILLISECONDS) + + val timestamp = new LocalDateTime(2013, 1, 19, 3, 14, 7, 19) + val select = "SELECT * FROM posts" + + withConnection { + connection => + executeQuery(connection, create) + executeQuery(connection, insert) + val rows = executePreparedStatement( connection, select).rows.get + + val row = rows(0) + + row("created_at_time") === time + row("created_at_timestamp") === timestamp + + val otherRow = executeQuery( connection, select ).rows.get(0) + + otherRow("created_at_time") === time + otherRow("created_at_timestamp") === timestamp + + } + + } + } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 71a6264a..173b0b98 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.mysql.exceptions.MySQLException -import org.joda.time.{ReadableDateTime, LocalTime, LocalDate} +import org.joda.time._ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration import java.util.concurrent.TimeUnit @@ -73,7 +73,7 @@ class QuerySpec extends Specification with ConnectionHelper { date.getMonthOfYear === 1 date.getDayOfMonth === 19 - val dateTime = result("created_at_datetime").asInstanceOf[ReadableDateTime] + val dateTime = result("created_at_datetime").asInstanceOf[LocalDateTime] dateTime.getYear === 2013 dateTime.getMonthOfYear === 1 dateTime.getDayOfMonth === 19 @@ -81,7 +81,7 @@ class QuerySpec extends Specification with ConnectionHelper { dateTime.getMinuteOfHour === 14 dateTime.getSecondOfMinute === 7 - val timestamp = result("created_at_timestamp").asInstanceOf[ReadableDateTime] + val timestamp = result("created_at_timestamp").asInstanceOf[LocalDateTime] timestamp.getYear === 2020 timestamp.getMonthOfYear === 1 timestamp.getDayOfMonth === 19 From b9dd2c450691a7b31f5291be71ee61ee769110f2 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 20 May 2013 12:17:47 -0300 Subject: [PATCH 117/357] Adding reference to timestamp(n) sintax to MySQL readme --- mysql-async/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mysql-async/README.md b/mysql-async/README.md index c2d855a4..8e499811 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -18,6 +18,9 @@ You can find more information about the MySQL network protocol [here](http://dev * `unsigned` types are not supported, their behaviour when using this driver is undefined. * Prior to version [5.6.4](http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html) MySQL truncates millis in `datetime`, `timestamp` and `time` fields. If your date has millis, they will be gone ([docs here](http://dev.mysql.com/doc/refman/5.0/en/fractional-seconds.html)) +* If using `5.6` support for microseconds on `timestamp` fields (using the `timestamp(3)` syntax) you can't + go longer than 3 in precision since `JodaTime` and `Date` objects in Java only go as far as millis and not micro. + For `time` fields, since `Duration` is used, you get full microsecond precision. * Timezone support is rather complicated ([see here](http://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html)), avoid using timezones in MySQL. This driver just stores the dates as they are and won't perform any computation or calculation. I'd recommend using only `datetime` fields and avoid `timestamp` fields as much as possible. From 415e9c767000663a00131b40a87241ca1156a8d3 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 20 May 2013 12:59:22 -0300 Subject: [PATCH 118/357] Do not execute the timestamp(n) specs if the MySQL version does not support it --- .../async/db/column/DateEncoderDecoder.scala | 1 - .../async/db/mysql/MySQLConnection.scala | 8 +++++- .../db/mysql/PreparedStatementsSpec.scala | 25 ++++++++++++------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala index 7a56ee84..63c01c6e 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala @@ -32,7 +32,6 @@ object DateEncoderDecoder extends ColumnEncoderDecoder { value match { case d: java.sql.Date => this.formatter.print(new LocalDate(d)) case d: ReadablePartial => this.formatter.print(d) - case d: LocalDate => this.formatter.print(d) case _ => throw new DateEncoderNotAvailableException(value) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 9da1957f..cf5a506a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -24,7 +24,7 @@ import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util.{Version, Log} import com.github.mauricio.async.db._ import org.jboss.netty.channel._ import scala.Some @@ -37,6 +37,7 @@ import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryExcept object MySQLConnection { final val log = Log.get[MySQLConnection] final val Counter = new AtomicLong() + final val MicrosecondsVersion = Version(5,6,0) } class MySQLConnection( @@ -64,7 +65,9 @@ class MySQLConnection( private var queryPromise: Promise[QueryResult] = null private var connected = false private var _lastException : Throwable = null + private var serverVersion : Version = null + def version = this.serverVersion def lastException : Throwable = this._lastException def count : Long = this.connectionCount @@ -150,6 +153,9 @@ class MySQLConnection( } override def onHandshake(message: HandshakeMessage) { + + this.serverVersion = Version(message.serverVersion) + this.connectionHandler.write(new HandshakeResponseMessage( configuration.username, configuration.charset, diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 14ecb1f5..860a9ea1 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -245,6 +245,8 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { "read a timestamp with microseconds" in { + + val create = """CREATE TEMPORARY TABLE posts ( id INT NOT NULL AUTO_INCREMENT, @@ -267,19 +269,24 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { withConnection { connection => - executeQuery(connection, create) - executeQuery(connection, insert) - val rows = executePreparedStatement( connection, select).rows.get - val row = rows(0) + if ( connection.version < MySQLConnection.MicrosecondsVersion ) { + pending(s"this version of MySQL (${connection.version}) does not support microseconds") + } else { + executeQuery(connection, create) + executeQuery(connection, insert) + val rows = executePreparedStatement( connection, select).rows.get - row("created_at_time") === time - row("created_at_timestamp") === timestamp + val row = rows(0) + + row("created_at_time") === time + row("created_at_timestamp") === timestamp - val otherRow = executeQuery( connection, select ).rows.get(0) + val otherRow = executeQuery( connection, select ).rows.get(0) - otherRow("created_at_time") === time - otherRow("created_at_timestamp") === timestamp + otherRow("created_at_time") === time + otherRow("created_at_timestamp") === timestamp + } } From 52d2ba42de0ca952b2a1f82df30ffce1ec4fb01f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 20 May 2013 14:22:35 -0300 Subject: [PATCH 119/357] Upgrading netty and fixing typo --- mysql-async/README.md | 2 +- project/Build.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mysql-async/README.md b/mysql-async/README.md index 8e499811..c6bcea16 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -1,4 +1,4 @@ -# mysql-async - an asyncm Netty based, MySQL driver written in Scala 2.10 +# mysql-async - an async, Netty based, MySQL driver written in Scala 2.10 This is the MySQL part of the async driver collection. As the PostgreSQL version, it is not supposed to be a JDBC replacement, but a simpler solution for those that need something that queries and then returns rows. diff --git a/project/Build.scala b/project/Build.scala index ef1aea26..351918c8 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.2" + val commonVersion = "0.2.3" val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" @@ -55,7 +55,7 @@ object Configuration { "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", "org.scala-lang" % "scala-library" % "2.10.1", - "io.netty" % "netty" % "3.6.5.Final", + "io.netty" % "netty" % "3.6.6.Final", specs2Dependency ) From b0254a1f5d0c1ef3666963f711e2fb93bf3b666c Mon Sep 17 00:00:00 2001 From: Gabriel Ciuloaica Date: Tue, 21 May 2013 14:22:21 +0300 Subject: [PATCH 120/357] fix proposal for issue #7 --- .../async/db/postgresql/util/ParseURL.java | 163 ------------------ .../async/db/postgresql/util/ParserURL.scala | 53 ++++++ .../async/db/postgresql/util/URLParser.scala | 8 +- .../db/postgresql/util/URLParserSpec.scala | 47 ++++- 4 files changed, 102 insertions(+), 169 deletions(-) delete mode 100644 postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ParserURL.scala diff --git a/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java b/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java deleted file mode 100644 index e5d9def2..00000000 --- a/postgresql-async/src/main/java/com/github/mauricio/async/db/postgresql/util/ParseURL.java +++ /dev/null @@ -1,163 +0,0 @@ -/*------------------------------------------------------------------------- -* -* Copyright (c) 2003-2011, PostgreSQL Global Development Group -* -* -*------------------------------------------------------------------------- -*/ - -package com.github.mauricio.async.db.postgresql.util; - -import com.github.mauricio.async.db.Configuration; - -import java.sql.SQLException; -import java.util.Properties; -import java.util.StringTokenizer; - -/** - * - * Copied over from the JDBC PostgreSQL driver. - * - */ - -public class ParseURL { - - public static final String PGPORT = "port"; - public static final String PGDBNAME = "database"; - public static final String PGHOST = "host"; - public static final String PGUSERNAME = "username"; - public static final String PGPASSWORD = "password"; - - - public static Properties parseURL(String url) throws SQLException - { - int state = -1; - Properties urlProps = new Properties(); - urlProps.setProperty(PGHOST, Configuration.Default().host()); - urlProps.setProperty(PGPORT, Integer.toString(Configuration.Default().port())); - - String l_urlServer = url; - String l_urlArgs = ""; - - int l_qPos = url.indexOf('?'); - if (l_qPos != -1) - { - l_urlServer = url.substring(0, l_qPos); - l_urlArgs = url.substring(l_qPos + 1); - } - - // look for an IPv6 address that is enclosed by [] - // the upcoming parsing that uses colons as identifiers can't handle - // the colons in an IPv6 address. - int ipv6start = l_urlServer.indexOf("["); - int ipv6end = l_urlServer.indexOf("]"); - String ipv6address = null; - if (ipv6start != -1 && ipv6end > ipv6start) - { - ipv6address = l_urlServer.substring(ipv6start + 1, ipv6end); - l_urlServer = l_urlServer.substring(0, ipv6start) + "ipv6host" + l_urlServer.substring(ipv6end + 1); - } - - int serverStartIndex = url.indexOf("://"); - int serverEndIndex = url.indexOf("/", serverStartIndex + 3); - - if ( serverStartIndex >= 0 && serverEndIndex > serverStartIndex ) { - - String serverPart = url.substring( serverStartIndex + 3, serverEndIndex ); - - if ( serverPart.contains("@") ) { - String[] parts = serverPart.split("@"); - - if ( parts[0].contains(":") ) { - String[] userInfo = parts[0].split(":"); - urlProps.setProperty(PGUSERNAME, userInfo[0]); - urlProps.setProperty(PGPASSWORD, userInfo[1]); - } - - if ( parts[1].contains(":") ) { - String[] hostParts = parts[1].split(":"); - urlProps.setProperty(PGHOST, hostParts[0]); - urlProps.setProperty(PGPORT, hostParts[1]); - } else { - urlProps.setProperty(PGHOST, parts[1]); - } - - } - - } - - //parse the server part of the url - StringTokenizer st = new StringTokenizer(l_urlServer, ":/", true); - int count; - for (count = 0; (st.hasMoreTokens()); count++) - { - String token = st.nextToken(); - - if (count > 3) - { - if (count == 4 && token.equals("/")) - state = 0; - else if (count == 4) - { - urlProps.setProperty(PGDBNAME, token); - state = -2; - } - else if (count == 5 && state == 0 && token.equals("/")) - state = 1; - else if (count == 5 && state == 0) - return null; - else if (count == 6 && state == 1) - urlProps.setProperty(PGHOST, token); - else if (count == 7 && token.equals(":")) - state = 2; - else if (count == 8 && state == 2) - { - try - { - Integer portNumber = Integer.decode(token); - urlProps.setProperty(PGPORT, portNumber.toString()); - } - catch (Exception e) - { - return null; - } - } - else if ((count == 7 || count == 9) && - (state == 1 || state == 2) && token.equals("/")) - state = -1; - else if (state == -1) - { - urlProps.setProperty(PGDBNAME, token); - state = -2; - } - } - } - if (count <= 1) - { - return null; - } - - // if we extracted an IPv6 address out earlier put it back - if (ipv6address != null) - urlProps.setProperty(PGHOST, ipv6address); - - //parse the args part of the url - StringTokenizer qst = new StringTokenizer(l_urlArgs, "&"); - for (count = 0; (qst.hasMoreTokens()); count++) - { - String token = qst.nextToken(); - int l_pos = token.indexOf('='); - if (l_pos == -1) - { - urlProps.setProperty(token, ""); - } - else - { - urlProps.setProperty(token.substring(0, l_pos), token.substring(l_pos + 1)); - } - } - - return urlProps; - } - -} 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 new file mode 100644 index 00000000..de86d6a7 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ParserURL.scala @@ -0,0 +1,53 @@ +/** + * + */ +package com.github.mauricio.async.db.postgresql.util + +/** + * @author gciuloaica + * + */ +object ParserURL { + + val PGPORT = "port" + val PGDBNAME = "database" + val PGHOST = "host" + val PGUSERNAME = "username" + val PGPASSWORD = "password" + + val DEFAULT_PORT = "5234" + + def parse(connectionURL: String): Map[String, String] = { + val properties: Map[String, String] = Map() + val pgurl1 = """(jdbc:postgresql)://(.*):(\d+)/(.*)\?username=(.*)&password=(.*)""".r + val pgurl2 = """(postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r + + if (connectionURL.startsWith("jdbc")) { + connectionURL match { + case pgurl1(protocol, server, port, dbname, username, password) => { + properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) + } + } + + } else { + + if (connectionURL.startsWith("postgresql")) { + connectionURL match { + case pgurl2(protocol, username, password, server, port, dbname) => { + properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) + } + } + } else { + properties + } + } + + } + + private def unwrapIpv6address(server: String): String = { + if (server.startsWith("[")) { + server.substring(1, server.length() - 1) + } else server + } + +} \ No newline at end of file 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 0fb8703c..34a6861e 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 @@ -34,15 +34,15 @@ object URLParser { charset: Charset = Default.charset ): Configuration = { - val properties = ParseURL.parseURL(url).toMap + val properties = ParserURL.parse(url) - val port = properties(ParseURL.PGPORT).toInt + val port = properties.get(ParserURL.PGPORT).getOrElse(ParserURL.DEFAULT_PORT).toInt new Configuration( username = properties.get(Username).getOrElse(Default.username), password = properties.get(Password), - database = properties.get(ParseURL.PGDBNAME), - host = properties(ParseURL.PGHOST), + database = properties.get(ParserURL.PGDBNAME), + host = properties(ParserURL.PGHOST), port = port, charset = charset, workerPool = workerPool, 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 e331ccc1..e48c98c0 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 @@ -26,7 +26,6 @@ class URLParserSpec extends Specification { val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database?username=john&password=doe" val configuration = URLParser.parse(connectionUri) - configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") @@ -38,7 +37,6 @@ class URLParserSpec extends Specification { val connectionUri = "postgresql://john:doe@128.567.54.90:9987/my_database" val configuration = URLParser.parse(connectionUri) - configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") @@ -46,6 +44,51 @@ class URLParserSpec extends Specification { configuration.port === 9987 } + "create a connection with the available fields and named server" in { + val connectionUri = "jdbc:postgresql://localhost:9987/my_database?username=john&password=doe" + + val configuration = URLParser.parse(connectionUri) + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "localhost" + configuration.port === 9987 + } + + "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) + 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:postgresql://[::1]:9987/my_database?username=john&password=doe" + + val configuration = URLParser.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 = "postgresql://john:doe@[::1]:9987/my_database" + + val configuration = URLParser.parse(connectionUri) + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "::1" + configuration.port === 9987 + } + } } From c843b8fb8d52b162fdb04e80dcfaad31417de005 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 21 May 2013 21:41:10 -0300 Subject: [PATCH 121/357] Closing release 0.2.3 --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5184f39d..853884cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +generate_bundles.rb .cache target/* bin/* @@ -13,3 +14,4 @@ mysql-async/target/* .rvmrc .ruby-version .ruby-gemset +*.jar From d18e34e726c8c117718b37ef23b1eadb6ae5a268 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 21 May 2013 21:48:46 -0300 Subject: [PATCH 122/357] Updating README --- README.markdown | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.markdown b/README.markdown index d500557c..510f62a0 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.2" +"com.github.mauricio" %% "postgresql-async" % "0.2.3" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.2 + 0.2.3 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.2" +"com.github.mauricio" %% "mysql-async" % "0.2.3" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.2 + 0.2.3 ``` @@ -209,6 +209,8 @@ Check the blog post above for more details and the project's ScalaDocs. ## Contributors * [fwbrasil](https://github.com/fwbrasil) +* [theon](https://github.com/theon) +* [devsprint](https://github.com/devsprint) ## Licence From 10dec5004af5af404b3c1e1f8d6b7cf2ce6e6fbc Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 21 May 2013 21:55:48 -0300 Subject: [PATCH 123/357] Updating changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f6cc2f..beca8b09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.2.3 - 2013-05-21 + +* Upgraded Netty to 3.6.6.Final +* Added support for `timestamp(n)` values on MySQL +* Updated docs to reference MySQL's 5.6 support for microseconds - @theon +* MySQL driver returns DateTime instead of LocalDateTime when in text protocol - #24 + ## 0.2.2 - 2013-05-18 * Implement MySQL support, should be able to execute common statements, prepared statements and login with password (#9) From 00305a65965270bf33d98a323fe542502b8d0501 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 23 May 2013 20:35:51 -0300 Subject: [PATCH 124/357] Fixes issue with MySQL client writing binary encoded length as a 4 bytes int but should be a 2 bytes short - fixes #27 --- .../async/db/util/ChannelWrapper.scala | 2 +- .../mysql/binary/decoder/StringDecoder.scala | 12 +++++++++-- .../mysql/binary/encoder/StringEncoder.scala | 7 ++++++- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 2 ++ .../db/mysql/PreparedStatementsSpec.scala | 21 +++++++++++++++++++ 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index 38d8381b..a958c34c 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -82,7 +82,7 @@ class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { buffer.writeByte( length.asInstanceOf[Byte]) } else if (length < 65536L) { buffer.writeByte(252) - buffer.writeInt(length.asInstanceOf[Int]) + buffer.writeShort(length.asInstanceOf[Int]) } else if (length < 16777216L) { buffer.writeByte(253) writeLongInt(length.asInstanceOf[Int]) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala index d4e96596..82e695c2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala @@ -16,10 +16,18 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset +import org.jboss.netty.buffer.ChannelBuffer + +object StringDecoder { + final val log = Log.get[StringDecoder] +} class StringDecoder( charset : Charset ) extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = buffer.readLengthEncodedString(charset) + + def decode(buffer: ChannelBuffer): Any = { + buffer.readLengthEncodedString(charset) + } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala index 463c9180..0afdd386 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala @@ -16,10 +16,15 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer -import com.github.mauricio.async.db.mysql.column.ColumnTypes + +object StringEncoder { + final val log = Log.get[StringEncoder] +} class StringEncoder( charset : Charset ) extends BinaryEncoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 3db55bf2..3d7b3ed7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -72,6 +72,8 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten val result = encoder.encode(message) + //log.debug(s"length is ${result.writerIndex()}") + ChannelUtils.writePacketLength(result, sequence) //val dump = MySQLHelper.dumpAsHex(result) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 860a9ea1..8c966d2c 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -292,6 +292,27 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } + "support prepared statement with a big string" in { + + val bigString = { + val builder = new StringBuilder() + for (i <- 0 until 400) + builder.append( "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789") + builder.toString() + } + + withConnection { + connection => + executeQuery(connection, "CREATE TEMPORARY TABLE BIGSTRING( id INT NOT NULL AUTO_INCREMENT, STRING LONGTEXT, primary key (id))") + executePreparedStatement(connection, "INSERT INTO BIGSTRING (STRING) VALUES (?)", bigString) + val row = executePreparedStatement(connection, "SELECT STRING, id FROM BIGSTRING").rows.get(0) + row("id") === 1 + val result = row("STRING").asInstanceOf[String] + result === bigString + } + } + + } } From af2bc91b0916b40534731168155d3d588516e06d Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 23 May 2013 21:53:15 -0300 Subject: [PATCH 125/357] Making sure connections are closed --- .../pool/MySQLConnectionFactorySpec.scala | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala index 3d6afbb9..8703dd98 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala @@ -16,12 +16,13 @@ package com.github.mauricio.async.db.mysql.pool -import com.github.mauricio.async.db.mysql.ConnectionHelper +import com.github.mauricio.async.db.mysql.{MySQLConnection, ConnectionHelper} import com.github.mauricio.async.db.util.FutureUtils.await import org.specs2.mutable.Specification import scala.util._ import com.github.mauricio.async.db.exceptions.ConnectionNotConnectedException import scala.util.Failure +import org.specs2.matcher.MatchResult class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { @@ -32,15 +33,21 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { "fail validation if a connection has errored" in { val connection = factory.create + val result = Try { executeQuery(connection, "this is not sql") } - factory.validate(connection) match { - case Failure(e) => ok("connection sucessfully rejected") - case Success(e) => failure("should not have come here") + try { + factory.validate(connection) match { + case Failure(e) => ok("connection sucessfully rejected") + case Success(e) => failure("should not have come here") + } + } finally { + await(connection.close) } + } "it should take a connection from the pool and the pool should not accept it back if it is broken" in { @@ -55,7 +62,7 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { try { await(pool.giveBack(connection)) } catch { - case e : ConnectionNotConnectedException => { + case e: ConnectionNotConnectedException => { // all good } } @@ -95,6 +102,8 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { case Failure(e) => ok("connection successfully rejected") case Success(c) => failure("should not have come here") } + + await(connection.close) === connection } "accept a good connection" in { @@ -104,6 +113,8 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { case Success(c) => ok("connection successfully accepted") case Failure(e) => failure("should not have come here") } + + await(connection.close) === connection } "test a valid connection and say it is ok" in { @@ -115,6 +126,8 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { case Failure(e) => failure("should not have come here") } + await(connection.close) === connection + } "fail test if a connection is disconnected" in { From a60fe236fa7a1fba2084bfc96a855a8a4ff7c3a5 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 24 May 2013 01:17:19 -0300 Subject: [PATCH 126/357] Fixing issue with wrong NULL-bitmap calculation - fixes #28 --- .../db/mysql/binary/BinaryRowDecoder.scala | 34 +++++++++++-------- .../db/mysql/binary/BinaryRowEncoder.scala | 21 +++++++----- .../db/mysql/codec/MySQLFrameDecoder.scala | 4 ++- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 11 +----- .../db/mysql/PreparedStatementsSpec.scala | 12 ++++++- 5 files changed, 46 insertions(+), 36 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index 10d0ad07..824c9f4f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -35,7 +35,7 @@ object BinaryRowDecoder { class BinaryRowDecoder(charset: Charset) { - import BinaryRowDecoder._ + // import BinaryRowDecoder._ private final val bigDecimalDecoder = new BigDecimalDecoder(charset) private final val stringDecoder = new StringDecoder(charset) @@ -46,26 +46,30 @@ class BinaryRowDecoder(charset: Charset) { //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer)) //PrintUtils.printArray("bitmap", buffer) - - val bitMap = BitMap.fromBuffer( columns.size + 7 + 2, buffer ) + val bitMapBytes = new Array[Byte]((columns.size + 7 + 2) / 8) + buffer.readBytes(bitMapBytes) + val bitMap = new BitMap(bitMapBytes) //log.debug("bitmap is {}", bitMap) val row = new ArrayBuffer[Any](columns.size) - bitMap.foreachWithLimit(BitMapOffset, columns.length, { - case (index, isNull) => { - if (isNull) { - row += null - } else { - val decoder = decoderFor(columns(index - BitMapOffset)) + var index = 0 + val baseIndex = (bitMapBytes.length * 8) - 3 - //log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") + while (index < columns.size) { - row += decoder.decode(buffer) - } + if (bitMap.isSet(baseIndex - index)) { + row += null + } else { + val decoder = decoderFor(columns(index)) + + //log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") + + row += decoder.decode(buffer) } - }) + index += 1 + } //log.debug("values are {}", row) @@ -80,7 +84,7 @@ class BinaryRowDecoder(charset: Charset) { val columnType = column.columnType - (columnType : @switch) match { + (columnType: @switch) match { case ColumnTypes.FIELD_TYPE_VARCHAR | ColumnTypes.FIELD_TYPE_VAR_STRING | ColumnTypes.FIELD_TYPE_STRING | @@ -89,7 +93,7 @@ class BinaryRowDecoder(charset: Charset) { ColumnTypes.FIELD_TYPE_LONG_BLOB | ColumnTypes.FIELD_TYPE_MEDIUM_BLOB | ColumnTypes.FIELD_TYPE_TINY_BLOB => { - if ( column.characterSet == CharsetMapper.Binary ) { + if (column.characterSet == CharsetMapper.Binary) { ByteArrayDecoder } else { this.stringDecoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 5862d89f..c2d07921 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -22,6 +22,8 @@ import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util._ import org.joda.time._ import scala.Some +import com.github.mauricio.async.db.mysql.column.ColumnTypes +import java.nio.ByteOrder object BinaryRowEncoder { final val log = Log.get[BinaryRowEncoder] @@ -65,20 +67,21 @@ class BinaryRowEncoder( charset : Charset ) { def encode( values : Seq[Any] ) : ChannelBuffer = { - val bitMapBuffer = ChannelUtils.mysqlBuffer(values.length) + val nullBitsCount = (values.size + 7) / 8 + val nullBits = new Array[Byte](nullBitsCount) + val bitMapBuffer = ChannelUtils.mysqlBuffer(1 + nullBitsCount) val parameterTypesBuffer = ChannelUtils.mysqlBuffer(values.size * 2) val parameterValuesBuffer = ChannelUtils.mysqlBuffer() - val bitMap = new BitMap( new Array[Byte]( (values.size + 7) / 8 ) ) + var index = 0 - var allNulls = true while ( index < values.length ) { val value = values(index) if ( value == null ) { - bitMap.set(index) + nullBits(index / 8) = (nullBits(index / 8) | (1 << (index & 7))).asInstanceOf[Byte] + parameterTypesBuffer.writeShort(ColumnTypes.FIELD_TYPE_NULL) } else { - allNulls = false val encoder = encoderFor(value) parameterTypesBuffer.writeShort(encoder.encodesTo) encoder.encode(value, parameterValuesBuffer) @@ -86,11 +89,11 @@ class BinaryRowEncoder( charset : Charset ) { index += 1 } - bitMap.write(bitMapBuffer) - if ( allNulls ) { - bitMapBuffer.writeByte(0) - } else { + bitMapBuffer.writeBytes(nullBits) + if ( values.size > 0 ) { bitMapBuffer.writeByte(1) + } else { + bitMapBuffer.writeByte(0) } ChannelBuffers.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 52f6fb7f..668501c1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -192,7 +192,9 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { if (this.totalColumns == this.processedColumns) { if (this.isPreparedStatementExecute) { - new BinaryRowMessage(slice.readSlice(slice.readableBytes())) + val row = slice.readSlice(slice.readableBytes()) + row.readByte() // reads initial 00 at message + new BinaryRowMessage(row) } else { this.rowDecoder.decode(slice) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 3d7b3ed7..4133bd05 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException +import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import com.github.mauricio.async.db.mysql.encoder._ import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.mysql.util.CharsetMapper @@ -26,8 +27,6 @@ import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import scala.annotation.switch -import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder -import com.github.mauricio.async.db.mysql.MySQLHelper object MySQLOneToOneEncoder { val log = Log.get[MySQLOneToOneEncoder] @@ -35,8 +34,6 @@ object MySQLOneToOneEncoder { class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends OneToOneEncoder { - import MySQLOneToOneEncoder.log - private final val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private final val queryEncoder = new QueryMessageEncoder(charset) private final val rowEncoder = new BinaryRowEncoder(charset) @@ -72,14 +69,8 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten val result = encoder.encode(message) - //log.debug(s"length is ${result.writerIndex()}") - ChannelUtils.writePacketLength(result, sequence) - //val dump = MySQLHelper.dumpAsHex(result) - - //log.debug("response dump for message {} is \n{}", msg, dump) - sequence += 1 result diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 8c966d2c..43346303 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -312,7 +312,17 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } } + "support setting null to a column" in { + withConnection { + connection => + executeQuery(connection, "CREATE TEMPORARY TABLE timestamps ( id INT NOT NULL, moment TIMESTAMP NULL, primary key (id))") + executePreparedStatement(connection, "INSERT INTO timestamps (moment, id) VALUES (?, ?)", null, 10) + val row = executePreparedStatement(connection, "SELECT moment, id FROM timestamps").rows.get(0) + row("id") === 10 + row("moment") === null + } + } } -} +} \ No newline at end of file From 57513df84db8722e87ba07c9c7e8ca22d82dc5e1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 24 May 2013 01:23:17 -0300 Subject: [PATCH 127/357] Fixing broken decoder spec --- .../mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala index 37d40563..ed42728c 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala @@ -27,12 +27,12 @@ class BinaryRowDecoderSpec extends Specification { val decoder = new BinaryRowDecoder(CharsetUtil.UTF_8) - val idAndName = Array[Byte](0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) + val idAndName = Array[Byte]( 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) val idAndNameColumns = Array( createColumn("id", ColumnTypes.FIELD_TYPE_LONGLONG), createColumn("name", ColumnTypes.FIELD_TYPE_VAR_STRING) ) - val idNameAndNull = Array[Byte](0, 16, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) + val idNameAndNull = Array[Byte]( 16, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) val idNameAndNullColumns = idAndNameColumns ++ List( createColumn("null_value", ColumnTypes.FIELD_TYPE_NULL) ) "binary row decoder" should { From 8443a936acaab1bc519d3620302b42325e3e2661 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 25 May 2013 00:39:57 -0300 Subject: [PATCH 128/357] Selecting column decoder only once and fixing issue when handling a query with many nulls --- .../async/db/util/ChannelWrapper.scala | 3 +- .../async/db/mysql/MySQLConnection.scala | 13 +-- .../db/mysql/binary/BinaryRowDecoder.scala | 72 ++++--------- .../db/mysql/codec/DecoderRegistry.scala | 102 ++++++++++++++++++ .../mysql/codec/MySQLConnectionHandler.scala | 10 +- .../db/mysql/codec/MySQLFrameDecoder.scala | 3 +- .../column/MySQLColumnDecoderRegistry.scala | 71 ------------ .../decoder/ColumnDefinitionDecoder.scala | 7 +- .../server/ColumnDefinitionMessage.scala | 9 +- .../mysql/binary/BinaryRowDecoderSpec.scala | 8 +- 10 files changed, 153 insertions(+), 145 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index a958c34c..98eeed4e 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -25,12 +25,13 @@ object ChannelWrapper { implicit def bufferToWrapper( buffer : ChannelBuffer ) = new ChannelWrapper(buffer) final val MySQL_NULL = 0xfb + final val log = Log.get[ChannelWrapper] } class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { - import ChannelWrapper.MySQL_NULL + import ChannelWrapper._ def readFixedString( length : Int, charset : Charset ) : String = { val bytes = new Array[Byte](length) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index cf5a506a..c858b267 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -16,23 +16,21 @@ package com.github.mauricio.async.db.mysql -import com.github.mauricio.async.db.column.ColumnDecoderRegistry +import com.github.mauricio.async.db._ +import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} -import com.github.mauricio.async.db.mysql.column.MySQLColumnDecoderRegistry import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util.{Version, Log} -import com.github.mauricio.async.db._ +import java.util.concurrent.atomic.AtomicLong import org.jboss.netty.channel._ import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure import scala.util.Success -import java.util.concurrent.atomic.AtomicLong -import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException object MySQLConnection { final val log = Log.get[MySQLConnection] @@ -42,8 +40,7 @@ object MySQLConnection { class MySQLConnection( configuration: Configuration, - charsetMapper: CharsetMapper = CharsetMapper.Instance, - columnDecoderRegistry: MySQLColumnDecoderRegistry = MySQLColumnDecoderRegistry.Instance + charsetMapper: CharsetMapper = CharsetMapper.Instance ) extends MySQLHandlerDelegate with Connection @@ -57,7 +54,7 @@ class MySQLConnection( private final val connectionCount = MySQLConnection.Counter.incrementAndGet() private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) - private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, columnDecoderRegistry) + private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this) private final val connectionPromise = Promise[Connection]() private final val disconnectionPromise = Promise[Connection]() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index 824c9f4f..303f9720 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -33,41 +33,49 @@ object BinaryRowDecoder { final val BitMapOffset = 9 } -class BinaryRowDecoder(charset: Charset) { +class BinaryRowDecoder { - // import BinaryRowDecoder._ - - private final val bigDecimalDecoder = new BigDecimalDecoder(charset) - private final val stringDecoder = new StringDecoder(charset) + //import BinaryRowDecoder._ def decode(buffer: ChannelBuffer, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { - //log.debug("columns are {}", columns) - + //log.debug("columns are {} - {}", buffer.readableBytes(), columns) //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer)) //PrintUtils.printArray("bitmap", buffer) - val bitMapBytes = new Array[Byte]((columns.size + 7 + 2) / 8) - buffer.readBytes(bitMapBytes) - val bitMap = new BitMap(bitMapBytes) - //log.debug("bitmap is {}", bitMap) + val nullCount = (columns.size + 9) / 8 + + val nullBitMask = new Array[Byte](nullCount) + buffer.readBytes(nullBitMask) + + var nullMaskPos = 0 + var bit = 4 val row = new ArrayBuffer[Any](columns.size) var index = 0 - val baseIndex = (bitMapBytes.length * 8) - 3 while (index < columns.size) { - if (bitMap.isSet(baseIndex - index)) { + if ((nullBitMask(nullMaskPos) & bit) != 0) { row += null } else { - val decoder = decoderFor(columns(index)) + + val column = columns(index) //log.debug(s"${decoder.getClass.getSimpleName} - ${buffer.readableBytes()}") + //log.debug("Column value [{}] - {}", value, column.name) - row += decoder.decode(buffer) + row += column.binaryDecoder.decode(buffer) } + + bit <<= 1 + + if (( bit & 255) == 0) { + bit = 1 + nullMaskPos += 1 + } + index += 1 } @@ -80,38 +88,4 @@ class BinaryRowDecoder(charset: Charset) { row } - def decoderFor(column: ColumnDefinitionMessage): BinaryDecoder = { - - val columnType = column.columnType - - (columnType: @switch) match { - case ColumnTypes.FIELD_TYPE_VARCHAR | - ColumnTypes.FIELD_TYPE_VAR_STRING | - ColumnTypes.FIELD_TYPE_STRING | - ColumnTypes.FIELD_TYPE_ENUM => this.stringDecoder - case ColumnTypes.FIELD_TYPE_BLOB | - ColumnTypes.FIELD_TYPE_LONG_BLOB | - ColumnTypes.FIELD_TYPE_MEDIUM_BLOB | - ColumnTypes.FIELD_TYPE_TINY_BLOB => { - if (column.characterSet == CharsetMapper.Binary) { - ByteArrayDecoder - } else { - this.stringDecoder - } - } - case ColumnTypes.FIELD_TYPE_LONGLONG => LongDecoder - case ColumnTypes.FIELD_TYPE_LONG | ColumnTypes.FIELD_TYPE_INT24 => IntegerDecoder - case ColumnTypes.FIELD_TYPE_YEAR | ColumnTypes.FIELD_TYPE_SHORT => ShortDecoder - case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder - case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleDecoder - case ColumnTypes.FIELD_TYPE_FLOAT => FloatDecoder - case ColumnTypes.FIELD_TYPE_NUMERIC | - ColumnTypes.FIELD_TYPE_DECIMAL | - ColumnTypes.FIELD_TYPE_NEW_DECIMAL => this.bigDecimalDecoder - case ColumnTypes.FIELD_TYPE_DATETIME | ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampDecoder - case ColumnTypes.FIELD_TYPE_DATE => DateDecoder - case ColumnTypes.FIELD_TYPE_TIME => TimeDecoder - } - } - } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala new file mode 100644 index 00000000..df14afd2 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala @@ -0,0 +1,102 @@ +/* + * 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.mysql.codec + +import com.github.mauricio.async.db.mysql.binary.decoder._ +import scala.annotation.switch +import com.github.mauricio.async.db.mysql.column._ +import com.github.mauricio.async.db.mysql.util.CharsetMapper +import java.nio.charset.Charset +import com.github.mauricio.async.db.column._ +import com.github.mauricio.async.db.column.{ByteDecoder => TextByteDecoder} +import com.github.mauricio.async.db.mysql.binary.decoder.ByteDecoder +import com.github.mauricio.async.db.mysql.binary.decoder.TimeDecoder +import com.github.mauricio.async.db.mysql.column.{TimeDecoder => TextTimeDecoder} + +class DecoderRegistry(charset: Charset) { + + private final val bigDecimalDecoder = new BigDecimalDecoder(charset) + private final val stringDecoder = new StringDecoder(charset) + + def binaryDecoderFor(columnType: Int, charsetCode: Int): BinaryDecoder = { + + (columnType: @switch) match { + case ColumnTypes.FIELD_TYPE_VARCHAR | + ColumnTypes.FIELD_TYPE_VAR_STRING | + ColumnTypes.FIELD_TYPE_STRING | + ColumnTypes.FIELD_TYPE_ENUM => this.stringDecoder + case ColumnTypes.FIELD_TYPE_BLOB | + ColumnTypes.FIELD_TYPE_LONG_BLOB | + ColumnTypes.FIELD_TYPE_MEDIUM_BLOB | + ColumnTypes.FIELD_TYPE_TINY_BLOB => { + if (charsetCode == CharsetMapper.Binary) { + ByteArrayDecoder + } else { + this.stringDecoder + } + } + case ColumnTypes.FIELD_TYPE_LONGLONG => LongDecoder + case ColumnTypes.FIELD_TYPE_LONG | ColumnTypes.FIELD_TYPE_INT24 => IntegerDecoder + case ColumnTypes.FIELD_TYPE_YEAR | ColumnTypes.FIELD_TYPE_SHORT => ShortDecoder + case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder + case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleDecoder + case ColumnTypes.FIELD_TYPE_FLOAT => FloatDecoder + case ColumnTypes.FIELD_TYPE_NUMERIC | + ColumnTypes.FIELD_TYPE_DECIMAL | + ColumnTypes.FIELD_TYPE_NEW_DECIMAL => this.bigDecimalDecoder + case ColumnTypes.FIELD_TYPE_DATETIME | ColumnTypes.FIELD_TYPE_TIMESTAMP => TimestampDecoder + case ColumnTypes.FIELD_TYPE_DATE => DateDecoder + case ColumnTypes.FIELD_TYPE_TIME => TimeDecoder + case ColumnTypes.FIELD_TYPE_NULL => NullDecoder + } + } + + def textDecoderFor(columnType: Int, charsetCode: Int): ColumnDecoder = { + (columnType: @switch) match { + case ColumnTypes.FIELD_TYPE_DATE => DateEncoderDecoder + case ColumnTypes.FIELD_TYPE_DATETIME | + ColumnTypes.FIELD_TYPE_TIMESTAMP => LocalDateTimeEncoderDecoder + case ColumnTypes.FIELD_TYPE_DECIMAL | + ColumnTypes.FIELD_TYPE_NEW_DECIMAL | + ColumnTypes.FIELD_TYPE_NUMERIC => BigDecimalEncoderDecoder + case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleEncoderDecoder + case ColumnTypes.FIELD_TYPE_FLOAT => FloatEncoderDecoder + case ColumnTypes.FIELD_TYPE_INT24 => IntegerEncoderDecoder + case ColumnTypes.FIELD_TYPE_LONG => IntegerEncoderDecoder + case ColumnTypes.FIELD_TYPE_LONGLONG => LongEncoderDecoder + case ColumnTypes.FIELD_TYPE_NEWDATE => DateEncoderDecoder + case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder + case ColumnTypes.FIELD_TYPE_TIME => TextTimeDecoder + case ColumnTypes.FIELD_TYPE_TINY => TextByteDecoder + case ColumnTypes.FIELD_TYPE_VAR_STRING | + ColumnTypes.FIELD_TYPE_VARCHAR | + ColumnTypes.FIELD_TYPE_STRING | + ColumnTypes.FIELD_TYPE_ENUM => StringEncoderDecoder + case ColumnTypes.FIELD_TYPE_YEAR => ShortEncoderDecoder + case ColumnTypes.FIELD_TYPE_BLOB => { + if (charsetCode == CharsetMapper.Binary) { + ByteArrayColumnDecoder + } else { + StringEncoderDecoder + } + } + case _ => StringEncoderDecoder + } + + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 2cd9659a..7f4d6b55 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -17,7 +17,6 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.column.ColumnDecoderRegistry import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.mysql.binary.BinaryRowDecoder import com.github.mauricio.async.db.mysql.message.client._ @@ -34,7 +33,6 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent.{ExecutionContext, Promise, Future} -import com.github.mauricio.async.db.mysql.column.MySQLColumnDecoderRegistry object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -43,13 +41,11 @@ object MySQLConnectionHandler { class MySQLConnectionHandler( configuration: Configuration, charsetMapper: CharsetMapper, - handlerDelegate: MySQLHandlerDelegate, - columnDecoderRegistry: MySQLColumnDecoderRegistry + handlerDelegate: MySQLHandlerDelegate ) extends SimpleChannelHandler with LifeCycleAwareChannelHandler { - import MySQLConnectionHandler.log private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) @@ -65,7 +61,7 @@ class MySQLConnectionHandler( private final val currentParameters = new ArrayBuffer[ColumnDefinitionMessage]() private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() private final val parsedStatements = new HashMap[String,PreparedStatementHolder]() - private final val binaryRowDecoder = new BinaryRowDecoder(configuration.charset) + private final val binaryRowDecoder = new BinaryRowDecoder() private var currentPreparedStatementHolder : PreparedStatementHolder = null private var currentPreparedStatement : PreparedStatementMessage = null @@ -152,7 +148,7 @@ class MySQLConnectionHandler( null } else { val columnDescription = this.currentQuery.columnTypes(x) - this.columnDecoderRegistry.decode(columnDescription, message(x), configuration.charset) + columnDescription.textDecoder.decode(message(x), configuration.charset) } x += 1 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 668501c1..7d9bff4d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -39,7 +39,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) private final val okDecoder = new OkDecoder(charset) - private final val columnDecoder = new ColumnDefinitionDecoder(charset) + private final val columnDecoder = new ColumnDefinitionDecoder(charset, new DecoderRegistry(charset)) private final val rowDecoder = new ResultSetRowDecoder(charset) private final val preparedStatementPrepareDecoder = new PreparedStatementPrepareResponseDecoder() @@ -77,7 +77,6 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { val slice = buffer.readSlice(size) //val dump = MySQLHelper.dumpAsHex(slice) - //log.debug(s"Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) slice.readByte() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala deleted file mode 100644 index 03339ea1..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/MySQLColumnDecoderRegistry.scala +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.mysql.column - -import com.github.mauricio.async.db.column._ -import scala.annotation.switch -import org.jboss.netty.buffer.ChannelBuffer -import java.nio.charset.Charset -import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage -import com.github.mauricio.async.db.mysql.util.CharsetMapper - -object MySQLColumnDecoderRegistry { - final val Instance = new MySQLColumnDecoderRegistry() -} - -class MySQLColumnDecoderRegistry { - - def decode( columnType : ColumnDefinitionMessage , value: ChannelBuffer, charset: Charset): Any = { - - val kind = if ( columnType.dataType == ColumnTypes.FIELD_TYPE_BLOB && - columnType.characterSet != CharsetMapper.Binary ) { - ColumnTypes.FIELD_TYPE_STRING - } else { - columnType.dataType - } - - decoderFor(kind).decode(value, charset) - } - - def decoderFor( kind : Int ) : ColumnDecoder = { - (kind : @switch) match { - case ColumnTypes.FIELD_TYPE_DATE => DateEncoderDecoder - case ColumnTypes.FIELD_TYPE_DATETIME | - ColumnTypes.FIELD_TYPE_TIMESTAMP => LocalDateTimeEncoderDecoder - case ColumnTypes.FIELD_TYPE_DECIMAL | - ColumnTypes.FIELD_TYPE_NEW_DECIMAL | - ColumnTypes.FIELD_TYPE_NUMERIC => BigDecimalEncoderDecoder - case ColumnTypes.FIELD_TYPE_DOUBLE => DoubleEncoderDecoder - case ColumnTypes.FIELD_TYPE_FLOAT => FloatEncoderDecoder - case ColumnTypes.FIELD_TYPE_INT24 => IntegerEncoderDecoder - case ColumnTypes.FIELD_TYPE_LONG => IntegerEncoderDecoder - case ColumnTypes.FIELD_TYPE_LONGLONG => LongEncoderDecoder - case ColumnTypes.FIELD_TYPE_NEWDATE => DateEncoderDecoder - case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder - case ColumnTypes.FIELD_TYPE_TIME => TimeDecoder - case ColumnTypes.FIELD_TYPE_TINY => ByteDecoder - case ColumnTypes.FIELD_TYPE_VAR_STRING | - ColumnTypes.FIELD_TYPE_VARCHAR | - ColumnTypes.FIELD_TYPE_STRING | - ColumnTypes.FIELD_TYPE_ENUM => StringEncoderDecoder - case ColumnTypes.FIELD_TYPE_YEAR => ShortEncoderDecoder - case ColumnTypes.FIELD_TYPE_BLOB => ByteArrayColumnDecoder - case _ => StringEncoderDecoder - } - } - -} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala index e88826e5..e62505de 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala @@ -21,12 +21,13 @@ import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer +import com.github.mauricio.async.db.mysql.codec.DecoderRegistry object ColumnDefinitionDecoder { final val log = Log.get[ColumnDefinitionDecoder] } -class ColumnDefinitionDecoder(charset: Charset) extends MessageDecoder { +class ColumnDefinitionDecoder(charset: Charset, registry : DecoderRegistry) extends MessageDecoder { override def decode(buffer: ChannelBuffer): ColumnDefinitionMessage = { @@ -58,7 +59,9 @@ class ColumnDefinitionDecoder(charset: Charset) extends MessageDecoder { columnLength, columnType, flags, - decimals + decimals, + registry.binaryDecoderFor(columnType, characterSet), + registry.textDecoderFor(columnType,characterSet) ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala index 26802161..82dcc47d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -19,6 +19,8 @@ package com.github.mauricio.async.db.mysql.message.server import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.general.ColumnData +import com.github.mauricio.async.db.mysql.binary.decoder.BinaryDecoder +import com.github.mauricio.async.db.column.ColumnDecoder case class ColumnDefinitionMessage( catalog: String, @@ -31,11 +33,12 @@ case class ColumnDefinitionMessage( columnLength: Long, columnType: Int, flags: Short, - decimals: Byte + decimals: Byte, + binaryDecoder: BinaryDecoder, + textDecoder: ColumnDecoder ) extends ServerMessage(ServerMessage.ColumnDefinition) - with ColumnData -{ + with ColumnData { def dataType: Int = this.columnType diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala index ed42728c..070f3152 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala @@ -22,10 +22,12 @@ import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification import java.nio.ByteOrder +import com.github.mauricio.async.db.mysql.codec.DecoderRegistry class BinaryRowDecoderSpec extends Specification { - val decoder = new BinaryRowDecoder(CharsetUtil.UTF_8) + val registry = new DecoderRegistry(CharsetUtil.UTF_8) + val decoder = new BinaryRowDecoder() val idAndName = Array[Byte]( 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 106, 111, 101) val idAndNameColumns = Array( @@ -71,7 +73,9 @@ class BinaryRowDecoderSpec extends Specification { 0, columnType, 0, - 0 + 0, + registry.binaryDecoderFor(columnType, 3), + registry.textDecoderFor(columnType, 3) ) } From 4563ad1cb5563bc19e87d324c833c721654b08bb Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Sun, 26 May 2013 08:14:38 -0300 Subject: [PATCH 129/357] extends KindedMessage from Serializable for convenience --- .../main/scala/com/github/mauricio/async/db/KindedMessage.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala index b1f8eceb..2074bbd6 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/KindedMessage.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db -trait KindedMessage { +trait KindedMessage extends Serializable { def kind : Int From 6152f3c9005ba8fc491bd613b25a01cedcd5b97d Mon Sep 17 00:00:00 2001 From: Martin Grotzke Date: Sun, 30 Jun 2013 23:58:35 +0200 Subject: [PATCH 130/357] Add support for Options (Some/None) in prepared stmt parameters Allows to specify Some(value) or None in prepared stmt parameters, as until now an Option had to be passed as opt.getOrElse(null). Is implemented for mysql and postgresql. Closes #30 --- .../db/mysql/binary/BinaryRowEncoder.scala | 15 ++++-- .../db/mysql/PreparedStatementsSpec.scala | 23 +++++++++ .../mysql/binary/BinaryRowEncoderSpec.scala | 45 ++++++++++++++++ .../PostgreSQLColumnEncoderRegistry.scala | 35 ++++++++++--- .../PreparedStatementEncoderHelper.scala | 2 +- .../PostgreSQLColumnEncoderRegistrySpec.scala | 51 +++++++++++++++++++ .../db/postgresql/PreparedStatementSpec.scala | 29 ++++++++++- 7 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index c2d07921..78848f80 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -78,13 +78,14 @@ class BinaryRowEncoder( charset : Charset ) { while ( index < values.length ) { val value = values(index) - if ( value == null ) { + if ( value == null || value == None ) { nullBits(index / 8) = (nullBits(index / 8) | (1 << (index & 7))).asInstanceOf[Byte] parameterTypesBuffer.writeShort(ColumnTypes.FIELD_TYPE_NULL) } else { - val encoder = encoderFor(value) - parameterTypesBuffer.writeShort(encoder.encodesTo) - encoder.encode(value, parameterValuesBuffer) + value match { + case Some(v) => encode(parameterTypesBuffer, parameterValuesBuffer, v) + case _ => encode(parameterTypesBuffer, parameterValuesBuffer, value) + } } index += 1 } @@ -99,6 +100,12 @@ class BinaryRowEncoder( charset : Charset ) { ChannelBuffers.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) } + private def encode(parameterTypesBuffer: ChannelBuffer, parameterValuesBuffer: ChannelBuffer, value: Any): Unit = { + val encoder = encoderFor(value) + parameterTypesBuffer.writeShort(encoder.encodesTo) + encoder.encode(value, parameterValuesBuffer) + } + private def encoderFor( v : Any ) : BinaryEncoder = { this.encoders.get(v.getClass) match { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 43346303..43f07837 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -323,6 +323,29 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } } + "support setting None to a column" in { + withConnection { + connection => + executeQuery(connection, "CREATE TEMPORARY TABLE timestamps ( id INT NOT NULL, moment TIMESTAMP NULL, primary key (id))") + executePreparedStatement(connection, "INSERT INTO timestamps (moment, id) VALUES (?, ?)", None, 10) + val row = executePreparedStatement(connection, "SELECT moment, id FROM timestamps").rows.get(0) + row("id") === 10 + row("moment") === null + } + } + + "support setting Some(value) to a column" in { + withConnection { + connection => + executeQuery(connection, "CREATE TEMPORARY TABLE timestamps ( id INT NOT NULL, moment TIMESTAMP NULL, primary key (id))") + val moment = LocalDateTime.now().withMillisOfDay(0) // cut off millis to match timestamp + executePreparedStatement(connection, "INSERT INTO timestamps (moment, id) VALUES (?, ?)", Some(moment), 10) + val row = executePreparedStatement(connection, "SELECT moment, id FROM timestamps").rows.get(0) + row("id") === 10 + row("moment") === moment + } + } + } } \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala new file mode 100644 index 00000000..0b216a03 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala @@ -0,0 +1,45 @@ +/* + * 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.mysql.binary + +import org.jboss.netty.util.CharsetUtil +import org.specs2.mutable.Specification + +class BinaryRowEncoderSpec extends Specification { + + val encoder = new BinaryRowEncoder(CharsetUtil.UTF_8) + + "binary row encoder" should { + + "encode Some(value) like value" in { + val actual = encoder.encode(List(Some(1l), Some("foo"))) + val expected = encoder.encode(List(1l, "foo")) + + actual mustEqual expected + + } + + "encode None as null" in { + val actual = encoder.encode(List(None)) + val expected = encoder.encode(List(null)) + + actual mustEqual expected + } + + } + +} 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 aa06cb71..a4e7cc6c 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 @@ -71,12 +71,25 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { private final val classes = classesSequence.toMap - def encode(value: Any): String = { + override def encode(value: Any): String = { if (value == null) { return null } + value match { + case Some(v) => encode(v) + case None => null + case _ => encodeValue(value) + } + + } + + /** + * Used to encode a value that is not null and not an Option. + */ + private def encodeValue(value: Any): String = { + val encoder = this.classes.get(value.getClass) if (encoder.isDefined) { @@ -104,7 +117,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { } - def encodeArray(collection: Traversable[_]): String = { + private def encodeArray(collection: Traversable[_]): String = { val builder = new StringBuilder() builder.append('{') @@ -130,7 +143,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { builder.toString() } - def shouldQuote(value: Any): Boolean = { + private def shouldQuote(value: Any): Boolean = { value match { case n: java.lang.Number => false case n: Int => false @@ -141,17 +154,23 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { case n: java.lang.Iterable[_] => false case n: Traversable[_] => false case n: Array[_] => false + case Some(v) => shouldQuote(v) case _ => true } } - def kindOf(value: Any): Int = { - if ( value == null ) { + override def kindOf(value: Any): Int = { + if ( value == null || value == None ) { 0 } else { - this.classes.get(value.getClass) match { - case Some( entry ) => entry._2 - case None => 0 + value match { + case Some(v) => kindOf(v) + case _ => { + this.classes.get(value.getClass) match { + case Some( entry ) => entry._2 + case None => 0 + } + } } } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala index ede0b165..c88dc552 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -53,7 +53,7 @@ trait PreparedStatementEncoderHelper { bindBuffer.writeShort(values.length) for (value <- values) { - if (value == null) { + if (value == null || value == None) { bindBuffer.writeInt(-1) } else { val content = encoder.encode(value).getBytes(charset) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala new file mode 100644 index 00000000..e30b7494 --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala @@ -0,0 +1,51 @@ +package com.github.mauricio.async.db.postgresql + +import com.github.mauricio.async.db.postgresql.column.PostgreSQLColumnEncoderRegistry +import org.specs2.mutable.Specification + +class PostgreSQLColumnEncoderRegistrySpec extends Specification { + + val encoder = new PostgreSQLColumnEncoderRegistry + + "column encoder registry" should { + + "encode Some(value) like value" in { + + val actual = encoder.encode(Some(1l)) + val expected = encoder.encode(1l) + + actual mustEqual expected + } + + "encode Some(value) in list like value in list" in { + + val actual = encoder.encode(List(Some(1l), Some("foo"))) + val expected = encoder.encode(List(1l, "foo")) + + actual mustEqual expected + } + + "encode None as null" in { + val actual = encoder.encode(None) + val expected = encoder.encode(null) + + actual mustEqual expected + } + + "determine kindOf Some(value) like kindOf value" in { + val actual = encoder.kindOf(Some(1l)) + val expected = encoder.kindOf(1l) + + actual mustEqual expected + } + + "determine kindOf None like kindOf null" in { + val actual = encoder.kindOf(None) + val expected = encoder.kindOf(null) + + actual mustEqual expected + } + + } + +} \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 7311f08a..9356ea6f 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -28,7 +28,7 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { ( id bigserial NOT NULL, content character varying(255) NOT NULL, - moment date NOT NULL, + moment date NULL, CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) )""" val messagesInsert = s"INSERT INTO messages $filler (content,moment) VALUES (?,?) RETURNING id" @@ -88,6 +88,33 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } + "support prepared statement with Option parameters (Some/None)" in { + withHandler { + handler => + + val firstContent = "Some Moment" + val secondContent = "Some Other Moment" + val date = LocalDate.now() + + executeDdl(handler, this.messagesCreate) + executePreparedStatement(handler, this.messagesInsert, Array(Some(firstContent), None)) + executePreparedStatement(handler, this.messagesInsert, Array(Some(secondContent), Some(date))) + + val rows = executePreparedStatement(handler, this.messagesSelectAll).rows.get + + rows.length === 2 + + rows(0)("id") === 1 + rows(0)("content") === firstContent + rows(0)("moment") === null + + rows(1)("id") === 2 + rows(1)("content") === secondContent + rows(1)("moment") === date + + } + } + } } From 9931f9b078d80dbfd8cdceff177079bad4e04a22 Mon Sep 17 00:00:00 2001 From: Martin Grotzke Date: Mon, 1 Jul 2013 11:56:30 +0200 Subject: [PATCH 131/357] Support 'postgres' protocol in heroku like db urls. Heroku like db urls were supported with 'postgresql' protocol. In my heroku app, the DATABASE_URL was set with 'postgres' protocol, so that the db url could not be parsed. This change supports both 'postgresql' and 'postgres' protocols. --- .../async/db/postgresql/util/ParserURL.scala | 4 ++-- .../async/db/postgresql/util/URLParserSpec.scala | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) 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 index de86d6a7..3569d30f 100644 --- 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 @@ -20,7 +20,7 @@ object ParserURL { def parse(connectionURL: String): Map[String, String] = { val properties: Map[String, String] = Map() val pgurl1 = """(jdbc:postgresql)://(.*):(\d+)/(.*)\?username=(.*)&password=(.*)""".r - val pgurl2 = """(postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r + val pgurl2 = """(postgres|postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r if (connectionURL.startsWith("jdbc")) { connectionURL match { @@ -31,7 +31,7 @@ object ParserURL { } else { - if (connectionURL.startsWith("postgresql")) { + if (connectionURL.startsWith("postgres")) { connectionURL match { case pgurl2(protocol, username, password, server, port, dbname) => { properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) 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 e48c98c0..97e45ea2 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 @@ -33,7 +33,7 @@ class URLParserSpec extends Specification { configuration.port === 9987 } - "create a connection from a heroku like URL" in { + "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 configuration = URLParser.parse(connectionUri) @@ -44,6 +44,17 @@ class URLParserSpec extends Specification { configuration.port === 9987 } + "create a connection from a heroku like URL using 'postgres' protocol" in { + val connectionUri = "postgres://john:doe@128.567.54.90:9987/my_database" + + val configuration = URLParser.parse(connectionUri) + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "128.567.54.90" + configuration.port === 9987 + } + "create a connection with the available fields and named server" in { val connectionUri = "jdbc:postgresql://localhost:9987/my_database?username=john&password=doe" From a5208c29c323479972e1858d2ce5a7388a27c1e1 Mon Sep 17 00:00:00 2001 From: Martin Grotzke Date: Tue, 2 Jul 2013 00:52:00 +0200 Subject: [PATCH 132/357] Support db urls without port and username/password. The database url had to include port, username and password, like this: `jdbc:postgresql://localhost:5432/mydb?username=foo&password=bar` This change allows to leave out port or username/password, like this: `jdbc:postgresql://localhost/mydb` (for simplicity only both username and password can be left out, not username or password) This is implemented for `jdbc:postgresql` protocol, not for heroku `postgresql`/`postgres` protocol. This commit also includes a change of the default port from 5234 to 5432. Closes #32 --- .../async/db/postgresql/util/ParserURL.scala | 12 ++++++---- .../db/postgresql/util/URLParserSpec.scala | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) 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 index 3569d30f..f0c388ac 100644 --- 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 @@ -15,17 +15,21 @@ object ParserURL { val PGUSERNAME = "username" val PGPASSWORD = "password" - val DEFAULT_PORT = "5234" + val DEFAULT_PORT = "5432" + + private val pgurl1 = """(jdbc:postgresql)://([^:]*|\[.+\])(?::(\d+))?/([^?]+)(?:\?username=(.*)&password=(.*))?""".r + private val pgurl2 = """(postgres|postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r def parse(connectionURL: String): Map[String, String] = { val properties: Map[String, String] = Map() - val pgurl1 = """(jdbc:postgresql)://(.*):(\d+)/(.*)\?username=(.*)&password=(.*)""".r - val pgurl2 = """(postgres|postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r if (connectionURL.startsWith("jdbc")) { connectionURL match { case pgurl1(protocol, server, port, dbname, username, password) => { - properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) + var result = properties + (PGHOST -> unwrapIpv6address(server)) + (PGDBNAME -> dbname) + if(port != null) result += (PGPORT -> port) + if(username != null) result = (result + (PGUSERNAME -> username) + (PGPASSWORD -> password)) + result } } 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 97e45ea2..1902032e 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,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.util import org.specs2.mutable.Specification +import com.github.mauricio.async.db.Configuration class URLParserSpec extends Specification { @@ -33,6 +34,29 @@ class URLParserSpec extends Specification { configuration.port === 9987 } + "create a connection without port" in { + val connectionUri = "jdbc:postgresql://128.567.54.90/my_database?username=john&password=doe" + + val configuration = URLParser.parse(connectionUri) + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "128.567.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 configuration = URLParser.parse(connectionUri) + configuration.username === Configuration.Default.username + configuration.password === None + configuration.database === Some("my_database") + configuration.host === "128.567.54.90" + configuration.port === 9987 + } + "create a connection from a heroku like URL using 'postgresql' protocol" in { val connectionUri = "postgresql://john:doe@128.567.54.90:9987/my_database" From 7a4f6e9c198ea4c91add899270af52eb84e3d5e5 Mon Sep 17 00:00:00 2001 From: Martin Grotzke Date: Tue, 2 Jul 2013 01:11:40 +0200 Subject: [PATCH 133/357] Simplify ParserURL.parse, check different regexps in single match clause --- .../async/db/postgresql/util/ParserURL.scala | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) 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 index f0c388ac..5d0ea9d5 100644 --- 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 @@ -3,12 +3,16 @@ */ 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" @@ -23,25 +27,18 @@ object ParserURL { def parse(connectionURL: String): Map[String, String] = { val properties: Map[String, String] = Map() - if (connectionURL.startsWith("jdbc")) { - connectionURL match { - case pgurl1(protocol, server, port, dbname, username, password) => { - var result = properties + (PGHOST -> unwrapIpv6address(server)) + (PGDBNAME -> dbname) - if(port != null) result += (PGPORT -> port) - if(username != null) result = (result + (PGUSERNAME -> username) + (PGPASSWORD -> password)) - result - } + connectionURL match { + case pgurl1(protocol, server, port, dbname, username, password) => { + var result = properties + (PGHOST -> unwrapIpv6address(server)) + (PGDBNAME -> dbname) + if(port != null) result += (PGPORT -> port) + if(username != null) result = (result + (PGUSERNAME -> username) + (PGPASSWORD -> password)) + result } - - } else { - - if (connectionURL.startsWith("postgres")) { - connectionURL match { - case pgurl2(protocol, username, password, server, port, dbname) => { - properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) - } - } - } else { + case pgurl2(protocol, username, password, server, port, dbname) => { + properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) + } + case _ => { + logger.warn(s"Connection url '$connectionURL' could not be parsed.") properties } } From 4074c732637c367cbbffc0640263c28d43f230c5 Mon Sep 17 00:00:00 2001 From: Martin Grotzke Date: Fri, 5 Jul 2013 09:40:46 +0200 Subject: [PATCH 134/357] Fix jdbc:postgresql url format to use 'user' instead 'username'. When using a jdbc:postgresql url valid according to the current ParserURL the jdbc driver (used by the evolutions plugin) cannot connect to the db as it's missing the user connection parameter. According to the postgresql jdbc driver documentation the correct parameter is 'user' instead of the previously used 'username' - this shouldn't have worked for anyone. See: http://jdbc.postgresql.org/documentation/91/connect.html#connection-parameters --- .../mauricio/async/db/postgresql/util/ParserURL.scala | 2 +- .../mauricio/async/db/postgresql/util/URLParserSpec.scala | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) 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 index 5d0ea9d5..31652262 100644 --- 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 @@ -21,7 +21,7 @@ object ParserURL { val DEFAULT_PORT = "5432" - private val pgurl1 = """(jdbc:postgresql)://([^:]*|\[.+\])(?::(\d+))?/([^?]+)(?:\?username=(.*)&password=(.*))?""".r + private val pgurl1 = """(jdbc:postgresql)://([^:]*|\[.+\])(?::(\d+))?/([^?]+)(?:\?user=(.*)&password=(.*))?""".r private val pgurl2 = """(postgres|postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r def parse(connectionURL: String): Map[String, String] = { 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 1902032e..87cdd47e 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 @@ -24,7 +24,7 @@ class URLParserSpec extends Specification { "parser" should { "create a connection with the available fields" in { - val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database?username=john&password=doe" + val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database?user=john&password=doe" val configuration = URLParser.parse(connectionUri) configuration.username === "john" @@ -35,7 +35,7 @@ class URLParserSpec extends Specification { } "create a connection without port" in { - val connectionUri = "jdbc:postgresql://128.567.54.90/my_database?username=john&password=doe" + val connectionUri = "jdbc:postgresql://128.567.54.90/my_database?user=john&password=doe" val configuration = URLParser.parse(connectionUri) configuration.username === "john" @@ -80,7 +80,7 @@ class URLParserSpec extends Specification { } "create a connection with the available fields and named server" in { - val connectionUri = "jdbc:postgresql://localhost:9987/my_database?username=john&password=doe" + val connectionUri = "jdbc:postgresql://localhost:9987/my_database?user=john&password=doe" val configuration = URLParser.parse(connectionUri) configuration.username === "john" @@ -102,7 +102,7 @@ class URLParserSpec extends Specification { } "create a connection with the available fields and ipv6" in { - val connectionUri = "jdbc:postgresql://[::1]:9987/my_database?username=john&password=doe" + val connectionUri = "jdbc:postgresql://[::1]:9987/my_database?user=john&password=doe" val configuration = URLParser.parse(connectionUri) From 2aa43ad0d88c9798e56919e97f99d47567efcbcc Mon Sep 17 00:00:00 2001 From: Martin Grotzke Date: Fri, 5 Jul 2013 10:39:18 +0200 Subject: [PATCH 135/357] Change README to mention 'user' instead of 'username. --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 510f62a0..cd2532c4 100644 --- a/README.markdown +++ b/README.markdown @@ -172,7 +172,7 @@ object BasicExample { def main(args: Array[String]) { - val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?username=postgres&password=somepassword") + val configuration = URLParser.parse("jdbc:postgresql://localhost:5233/my_database?user=postgres&password=somepassword") val connection: Connection = new PostgreSQLConnection(configuration) Await.result(connection.connect, 5 seconds) From 65f89920ef44ec92e13e66b24a7e1d5df7c46d25 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 6 Jul 2013 17:32:44 -0300 Subject: [PATCH 136/357] Reuse the same NioClientSocketChannelFactory for all connections - fixes #36 --- CHANGELOG.md | 10 +++++++ README.markdown | 11 ++++---- .../mauricio/async/db/Configuration.scala | 12 ++------- .../mauricio/async/db/util/NettyUtils.scala | 26 +++++++++++++++++++ .../async/db/mysql/MySQLConnection.scala | 14 ++++++++-- .../mysql/codec/MySQLConnectionHandler.scala | 14 ++++------ .../db/postgresql/PostgreSQLConnection.scala | 8 +++--- .../codec/PostgreSQLConnectionHandler.scala | 15 +++++------ .../async/db/postgresql/util/URLParser.scala | 8 +----- 9 files changed, 72 insertions(+), 46 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/NettyUtils.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index beca8b09..ccc51f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.2.4 - 2013-07-06 + +* Mysql driver fails for null TIMESTAMP +* Mysql driver fails for big strings (TEXT) +* Support (auto convert) Option in prepared stmt parameters - @magro - #30 +* Allow database connections strings without port, username and password - @magro - #32 +* Support 'postgres' protocol in heroku like db urls - @magro - #33 +* Fix jdbc:postgresql url format to use 'user' instead 'username' - #35 + + ## 0.2.3 - 2013-05-21 * Upgraded Netty to 3.6.6.Final diff --git a/README.markdown b/README.markdown index cd2532c4..8f8d79bc 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.3" +"com.github.mauricio" %% "postgresql-async" % "0.2.4" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.3 + 0.2.4 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.3" +"com.github.mauricio" %% "mysql-async" % "0.2.4" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.3 + 0.2.4 ``` @@ -208,9 +208,10 @@ Check the blog post above for more details and the project's ScalaDocs. ## Contributors +* [devsprint](https://github.com/devsprint) * [fwbrasil](https://github.com/fwbrasil) +* [magro](https://github.com/magro) * [theon](https://github.com/theon) -* [devsprint](https://github.com/devsprint) ## Licence 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 449a2b16..716ecabd 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 @@ -16,12 +16,10 @@ package com.github.mauricio.async.db -import com.github.mauricio.async.db.util.ExecutorServiceUtils import java.nio.charset.Charset -import java.util.concurrent.ExecutorService -import scala.{None, Option, Int} -import scala.Predef._ import org.jboss.netty.util.CharsetUtil +import scala.Predef._ +import scala.{None, Option, Int} object Configuration { val DefaultCharset = CharsetUtil.UTF_8 @@ -37,10 +35,6 @@ object Configuration { * @param port database port, defaults to 5432 * @param password password, defaults to no password * @param database database name, defaults to no database - * @param bossPool executor service used by by the Netty boss handler, defaults to the driver's - * cached thread pool at [[com.github.mauricio.async.db.util.ExecutorServiceUtils]] - * @param workerPool executor service used by the Netty worker handler, defaults to the driver's - * cached thread pool at [[com.github.mauricio.async.db.util.ExecutorServiceUtils]] * @param charset charset for the connection, defaults to UTF-8, make sure you know what you are doing if you * change this * @param maximumMessageSize the maximum size a message from the server could possibly have, this limits possible @@ -54,8 +48,6 @@ case class Configuration(username: String, port: Int = 5432, password: Option[String] = None, database: Option[String] = None, - bossPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, - workerPool: ExecutorService = ExecutorServiceUtils.CachedThreadPool, charset: Charset = Configuration.DefaultCharset, maximumMessageSize: Int = 16777216 ) 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 new file mode 100644 index 00000000..03d508aa --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/NettyUtils.scala @@ -0,0 +1,26 @@ +package com.github.mauricio.async.db.util + +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory + +/* + * Copyright 2013 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * 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. + */ + +object NettyUtils { + + val DetaultSocketChannelFactory = new NioClientSocketChannelFactory( + ExecutorServiceUtils.CachedThreadPool, + ExecutorServiceUtils.CachedThreadPool) + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index c858b267..767da5bb 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -24,13 +24,23 @@ import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture -import com.github.mauricio.async.db.util.{Version, Log} +import com.github.mauricio.async.db.util._ import java.util.concurrent.atomic.AtomicLong import org.jboss.netty.channel._ import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure import scala.util.Success +import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage +import com.github.mauricio.async.db.mysql.message.client.HandshakeResponseMessage +import com.github.mauricio.async.db.mysql.message.server.ErrorMessage +import com.github.mauricio.async.db.mysql.message.client.QueryMessage +import scala.util.Failure +import scala.Some +import com.github.mauricio.async.db.mysql.message.server.OkMessage +import com.github.mauricio.async.db.mysql.message.client.PreparedStatementMessage +import scala.util.Success +import com.github.mauricio.async.db.mysql.message.server.EOFMessage object MySQLConnection { final val log = Log.get[MySQLConnection] @@ -52,7 +62,7 @@ class MySQLConnection( charsetMapper.toInt(configuration.charset) private final val connectionCount = MySQLConnection.Counter.incrementAndGet() - private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) + private implicit val internalPool = ExecutorServiceUtils.CachedExecutionContext private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 7f4d6b55..a81c37af 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -23,16 +23,16 @@ import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util._ import java.net.InetSocketAddress import java.nio.ByteOrder import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.buffer.HeapChannelBufferFactory import org.jboss.netty.channel._ -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory +import scala.Some import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} -import scala.concurrent.{ExecutionContext, Promise, Future} +import scala.concurrent.{Promise, Future} object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -46,13 +46,9 @@ class MySQLConnectionHandler( extends SimpleChannelHandler with LifeCycleAwareChannelHandler { + private implicit val internalPool = ExecutorServiceUtils.CachedExecutionContext - private implicit val internalPool = ExecutionContext.fromExecutorService(configuration.workerPool) - - private final val factory = new NioClientSocketChannelFactory( - configuration.bossPool, - configuration.workerPool, - 1) + private final val factory = NettyUtils.DetaultSocketChannelFactory private final val bootstrap = new ClientBootstrap(this.factory) private final val connectionPromise = Promise[MySQLConnectionHandler] 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 3d942476..c7a58da8 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 @@ -18,19 +18,19 @@ 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.ConnectionStillRunningQueryException import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler} import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, PostgreSQLColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ -import com.github.mauricio.async.db.util.{Version, Log} +import com.github.mauricio.async.db.util._ import com.github.mauricio.async.db.{Configuration, Connection} import java.util.concurrent.atomic._ import messages.backend._ import messages.frontend._ import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some -import scala.concurrent.{ExecutionContext, Future, Promise} -import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException +import scala.concurrent.{Future, Promise} object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] @@ -52,7 +52,7 @@ class PostgreSQLConnection private final val connectionHandler = new PostgreSQLConnectionHandler( configuration, encoderRegistry, decoderRegistry, this ) private final val currentCount = Counter.incrementAndGet() private final val preparedStatementsCounter = new AtomicInteger() - private final implicit val executionContext = ExecutionContext.fromExecutorService(configuration.workerPool) + private final implicit val executionContext = ExecutorServiceUtils.CachedExecutionContext private var readyForQuery = false private val parameterStatus = new scala.collection.mutable.HashMap[String, String]() diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index de31af4b..6ba8a35f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -22,14 +22,14 @@ import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.postgresql.messages.backend._ import com.github.mauricio.async.db.postgresql.messages.frontend._ import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util._ import java.net.InetSocketAddress import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.channel._ -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import scala.annotation.switch -import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.util.{Failure, Success} +import scala.concurrent.{Future, Promise} +import scala.util.Failure +import scala.util.Success object PostgreSQLConnectionHandler { final val log = Log.get[PostgreSQLConnectionHandler] @@ -56,12 +56,9 @@ class PostgreSQLConnectionHandler "DateStyle" -> "ISO", "extra_float_digits" -> "2") - private final val factory = new NioClientSocketChannelFactory( - configuration.bossPool, - configuration.workerPool, - 1) + private final val factory = NettyUtils.DetaultSocketChannelFactory - private final implicit val executionContext = ExecutionContext.fromExecutorService(configuration.workerPool) + private final implicit val executionContext = ExecutorServiceUtils.CachedExecutionContext private final val bootstrap = new ClientBootstrap(this.factory) private final val connectionFuture = Promise[PostgreSQLConnectionHandler]() private final val disconnectionPromise = Promise[PostgreSQLConnectionHandler]() 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 34a6861e..73cc33c4 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 @@ -18,8 +18,6 @@ package com.github.mauricio.async.db.postgresql.util import com.github.mauricio.async.db.Configuration import java.nio.charset.Charset -import java.util.concurrent.ExecutorService -import scala.collection.JavaConversions._ object URLParser { @@ -29,8 +27,6 @@ object URLParser { import Configuration.Default def parse(url: String, - bossPool: ExecutorService = Default.bossPool, - workerPool: ExecutorService = Default.workerPool, charset: Charset = Default.charset ): Configuration = { @@ -44,9 +40,7 @@ object URLParser { database = properties.get(ParserURL.PGDBNAME), host = properties(ParserURL.PGHOST), port = port, - charset = charset, - workerPool = workerPool, - bossPool = bossPool + charset = charset ) } From f38218e591c0498d7201fcb3dea6ebc6f849e8e2 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 6 Jul 2013 18:59:10 -0300 Subject: [PATCH 137/357] Fixing failed build and updating dependencies --- Procfile | 2 +- .../db/mysql/ExecuteManyQueriesSpec.scala | 4 +++ .../binary/decoder/BinaryDecodersSpec.scala | 29 ------------------- .../db/postgresql/PostgreSQLConnection.scala | 3 +- .../db/postgresql/DatabaseTestHelper.scala | 2 ++ .../postgresql/PostgreSQLConnectionSpec.scala | 4 +-- project/Build.scala | 8 ++--- 7 files changed, 15 insertions(+), 37 deletions(-) delete mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala diff --git a/Procfile b/Procfile index 04210f34..e30234e4 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -postgresql: /usr/local/Cellar/postgresql/9.1.5/bin/postgres -D /Users/mauricio/databases/postgresql +postgresql: /usr/local/Cellar/postgresql/9.2.4/bin/postgres -D /Users/mauricio/databases/postgresql mysql: mysqld --log-warnings --console \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala index 77232f80..1086af78 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ExecuteManyQueriesSpec.scala @@ -37,6 +37,8 @@ class ExecuteManyQueriesSpec extends Specification with ConnectionHelper { row(0) === 6578 row(1) === "this is some text" } + + success } } @@ -55,6 +57,8 @@ class ExecuteManyQueriesSpec extends Specification with ConnectionHelper { row(0) === 6578 row(1) === "this is some text" } + + success } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala deleted file mode 100644 index f5c25517..00000000 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecodersSpec.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.mysql.binary.decoder - -import org.specs2.mutable.Specification - -class BinaryDecodersSpec extends Specification { - - "decoders" should { - - - - } - -} 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 c7a58da8..6815a473 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 @@ -36,6 +36,7 @@ object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) val Counter = new AtomicLong() + val ServerVersionKey = "server_version" } class PostgreSQLConnection @@ -192,7 +193,7 @@ class PostgreSQLConnection override def onParameterStatus(m: ParameterStatusMessage) { this.parameterStatus.put(m.key, m.value) - if ( m.key == "server_version" ) { + if ( ServerVersionKey == m.key ) { this.version = Version(m.value) } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index 2872dd85..60a689e6 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -64,6 +64,8 @@ trait DatabaseTestHelper { if (rows != count) { throw new IllegalStateException("We expected %s rows but there were %s".format(count, rows)) } + + rows } private def handleTimeout[R]( handler : Connection, fn : => R ) = { 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 ffebc1c2..d1681586 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 @@ -112,7 +112,7 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { withHandler { handler => - executeDdl(handler, this.create) + executeDdl(handler, this.create) === 0 } } @@ -122,7 +122,7 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { withHandler { handler => executeDdl(handler, this.create) - executeDdl(handler, this.insert, 1) + executeDdl(handler, this.insert, 1) === 1 } diff --git a/project/Build.scala b/project/Build.scala index 351918c8..adf1e2cd 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -47,14 +47,14 @@ object Configuration { val commonVersion = "0.2.3" - val specs2Dependency = "org.specs2" %% "specs2" % "1.14" % "test" + val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" val commonDependencies = Seq( "commons-pool" % "commons-pool" % "1.6", - "ch.qos.logback" % "logback-classic" % "1.0.9", + "ch.qos.logback" % "logback-classic" % "1.0.13", "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", - "org.scala-lang" % "scala-library" % "2.10.1", + "org.scala-lang" % "scala-library" % "2.10.2", "io.netty" % "netty" % "3.6.6.Final", specs2Dependency ) @@ -71,7 +71,7 @@ object Configuration { :+ "-feature" , scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), - scalaVersion := "2.10.1", + scalaVersion := "2.10.2", javacOptions := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8"), organization := "com.github.mauricio", version := commonVersion, From d1d5426ad557131bf2654ca14100b70f3f39ab49 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 18 Jul 2013 16:58:00 -0300 Subject: [PATCH 138/357] Closing 0.2.4 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index adf1e2cd..ae6f17d7 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.3" + val commonVersion = "0.2.4" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" From 6db77b3203e6713fbb79cc41ea24dbe1a83771d7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 18 Jul 2013 17:04:29 -0300 Subject: [PATCH 139/357] Preparing for next version --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index ae6f17d7..18f2abf0 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.4" + val commonVersion = "0.2.5-SNAPSHOT" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" From 7adbec5d6721f9e978bb8a15267d12804a6c4c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Fri, 19 Jul 2013 09:15:04 -0300 Subject: [PATCH 140/357] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc51f98..2fbf31d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Support (auto convert) Option in prepared stmt parameters - @magro - #30 * Allow database connections strings without port, username and password - @magro - #32 * Support 'postgres' protocol in heroku like db urls - @magro - #33 -* Fix jdbc:postgresql url format to use 'user' instead 'username' - #35 +* Fix jdbc:postgresql url format to use 'user' instead 'username' - @magro - #35 ## 0.2.3 - 2013-05-21 @@ -37,4 +37,4 @@ ## 0.1.0 - 2013-04-29 -* First public release \ No newline at end of file +* First public release From d703fd2d93ce6b1e86c9ae126a10827a27e1e55e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 30 Jul 2013 22:31:35 -0300 Subject: [PATCH 141/357] Fixes #38 - also requires providers to implement an NioClientSocketChannelFactory --- .../async/db/mysql/MySQLConnection.scala | 19 ++++++------------- .../mysql/codec/MySQLConnectionHandler.scala | 13 +++++++------ .../db/postgresql/PostgreSQLConnection.scala | 18 ++++++++++++++---- .../codec/PostgreSQLConnectionHandler.scala | 13 +++++++------ project/Build.scala | 2 +- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 767da5bb..68ffb1a9 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -27,20 +27,11 @@ import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import java.util.concurrent.atomic.AtomicLong import org.jboss.netty.channel._ +import org.jboss.netty.channel.socket.ClientSocketChannelFactory import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure import scala.util.Success -import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage -import com.github.mauricio.async.db.mysql.message.client.HandshakeResponseMessage -import com.github.mauricio.async.db.mysql.message.server.ErrorMessage -import com.github.mauricio.async.db.mysql.message.client.QueryMessage -import scala.util.Failure -import scala.Some -import com.github.mauricio.async.db.mysql.message.server.OkMessage -import com.github.mauricio.async.db.mysql.message.client.PreparedStatementMessage -import scala.util.Success -import com.github.mauricio.async.db.mysql.message.server.EOFMessage object MySQLConnection { final val log = Log.get[MySQLConnection] @@ -50,7 +41,9 @@ object MySQLConnection { class MySQLConnection( configuration: Configuration, - charsetMapper: CharsetMapper = CharsetMapper.Instance + charsetMapper: CharsetMapper = CharsetMapper.Instance, + socketFactory : ClientSocketChannelFactory = NettyUtils.DetaultSocketChannelFactory, + executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends MySQLHandlerDelegate with Connection @@ -62,9 +55,9 @@ class MySQLConnection( charsetMapper.toInt(configuration.charset) private final val connectionCount = MySQLConnection.Counter.incrementAndGet() - private implicit val internalPool = ExecutorServiceUtils.CachedExecutionContext + private implicit val internalPool = executionContext - private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this) + private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, socketFactory, executionContext) private final val connectionPromise = Promise[Connection]() private final val disconnectionPromise = Promise[Connection]() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index a81c37af..d499fe49 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -29,10 +29,11 @@ import java.nio.ByteOrder import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.buffer.HeapChannelBufferFactory import org.jboss.netty.channel._ +import org.jboss.netty.channel.socket.ClientSocketChannelFactory import scala.Some import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} -import scala.concurrent.{Promise, Future} +import scala.concurrent._ object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -41,16 +42,16 @@ object MySQLConnectionHandler { class MySQLConnectionHandler( configuration: Configuration, charsetMapper: CharsetMapper, - handlerDelegate: MySQLHandlerDelegate + handlerDelegate: MySQLHandlerDelegate, + socketFactory : ClientSocketChannelFactory, + executionContext : ExecutionContext ) extends SimpleChannelHandler with LifeCycleAwareChannelHandler { - private implicit val internalPool = ExecutorServiceUtils.CachedExecutionContext + private implicit val internalPool = executionContext - private final val factory = NettyUtils.DetaultSocketChannelFactory - - private final val bootstrap = new ClientBootstrap(this.factory) + private final val bootstrap = new ClientBootstrap(this.socketFactory) private final val connectionPromise = Promise[MySQLConnectionHandler] private final val decoder = new MySQLFrameDecoder(configuration.charset) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) 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 6815a473..42a1d88e 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 @@ -28,9 +28,10 @@ import com.github.mauricio.async.db.{Configuration, Connection} import java.util.concurrent.atomic._ import messages.backend._ import messages.frontend._ +import org.jboss.netty.channel.socket.ClientSocketChannelFactory import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some -import scala.concurrent.{Future, Promise} +import scala.concurrent._ object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] @@ -43,17 +44,26 @@ class PostgreSQLConnection ( configuration: Configuration = Configuration.Default, encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, - decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance + decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance, + socketFactory : ClientSocketChannelFactory = NettyUtils.DetaultSocketChannelFactory, + executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends PostgreSQLConnectionDelegate with Connection { import PostgreSQLConnection._ - private final val connectionHandler = new PostgreSQLConnectionHandler( configuration, encoderRegistry, decoderRegistry, this ) + private final val connectionHandler = new PostgreSQLConnectionHandler( + configuration, + encoderRegistry, + decoderRegistry, + this, + socketFactory, + executionContext + ) private final val currentCount = Counter.incrementAndGet() private final val preparedStatementsCounter = new AtomicInteger() - private final implicit val executionContext = ExecutorServiceUtils.CachedExecutionContext + private final implicit val internalExecutionContext = executionContext private var readyForQuery = false private val parameterStatus = new scala.collection.mutable.HashMap[String, String]() diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index 6ba8a35f..92d595ab 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -26,8 +26,9 @@ import com.github.mauricio.async.db.util._ import java.net.InetSocketAddress import org.jboss.netty.bootstrap.ClientBootstrap import org.jboss.netty.channel._ +import org.jboss.netty.channel.socket.ClientSocketChannelFactory import scala.annotation.switch -import scala.concurrent.{Future, Promise} +import scala.concurrent._ import scala.util.Failure import scala.util.Success @@ -40,7 +41,9 @@ class PostgreSQLConnectionHandler configuration: Configuration, encoderRegistry: ColumnEncoderRegistry, decoderRegistry: ColumnDecoderRegistry, - connectionDelegate : PostgreSQLConnectionDelegate + connectionDelegate : PostgreSQLConnectionDelegate, + socketFactory : ClientSocketChannelFactory, + executionContext : ExecutionContext ) extends SimpleChannelHandler with LifeCycleAwareChannelHandler @@ -56,10 +59,8 @@ class PostgreSQLConnectionHandler "DateStyle" -> "ISO", "extra_float_digits" -> "2") - private final val factory = NettyUtils.DetaultSocketChannelFactory - - private final implicit val executionContext = ExecutorServiceUtils.CachedExecutionContext - private final val bootstrap = new ClientBootstrap(this.factory) + private implicit final val _executionContext = executionContext + private final val bootstrap = new ClientBootstrap(this.socketFactory) private final val connectionFuture = Promise[PostgreSQLConnectionHandler]() private final val disconnectionPromise = Promise[PostgreSQLConnectionHandler]() private var processData : ProcessData = null diff --git a/project/Build.scala b/project/Build.scala index 18f2abf0..11daa50f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -103,7 +103,7 @@ object Configuration { - mauricio-linhares + mauricio Maurício Linhares https://github.com/mauricio From d6b08627f30bbcc1cf76b1baa530216f405fc84a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 30 Jul 2013 22:38:06 -0300 Subject: [PATCH 142/357] Fixing the scala version --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index e69de29b..b78915ad 100644 --- a/build.sbt +++ b/build.sbt @@ -0,0 +1 @@ +scalaVersion := "2.10.2" \ No newline at end of file From c71e87a9544cc8236ca87ee4e5242c2a625fa1d7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 30 Jul 2013 22:40:19 -0300 Subject: [PATCH 143/357] Updating scala version on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 15c97117..c04d3e61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala scala: - - 2.10.1 + - 2.10.2 jdk: - oraclejdk7 - openjdk7 From cf7e0755079fcdbbb22d7950ba62f1526aa44e8e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 30 Jul 2013 23:00:21 -0300 Subject: [PATCH 144/357] Adding a query with limits --- .../db/mysql/PreparedStatementsSpec.scala | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 43f07837..30279c52 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -245,8 +245,6 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { "read a timestamp with microseconds" in { - - val create = """CREATE TEMPORARY TABLE posts ( id INT NOT NULL AUTO_INCREMENT, @@ -346,6 +344,38 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } } + "bind parameters on a prepared statement with limit" in { + + val create = """CREATE TEMPORARY TABLE posts ( + | id INT NOT NULL AUTO_INCREMENT, + | some_text TEXT not null, + | primary key (id) )""".stripMargin + + val insert = "insert into posts (some_text) values (?)" + val select = "select * from posts limit 100" + + withConnection { + connection => + executeQuery(connection, create) + + 1.until(10).foreach { index => + executePreparedStatement(connection, insert, "this is some text here") + } + + val row = executeQuery(connection, select).rows.get(0) + + row("id") === 1 + row("some_text") === "this is some text here" + + val queryRow = executeQuery(connection, select).rows.get(0) + + queryRow("id") === 1 + queryRow("some_text") === "this is some text here" + + + } + } + } } \ No newline at end of file From cbf0824084651d14fdb94245a69e9fedc2866562 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 30 Jul 2013 23:09:15 -0300 Subject: [PATCH 145/357] Updating changelog and readme --- CHANGELOG.md | 5 +++++ README.markdown | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fbf31d3..901f01b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.2.5 - SNAPSHOT + +* Allow the ClientSocketChannelFactory and ExecutionContext to be given at the connections instead of + always using the driver provided ones - #38 + ## 0.2.4 - 2013-07-06 * Mysql driver fails for null TIMESTAMP diff --git a/README.markdown b/README.markdown index 8f8d79bc..20b2347d 100644 --- a/README.markdown +++ b/README.markdown @@ -213,6 +213,16 @@ Check the blog post above for more details and the project's ScalaDocs. * [magro](https://github.com/magro) * [theon](https://github.com/theon) +## Contributing + +Contributing to the project is simple, fork it on Github, hack on what you're insterested in seeing done or at the +bug you want to fix and send a pull request back. If you thing the change is too big or requires architectural changes +please create an issue **before** you start working on it so we can discuss what you're trying to do. + +You should be easily able to build this project in your favorite IDE since it's built by [SBT](http://www.scala-sbt.org/) +using a plugin that generates your IDE's project files. You can use [sbt-idea](https://github.com/mpeltonen/sbt-idea) +for IntelliJ Idea and [sbteclipse](https://github.com/typesafehub/sbteclipse) for Eclipse integration. + ## Licence This project is freely available under the Apache 2 licence, fork, fix and send back :) From ae8f59d5c6417e16fe8023c036715461c6666616 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 4 Aug 2013 02:56:40 -0300 Subject: [PATCH 146/357] Fixes issue with NULL handling when executing a common query (not a prepared statement one) when some of the fields are NULL - fixes #39 --- .../async/db/mysql/codec/MySQLFrameDecoder.scala | 3 --- .../async/db/mysql/decoder/ResultSetRowDecoder.scala | 3 ++- .../async/db/mysql/PreparedStatementsSpec.scala | 10 +++++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 7d9bff4d..253f42a0 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -26,7 +26,6 @@ import java.nio.charset.Charset import org.jboss.netty.buffer.ChannelBuffer import org.jboss.netty.channel.{Channel, ChannelHandlerContext} import org.jboss.netty.handler.codec.frame.FrameDecoder -import com.github.mauricio.async.db.mysql.MySQLHelper object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] @@ -34,8 +33,6 @@ object MySQLFrameDecoder { class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { - import MySQLFrameDecoder.log - private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) private final val okDecoder = new OkDecoder(charset) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala index b66abfc2..977d6eec 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala @@ -35,7 +35,8 @@ class ResultSetRowDecoder( charset : Charset ) extends MessageDecoder { val row = new ResultSetRowMessage() while ( buffer.readable() ) { - if ( buffer.getByte(buffer.readerIndex()) == NULL ) { + if ( buffer.getUnsignedByte(buffer.readerIndex()) == NULL ) { + buffer.readByte() row += null } else { val length = buffer.readBinaryLength.asInstanceOf[Int] diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 30279c52..d8a3178e 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -349,6 +349,7 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { val create = """CREATE TEMPORARY TABLE posts ( | id INT NOT NULL AUTO_INCREMENT, | some_text TEXT not null, + | some_date DATE, | primary key (id) )""".stripMargin val insert = "insert into posts (some_text) values (?)" @@ -358,20 +359,19 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { connection => executeQuery(connection, create) - 1.until(10).foreach { index => - executePreparedStatement(connection, insert, "this is some text here") - } + executePreparedStatement(connection, insert, "this is some text here") val row = executeQuery(connection, select).rows.get(0) row("id") === 1 row("some_text") === "this is some text here" + row("some_date") must beNull - val queryRow = executeQuery(connection, select).rows.get(0) + val queryRow = executePreparedStatement(connection, select).rows.get(0) queryRow("id") === 1 queryRow("some_text") === "this is some text here" - + queryRow("some_date") must beNull } } From b45c753670498f3bb89e5402e210b1178f439729 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 1 Aug 2013 12:43:03 +0200 Subject: [PATCH 147/357] Port over to use netty 4 --- .../mauricio/async/db/Configuration.scala | 2 +- .../async/db/column/ColumnDecoder.scala | 4 +- .../db/column/ColumnDecoderRegistry.scala | 4 +- .../BufferNotFullyConsumedException.scala | 4 +- .../CanceledChannelFutureException.scala | 2 +- .../async/db/general/MutableResultSet.scala | 1 - .../mauricio/async/db/util/BitMap.scala | 6 +- .../db/util/ChannelFutureTransformer.scala | 6 +- .../mauricio/async/db/util/ChannelUtils.scala | 24 ++--- .../async/db/util/ChannelWrapper.scala | 6 +- .../mauricio/async/db/util/NettyUtils.scala | 6 +- .../mauricio/async/db/util/PrintUtils.scala | 4 +- .../async/db/util/ChannelUtilsSpec.scala | 10 +-- .../mauricio/async/db/mysql/MySQLHelper.java | 4 +- .../async/db/mysql/MySQLConnection.scala | 12 +-- .../db/mysql/binary/BinaryRowDecoder.scala | 4 +- .../db/mysql/binary/BinaryRowEncoder.scala | 8 +- .../binary/decoder/BigDecimalDecoder.scala | 4 +- .../mysql/binary/decoder/BinaryDecoder.scala | 4 +- .../binary/decoder/ByteArrayDecoder.scala | 4 +- .../db/mysql/binary/decoder/ByteDecoder.scala | 4 +- .../db/mysql/binary/decoder/DateDecoder.scala | 4 +- .../mysql/binary/decoder/DoubleDecoder.scala | 4 +- .../mysql/binary/decoder/FloatDecoder.scala | 4 +- .../mysql/binary/decoder/IntegerDecoder.scala | 4 +- .../db/mysql/binary/decoder/LongDecoder.scala | 4 +- .../db/mysql/binary/decoder/NullDecoder.scala | 4 +- .../mysql/binary/decoder/ShortDecoder.scala | 4 +- .../mysql/binary/decoder/StringDecoder.scala | 4 +- .../db/mysql/binary/decoder/TimeDecoder.scala | 4 +- .../binary/decoder/TimestampDecoder.scala | 4 +- .../mysql/binary/encoder/BinaryEncoder.scala | 4 +- .../mysql/binary/encoder/BooleanEncoder.scala | 4 +- .../binary/encoder/ByteArrayEncoder.scala | 4 +- .../db/mysql/binary/encoder/ByteEncoder.scala | 4 +- .../binary/encoder/CalendarEncoder.scala | 4 +- .../binary/encoder/DateTimeEncoder.scala | 4 +- .../mysql/binary/encoder/DoubleEncoder.scala | 4 +- .../binary/encoder/DurationEncoder.scala | 4 +- .../mysql/binary/encoder/FloatEncoder.scala | 4 +- .../mysql/binary/encoder/IntegerEncoder.scala | 4 +- .../binary/encoder/JavaDateEncoder.scala | 4 +- .../binary/encoder/LocalDateEncoder.scala | 4 +- .../binary/encoder/LocalDateTimeEncoder.scala | 4 +- .../binary/encoder/LocalTimeEncoder.scala | 4 +- .../db/mysql/binary/encoder/LongEncoder.scala | 4 +- .../encoder/ReadableInstantEncoder.scala | 4 +- .../mysql/binary/encoder/SQLDateEncoder.scala | 4 +- .../mysql/binary/encoder/SQLTimeEncoder.scala | 4 +- .../binary/encoder/SQLTimestampEncoder.scala | 4 +- .../mysql/binary/encoder/ShortEncoder.scala | 4 +- .../mysql/binary/encoder/StringEncoder.scala | 4 +- .../codec/LittleEndianByteBufAllocator.scala | 69 +++++++++++++++ .../mysql/codec/MySQLConnectionHandler.scala | 88 +++++++++++-------- .../db/mysql/codec/MySQLFrameDecoder.scala | 34 +++---- .../db/mysql/codec/MySQLHandlerDelegate.scala | 2 +- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 11 ++- .../mysql/column/ByteArrayColumnDecoder.scala | 4 +- .../decoder/ColumnDefinitionDecoder.scala | 4 +- .../ColumnProcessingFinishedDecoder.scala | 4 +- .../db/mysql/decoder/EOFMessageDecoder.scala | 4 +- .../async/db/mysql/decoder/ErrorDecoder.scala | 4 +- .../mysql/decoder/HandshakeV10Decoder.scala | 4 +- .../db/mysql/decoder/MessageDecoder.scala | 4 +- .../async/db/mysql/decoder/OkDecoder.scala | 4 +- ...amAndColumnProcessingFinishedDecoder.scala | 4 +- .../ParamProcessingFinishedDecoder.scala | 4 +- ...paredStatementPrepareResponseDecoder.scala | 4 +- .../mysql/decoder/ResultSetRowDecoder.scala | 10 ++- .../encoder/HandshakeResponseEncoder.scala | 4 +- .../db/mysql/encoder/MessageEncoder.scala | 4 +- .../PreparedStatementExecuteEncoder.scala | 6 +- .../PreparedStatementPrepareEncoder.scala | 4 +- .../mysql/encoder/QueryMessageEncoder.scala | 4 +- .../db/mysql/encoder/QuitMessageEncoder.scala | 4 +- .../message/server/BinaryRowMessage.scala | 4 +- .../message/server/ResultSetRowMessage.scala | 20 ++--- .../async/db/mysql/util/CharsetMapper.scala | 2 +- .../mauricio/async/db/mysql/QuerySpec.scala | 3 +- .../mysql/binary/BinaryRowDecoderSpec.scala | 8 +- .../mysql/binary/BinaryRowEncoderSpec.scala | 2 +- .../mysql/codec/MySQLFrameDecoderSpec.scala | 65 +++++++------- .../db/postgresql/PostgreSQLConnection.scala | 10 +-- .../db/postgresql/codec/MessageDecoder.scala | 22 ++--- .../db/postgresql/codec/MessageEncoder.scala | 11 ++- .../codec/PostgreSQLConnectionHandler.scala | 76 ++++++++-------- .../PostgreSQLColumnDecoderRegistry.scala | 6 +- .../PostgreSQLColumnEncoderRegistry.scala | 1 - .../encoders/CloseMessageEncoder.scala | 6 +- .../encoders/CredentialEncoder.scala | 6 +- .../db/postgresql/encoders/Encoder.scala | 4 +- .../ExecutePreparedStatementEncoder.scala | 4 +- .../PreparedStatementEncoderHelper.scala | 14 +-- .../PreparedStatementOpeningEncoder.scala | 8 +- .../encoders/QueryMessageEncoder.scala | 6 +- .../encoders/StartupMessageEncoder.scala | 6 +- .../messages/backend/DataRowMessage.scala | 4 +- .../parsers/AuthenticationStartupParser.scala | 4 +- .../parsers/BackendKeyDataParser.scala | 4 +- .../parsers/CommandCompleteParser.scala | 4 +- .../db/postgresql/parsers/DataRowParser.scala | 10 +-- .../parsers/InformationParser.scala | 6 +- .../db/postgresql/parsers/MessageParser.scala | 4 +- .../parsers/MessageParsersRegistry.scala | 4 +- .../parsers/ParameterStatusParser.scala | 4 +- .../parsers/ReadyForQueryParser.scala | 4 +- .../parsers/ReturningMessageParser.scala | 4 +- .../parsers/RowDescriptionParser.scala | 4 +- .../db/general/MutableResultSetSpec.scala | 12 +-- .../db/postgresql/MessageDecoderSpec.scala | 39 ++++---- .../db/postgresql/parsers/ParserESpec.scala | 6 +- .../db/postgresql/parsers/ParserKSpec.scala | 4 +- .../db/postgresql/parsers/ParserSSpec.scala | 6 +- .../postgresql/util/PasswordHelperSpec.scala | 2 +- project/Build.scala | 4 +- 115 files changed, 519 insertions(+), 425 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.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 716ecabd..30cf27bd 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 @@ -17,9 +17,9 @@ package com.github.mauricio.async.db import java.nio.charset.Charset -import org.jboss.netty.util.CharsetUtil import scala.Predef._ import scala.{None, Option, Int} +import io.netty.util.CharsetUtil object Configuration { val DefaultCharset = CharsetUtil.UTF_8 diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala index 37e819a5..19b98bc1 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.column -import org.jboss.netty.buffer.ChannelBuffer import java.nio.charset.Charset +import io.netty.buffer.ByteBuf trait ColumnDecoder { - def decode(value: ChannelBuffer, charset : Charset): Any = { + def decode(value: ByteBuf, charset : Charset): Any = { val bytes = new Array[Byte](value.readableBytes()) value.readBytes(bytes) decode(new String(bytes, charset)) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala index 1bcc740f..6ceac0fb 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.column -import org.jboss.netty.buffer.ChannelBuffer import java.nio.charset.Charset +import io.netty.buffer.ByteBuf trait ColumnDecoderRegistry { - def decode(kind: Int, value: ChannelBuffer, charset : Charset) : Any + def decode(kind: Int, value: ByteBuf, charset : Charset) : Any } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala index bae6723d..52e7481f 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/BufferNotFullyConsumedException.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.exceptions -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf -class BufferNotFullyConsumedException ( buffer : ChannelBuffer ) +class BufferNotFullyConsumedException ( buffer : ByteBuf ) extends DatabaseException( "Buffer was not fully consumed by decoder, %s bytes to read".format(buffer.readableBytes()) ) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala index 322db656..db1168e1 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/CanceledChannelFutureException.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.exceptions -import org.jboss.netty.channel.ChannelFuture +import io.netty.channel.ChannelFuture class CanceledChannelFutureException( val channelFuture : ChannelFuture ) extends IllegalStateException ( "This channel future was canceled -> %s".format(channelFuture) ) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index 96f6ced9..c0d0e18d 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -21,7 +21,6 @@ import com.github.mauricio.async.db.column.ColumnDecoderRegistry import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{RowData, ResultSet} import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer object MutableResultSet { val log = Log.get[MutableResultSet[ColumnData]] diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala index 3778c5b9..4bab73ba 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.util -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object BitMap { final val Bytes = Array(128, 64, 32, 16, 8, 4, 2, 1) @@ -30,7 +30,7 @@ object BitMap { new BitMap( new Array[Byte](finalSize) ) } - def fromBuffer( totalBits : Int, buffer : ChannelBuffer ) : BitMap = { + def fromBuffer( totalBits : Int, buffer : ByteBuf ) : BitMap = { val quotient = totalBits / 8 val remainder = totalBits % 8 @@ -112,7 +112,7 @@ class BitMap(bytes: Array[Byte]) extends IndexedSeq[(Int, Boolean)] { override def toString: String = this.map(entry => if (entry._2) '1' else '0').mkString("") - def write( buffer : ChannelBuffer ) { + def write( buffer : ByteBuf ) { buffer.writeBytes(bytes) } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala index a4d73c62..32280db2 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelFutureTransformer.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.util -import org.jboss.netty.channel.{ChannelFutureListener, ChannelFuture} +import io.netty.channel.{ChannelFutureListener, ChannelFuture} import scala.concurrent.{Promise, Future} import com.github.mauricio.async.db.exceptions.CanceledChannelFutureException import scala.language.implicitConversions @@ -31,11 +31,11 @@ object ChannelFutureTransformer { if ( future.isSuccess ) { promise.success(future) } else { - val exception = if ( future.getCause == null ) { + val exception = if ( future.cause == null ) { new CanceledChannelFutureException(future) .fillInStackTrace() } else { - future.getCause + future.cause } promise.failure(exception) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala index adf7662b..1bac1399 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -17,12 +17,12 @@ package com.github.mauricio.async.db.util import java.nio.charset.Charset -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import java.nio.ByteOrder +import io.netty.buffer.{Unpooled, ByteBuf} object ChannelUtils { - def writeLength(buffer: ChannelBuffer) { + def writeLength(buffer: ByteBuf) { val length = buffer.writerIndex() - 1 buffer.markWriterIndex() @@ -33,18 +33,18 @@ object ChannelUtils { } - def writeCString(content: String, b: ChannelBuffer, charset: Charset): Unit = { + def writeCString(content: String, b: ByteBuf, charset: Charset): Unit = { b.writeBytes(content.getBytes(charset)) b.writeByte(0) } - def writeSizedString( content : String, b : ChannelBuffer, charset : Charset ) { + def writeSizedString( content : String, b : ByteBuf, charset : Charset ) { val bytes = content.getBytes(charset) b.writeByte(bytes.length) b.writeBytes(bytes) } - def readCString(b: ChannelBuffer, charset: Charset): String = { + def readCString(b: ByteBuf, charset: Charset): String = { b.markReaderIndex() var byte: Byte = 0 @@ -64,7 +64,7 @@ object ChannelUtils { return result } - def readUntilEOF( b : ChannelBuffer, charset : Charset ) : String = { + def readUntilEOF( b : ByteBuf, charset : Charset ) : String = { if ( b.readableBytes() == 0 ) { return "" } @@ -94,17 +94,17 @@ object ChannelUtils { return result } - def read3BytesInt( b : ChannelBuffer ) : Int = { + def read3BytesInt( b : ByteBuf ) : Int = { (b.readByte() & 0xff) | ((b.readByte() & 0xff) << 8) | ((b.readByte() & 0xff) << 16) } - def write3BytesInt( b : ChannelBuffer, value : Int ) { + def write3BytesInt( b : ByteBuf, value : Int ) { b.writeByte( value & 0xff ) b.writeByte( value >>> 8 ) b.writeByte( value >>> 16 ) } - def writePacketLength(buffer: ChannelBuffer, sequence : Int = 1) { + def writePacketLength(buffer: ByteBuf, sequence : Int = 1) { val length = buffer.writerIndex() - 4 buffer.markWriterIndex() buffer.writerIndex(0) @@ -115,7 +115,7 @@ object ChannelUtils { buffer.resetWriterIndex() } - def packetBuffer( estimate : Int = 1024 ) : ChannelBuffer = { + def packetBuffer( estimate : Int = 1024 ) : ByteBuf = { val buffer = mysqlBuffer(estimate) buffer.writeInt(0) @@ -123,8 +123,8 @@ object ChannelUtils { buffer } - def mysqlBuffer( estimate : Int = 1024 ) : ChannelBuffer = { - ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, estimate) + def mysqlBuffer( estimate : Int = 1024 ) : ByteBuf = { + Unpooled.buffer(estimate).order(ByteOrder.LITTLE_ENDIAN) } } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index 98eeed4e..bbe38ed1 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -18,18 +18,18 @@ package com.github.mauricio.async.db.util import com.github.mauricio.async.db.exceptions.UnknownLengthException import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer import scala.language.implicitConversions +import io.netty.buffer.ByteBuf object ChannelWrapper { - implicit def bufferToWrapper( buffer : ChannelBuffer ) = new ChannelWrapper(buffer) + implicit def bufferToWrapper( buffer : ByteBuf ) = new ChannelWrapper(buffer) final val MySQL_NULL = 0xfb final val log = Log.get[ChannelWrapper] } -class ChannelWrapper( val buffer : ChannelBuffer ) extends AnyVal { +class ChannelWrapper( val buffer : ByteBuf ) extends AnyVal { import ChannelWrapper._ 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 03d508aa..2ecd6944 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 @@ -1,6 +1,6 @@ package com.github.mauricio.async.db.util -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory +import io.netty.channel.nio.NioEventLoopGroup /* * Copyright 2013 Maurício Linhares @@ -19,8 +19,6 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory object NettyUtils { - val DetaultSocketChannelFactory = new NioClientSocketChannelFactory( - ExecutorServiceUtils.CachedThreadPool, - ExecutorServiceUtils.CachedThreadPool) + val DetaultEventLoopGroup = new NioEventLoopGroup() } \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala index 9bd33703..db0ea002 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/PrintUtils.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.util -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object PrintUtils { private val log = Log.getByName(this.getClass.getName) - def printArray( name : String, buffer : ChannelBuffer ) { + def printArray( name : String, buffer : ByteBuf ) { buffer.markReaderIndex() val bytes = new Array[Byte](buffer.readableBytes()) buffer.readBytes(bytes) diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala index 9b839e3e..11998ba2 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala @@ -1,8 +1,8 @@ package com.github.mauricio.async.db.util import org.specs2.mutable.Specification -import org.jboss.netty.util.CharsetUtil -import org.jboss.netty.buffer.ChannelBuffers +import io.netty.util.CharsetUtil +import io.netty.buffer.Unpooled /* * Copyright 2013 Maurício Linhares @@ -28,7 +28,7 @@ class ChannelUtilsSpec extends Specification { "correctly write and read a string" in { val content = "some text" - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() ChannelUtils.writeCString(content, buffer, charset) @@ -38,7 +38,7 @@ class ChannelUtilsSpec extends Specification { "correctly read the buggy MySQL EOF string when there is an EOF" in { val content = "some text" - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() ChannelUtils.writeCString(content, buffer, charset) @@ -49,7 +49,7 @@ class ChannelUtilsSpec extends Specification { "correctly read the buggy MySQL EOF string when there is no EOF" in { val content = "some text" - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeBytes(content.getBytes(charset)) diff --git a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java index b53f0d80..36eeaac6 100644 --- a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java +++ b/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java @@ -1,10 +1,10 @@ package com.github.mauricio.async.db.mysql; -import org.jboss.netty.buffer.ChannelBuffer; +import io.netty.buffer.ByteBuf; public class MySQLHelper { - public static final String dumpAsHex(ChannelBuffer buffer) { + public static final String dumpAsHex(ByteBuf buffer) { int length = buffer.readableBytes(); byte[] byteBuffer = new byte[length]; diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 68ffb1a9..42ff05b2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -26,12 +26,12 @@ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import java.util.concurrent.atomic.AtomicLong -import org.jboss.netty.channel._ -import org.jboss.netty.channel.socket.ClientSocketChannelFactory import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure import scala.util.Success +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.ChannelHandlerContext object MySQLConnection { final val log = Log.get[MySQLConnection] @@ -42,7 +42,7 @@ object MySQLConnection { class MySQLConnection( configuration: Configuration, charsetMapper: CharsetMapper = CharsetMapper.Instance, - socketFactory : ClientSocketChannelFactory = NettyUtils.DetaultSocketChannelFactory, + group : NioEventLoopGroup = NettyUtils.DetaultEventLoopGroup, executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends MySQLHandlerDelegate @@ -57,7 +57,7 @@ class MySQLConnection( private final val connectionCount = MySQLConnection.Counter.incrementAndGet() private implicit val internalPool = executionContext - private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, socketFactory, executionContext) + private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, group, executionContext) private final val connectionPromise = Promise[Connection]() private final val disconnectionPromise = Promise[Connection]() @@ -99,7 +99,7 @@ class MySQLConnection( } override def connected(ctx: ChannelHandlerContext) { - log.debug("Connected to {}", ctx.getChannel.getRemoteAddress) + log.debug("Connected to {}", ctx.channel.remoteAddress) this.connected = true } @@ -230,4 +230,4 @@ class MySQLConnection( } } -} \ No newline at end of file +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index 303f9720..4e7757f6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.binary +import _root_.io.netty.buffer.ByteBuf import com.github.mauricio.async.db.exceptions.BufferNotFullyConsumedException import com.github.mauricio.async.db.mysql.binary.decoder._ import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage import com.github.mauricio.async.db.util._ import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer import scala.collection.mutable.ArrayBuffer import com.github.mauricio.async.db.mysql.MySQLHelper import scala.annotation.switch @@ -37,7 +37,7 @@ class BinaryRowDecoder { //import BinaryRowDecoder._ - def decode(buffer: ChannelBuffer, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { + def decode(buffer: ByteBuf, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { //log.debug("columns are {} - {}", buffer.readableBytes(), columns) //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer)) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 78848f80..75d7e67d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.mysql.binary +import io.netty.buffer.{Unpooled, ByteBuf} import java.nio.charset.Charset import com.github.mauricio.async.db.mysql.binary.encoder._ -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.util._ import org.joda.time._ import scala.Some @@ -65,7 +65,7 @@ class BinaryRowEncoder( charset : Charset ) { classOf[java.lang.Boolean] -> BooleanEncoder ) - def encode( values : Seq[Any] ) : ChannelBuffer = { + def encode( values : Seq[Any] ) : ByteBuf = { val nullBitsCount = (values.size + 7) / 8 val nullBits = new Array[Byte](nullBitsCount) @@ -97,10 +97,10 @@ class BinaryRowEncoder( charset : Charset ) { bitMapBuffer.writeByte(0) } - ChannelBuffers.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) + Unpooled.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) } - private def encode(parameterTypesBuffer: ChannelBuffer, parameterValuesBuffer: ChannelBuffer, value: Any): Unit = { + private def encode(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any): Unit = { val encoder = encoderFor(value) parameterTypesBuffer.writeShort(encoder.encodesTo) encoder.encode(value, parameterValuesBuffer) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala index 5418c551..812612e5 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BigDecimalDecoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import java.nio.charset.Charset +import io.netty.buffer.ByteBuf class BigDecimalDecoder( charset : Charset ) extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = { + def decode(buffer: ByteBuf): Any = { BigDecimal( buffer.readLengthEncodedString(charset) ) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala index 605fc6d4..3883c199 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/BinaryDecoder.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf trait BinaryDecoder { - def decode( buffer : ChannelBuffer ) : Any + def decode( buffer : ByteBuf ) : Any } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala index 45ec3066..7aa9a4d3 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteArrayDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper object ByteArrayDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = { + def decode(buffer: ByteBuf): Any = { val length = buffer.readBinaryLength val bytes = new Array[Byte](length.toInt) buffer.readBytes(bytes) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala index a8ed0ba4..50940cf6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ByteDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object ByteDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = buffer.readByte() + def decode(buffer: ByteBuf): Any = buffer.readByte() } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala index e77c8dfb..681bfbe0 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala @@ -16,9 +16,9 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.LocalDate object DateDecoder extends BinaryDecoder { - override def decode(buffer: ChannelBuffer): LocalDate = TimestampDecoder.decode(buffer).toLocalDate + override def decode(buffer: ByteBuf): LocalDate = TimestampDecoder.decode(buffer).toLocalDate } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala index 7fe3406f..e5fccb91 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DoubleDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object DoubleDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = buffer.readDouble() + def decode(buffer: ByteBuf): Any = buffer.readDouble() } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala index d62fa26f..25d00296 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/FloatDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object FloatDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = buffer.readFloat() + def decode(buffer: ByteBuf): Any = buffer.readFloat() } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala index 71e502e9..3a515a03 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/IntegerDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object IntegerDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = buffer.readInt() + def decode(buffer: ByteBuf): Any = buffer.readInt() } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala index 22de9a7b..b5800252 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/LongDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object LongDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = buffer.readLong() + def decode(buffer: ByteBuf): Any = buffer.readLong() } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala index 403f0be1..3b2181e7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/NullDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object NullDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = null + def decode(buffer: ByteBuf): Any = null } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala index 8a4c7a49..f5a04b74 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/ShortDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object ShortDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = buffer.readShort() + def decode(buffer: ByteBuf): Any = buffer.readShort() } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala index 82e695c2..56c05a15 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/StringDecoder.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.mysql.binary.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer object StringDecoder { final val log = Log.get[StringDecoder] @@ -27,7 +27,7 @@ object StringDecoder { class StringDecoder( charset : Charset ) extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Any = { + def decode(buffer: ByteBuf): Any = { buffer.readLengthEncodedString(charset) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala index 73ba6339..8b7f1ac6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimeDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import scala.concurrent.duration._ object TimeDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): Duration = { + def decode(buffer: ByteBuf): Duration = { buffer.readUnsignedByte() match { case 0 => 0.seconds diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala index ae676986..b6fc5a00 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.LocalDateTime object TimestampDecoder extends BinaryDecoder { - def decode(buffer: ChannelBuffer): LocalDateTime = { + def decode(buffer: ByteBuf): LocalDateTime = { val size = buffer.readUnsignedByte() size match { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala index 607e59ac..bb504ce6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf trait BinaryEncoder { - def encode( value : Any, buffer : ChannelBuffer ) + def encode( value : Any, buffer : ByteBuf ) def encodesTo : Int diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala index fc6c8901..56700009 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BooleanEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes object BooleanEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val boolean = value.asInstanceOf[Boolean] if ( boolean ) { buffer.writeByte(1) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala index e98516bc..260f22a4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala @@ -17,12 +17,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.mysql.column.ColumnTypes object ByteArrayEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[Array[Byte]] buffer.writeLength(bytes.length) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala index 87e87029..33472753 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteEncoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.util.Log object ByteEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { buffer.writeByte(value.asInstanceOf[Byte]) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala index 914fd044..488af40a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/CalendarEncoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import java.util.Calendar import org.joda.time.{LocalDateTime, DateTime} import com.github.mauricio.async.db.mysql.column.ColumnTypes object CalendarEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val calendar = value.asInstanceOf[Calendar] LocalDateTimeEncoder.encode(new LocalDateTime(calendar.getTimeInMillis), buffer) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala index ec4c9142..9e7f4956 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DateTimeEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes -import org.jboss.netty.buffer.ChannelBuffer import org.joda.time._ object DateTimeEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val instant = value.asInstanceOf[ReadableDateTime] LocalDateTimeEncoder.encode(new LocalDateTime(instant.getMillis), buffer) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala index 7c9b2899..9e792c8d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DoubleEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes object DoubleEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { buffer.writeDouble(value.asInstanceOf[Double]) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala index cd7a4b4e..f3231e17 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DurationEncoder.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import scala.concurrent.duration._ import com.github.mauricio.async.db.mysql.column.ColumnTypes @@ -24,7 +24,7 @@ object DurationEncoder extends BinaryEncoder { private final val Zero = 0.seconds - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val duration = value.asInstanceOf[Duration] val days = duration.toDays diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala index c65591b6..f664ac35 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/FloatEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes object FloatEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { buffer.writeFloat(value.asInstanceOf[Float]) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala index c7237223..03c35fda 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/IntegerEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes object IntegerEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { buffer.writeInt(value.asInstanceOf[Int]) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala index 97518ee3..65710db5 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/JavaDateEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.{LocalDateTime, DateTime} import com.github.mauricio.async.db.mysql.column.ColumnTypes object JavaDateEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val date = value.asInstanceOf[java.util.Date] LocalDateTimeEncoder.encode(new LocalDateTime(date.getTime), buffer) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala index a16cf463..09bbec4f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateEncoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.LocalDate import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException import com.github.mauricio.async.db.mysql.column.ColumnTypes object LocalDateEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val date = value.asInstanceOf[LocalDate] buffer.writeByte(4) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala index 9e58d869..48b1f6c6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalDateTimeEncoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes -import org.jboss.netty.buffer.ChannelBuffer import org.joda.time._ object LocalDateTimeEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val instant = value.asInstanceOf[LocalDateTime] val hasMillis = instant.getMillisOfSecond != 0 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala index 5e48535a..ccbf9dac 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LocalTimeEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.LocalTime import com.github.mauricio.async.db.mysql.column.ColumnTypes object LocalTimeEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val time = value.asInstanceOf[LocalTime] val hasMillis = time.getMillisOfSecond != 0 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala index d0182b45..91c86e92 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/LongEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes object LongEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { buffer.writeLong(value.asInstanceOf[Long]) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala index 03e1f270..e5781d3a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ReadableInstantEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes -import org.jboss.netty.buffer.ChannelBuffer import org.joda.time._ object ReadableInstantEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val date = value.asInstanceOf[ReadableInstant] LocalDateTimeEncoder.encode(new LocalDateTime(date.getMillis), buffer) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala index 68d6173a..6af7d7ea 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLDateEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.LocalDate import com.github.mauricio.async.db.mysql.column.ColumnTypes object SQLDateEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val date = value.asInstanceOf[java.sql.Date] LocalDateEncoder.encode(new LocalDate(date), buffer) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala index cc53820e..199c0d1b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimeEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.LocalTime import com.github.mauricio.async.db.mysql.column.ColumnTypes object SQLTimeEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val sqlTime = value.asInstanceOf[java.sql.Time].getTime val time = new LocalTime( sqlTime ) LocalTimeEncoder.encode(time, buffer) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala index 87afc738..f76b2ebc 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/SQLTimestampEncoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import org.joda.time.{LocalDateTime, DateTime} import com.github.mauricio.async.db.mysql.column.ColumnTypes object SQLTimestampEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { val date = value.asInstanceOf[java.sql.Timestamp] LocalDateTimeEncoder.encode(new LocalDateTime(date.getTime), buffer) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala index 9746c874..86cbbc66 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ShortEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes object ShortEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { buffer.writeShort(value.asInstanceOf[Short]) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala index 0afdd386..d7ac4614 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/StringEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer object StringEncoder { final val log = Log.get[StringEncoder] @@ -28,7 +28,7 @@ object StringEncoder { class StringEncoder( charset : Charset ) extends BinaryEncoder { - def encode(value: Any, buffer: ChannelBuffer) { + def encode(value: Any, buffer: ByteBuf) { buffer.writeLenghtEncodedString(value.toString, charset) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala new file mode 100644 index 00000000..fafc58ab --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala @@ -0,0 +1,69 @@ +/* + * Copyright 2013 Norman Maurer + * + * Norman Maurer, 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.codec + +import io.netty.buffer.{CompositeByteBuf, UnpooledByteBufAllocator, ByteBuf, ByteBufAllocator} +import java.nio.ByteOrder + +object LittleEndianByteBufAllocator { + val INSTANCE = new LittleEndianByteBufAllocator +} + +/** + * Allocates ByteBuf which have LITTLE_ENDIAN order. + */ +class LittleEndianByteBufAllocator extends ByteBufAllocator { + private val allocator = new UnpooledByteBufAllocator(false) + + + def buffer() = littleEndian(allocator.buffer()) + + def buffer(initialCapacity: Int) = littleEndian(allocator.buffer(initialCapacity)) + + def buffer(initialCapacity: Int, maxCapacity: Int) = littleEndian(allocator.buffer(initialCapacity, maxCapacity)) + + def ioBuffer() = littleEndian(allocator.ioBuffer()) + + def ioBuffer(initialCapacity: Int) = littleEndian(allocator.ioBuffer(initialCapacity)) + + def ioBuffer(initialCapacity: Int, maxCapacity: Int) = littleEndian(allocator.ioBuffer(initialCapacity, maxCapacity)) + + def heapBuffer() = littleEndian(allocator.heapBuffer()) + + def heapBuffer(initialCapacity: Int) = littleEndian(allocator.heapBuffer(initialCapacity)) + + def heapBuffer(initialCapacity: Int, maxCapacity: Int) = littleEndian(allocator.heapBuffer(initialCapacity, maxCapacity)) + + def directBuffer() = littleEndian(allocator.directBuffer()) + + def directBuffer(initialCapacity: Int) = littleEndian(allocator.directBuffer(initialCapacity)) + + def directBuffer(initialCapacity: Int, maxCapacity: Int): ByteBuf = littleEndian(allocator.directBuffer(initialCapacity, maxCapacity)) + + def compositeBuffer() = allocator.compositeBuffer() + + def compositeBuffer(maxNumComponents: Int) = allocator.compositeBuffer(maxNumComponents) + + def compositeHeapBuffer() = allocator.compositeHeapBuffer() + + def compositeHeapBuffer(maxNumComponents: Int) = allocator.compositeHeapBuffer(maxNumComponents) + + def compositeDirectBuffer() = allocator.compositeDirectBuffer() + + def compositeDirectBuffer(maxNumComponents: Int): CompositeByteBuf = allocator.compositeDirectBuffer(maxNumComponents) + + private def littleEndian(b: ByteBuf) = b.order(ByteOrder.LITTLE_ENDIAN) +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index d499fe49..4d16ce1a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -26,14 +26,28 @@ import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import java.net.InetSocketAddress import java.nio.ByteOrder -import org.jboss.netty.bootstrap.ClientBootstrap -import org.jboss.netty.buffer.HeapChannelBufferFactory -import org.jboss.netty.channel._ -import org.jboss.netty.channel.socket.ClientSocketChannelFactory import scala.Some import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent._ +import io.netty.channel._ +import io.netty.bootstrap.Bootstrap +import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage +import com.github.mauricio.async.db.mysql.message.client.HandshakeResponseMessage +import com.github.mauricio.async.db.mysql.message.server.ErrorMessage +import com.github.mauricio.async.db.mysql.message.server.PreparedStatementPrepareResponse +import com.github.mauricio.async.db.mysql.message.client.QueryMessage +import scala.Some +import com.github.mauricio.async.db.mysql.message.server.OkMessage +import com.github.mauricio.async.db.mysql.message.client.PreparedStatementMessage +import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage +import com.github.mauricio.async.db.mysql.message.client.PreparedStatementExecuteMessage +import com.github.mauricio.async.db.mysql.message.server.BinaryRowMessage +import com.github.mauricio.async.db.mysql.message.server.EOFMessage +import com.github.mauricio.async.db.mysql.message.client.PreparedStatementPrepareMessage +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.buffer.ByteBufAllocator +import io.netty.handler.codec.CodecException object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -43,15 +57,14 @@ class MySQLConnectionHandler( configuration: Configuration, charsetMapper: CharsetMapper, handlerDelegate: MySQLHandlerDelegate, - socketFactory : ClientSocketChannelFactory, + group : EventLoopGroup, executionContext : ExecutionContext ) - extends SimpleChannelHandler - with LifeCycleAwareChannelHandler { + extends SimpleChannelInboundHandler[Object] { private implicit val internalPool = executionContext - private final val bootstrap = new ClientBootstrap(this.socketFactory) + private final val bootstrap = new Bootstrap().group(this.group) private final val connectionPromise = Promise[MySQLConnectionHandler] private final val decoder = new MySQLFrameDecoder(configuration.charset) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) @@ -66,11 +79,11 @@ class MySQLConnectionHandler( private var currentContext: ChannelHandlerContext = null def connect: Future[MySQLConnectionHandler] = { + this.bootstrap.channel(classOf[NioSocketChannel]) + this.bootstrap.handler(new ChannelInitializer[io.netty.channel.Channel]() { - this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - override def getPipeline(): ChannelPipeline = { - Channels.pipeline( + override def initChannel(channel: io.netty.channel.Channel): Unit = { + channel.pipeline.addLast( decoder, encoder, MySQLConnectionHandler.this) @@ -78,10 +91,8 @@ class MySQLConnectionHandler( }) - this.bootstrap.setOption("child.tcpNoDelay", true) - this.bootstrap.setOption("child.keepAlive", true) - - this.bootstrap.setOption("bufferFactory", HeapChannelBufferFactory.getInstance(ByteOrder.LITTLE_ENDIAN)); + this.bootstrap.option[java.lang.Boolean](ChannelOption.SO_KEEPALIVE, true) + this.bootstrap.option[ByteBufAllocator](ChannelOption.ALLOCATOR, LittleEndianByteBufAllocator.INSTANCE) this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { case exception => this.connectionPromise.tryFailure(exception) @@ -90,11 +101,11 @@ class MySQLConnectionHandler( this.connectionPromise.future } - override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent) { + override def channelRead0(ctx: ChannelHandlerContext, message: Object) { - //log.debug("Message received {}", e.getMessage) + //log.debug("Message received {}", message) - e.getMessage match { + message match { case m: ServerMessage => { (m.kind: @switch) match { case ServerMessage.ServerProtocolVersion => { @@ -167,30 +178,33 @@ class MySQLConnectionHandler( } - override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { + override def channelActive(ctx: ChannelHandlerContext): Unit = { handlerDelegate.connected(ctx) } - override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { - if (!this.connectionPromise.isCompleted) { - this.connectionPromise.failure(e.getCause) + override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + // unwrap CodecException if needed + cause match { + case t: CodecException => handleException(t.getCause) + case _ => handleException(cause) } - handlerDelegate.exceptionCaught(e.getCause) - } - def beforeAdd(ctx: ChannelHandlerContext) {} + } - def beforeRemove(ctx: ChannelHandlerContext) {} + private def handleException(cause: Throwable) { + if (!this.connectionPromise.isCompleted) { + this.connectionPromise.failure(cause) + } + handlerDelegate.exceptionCaught(cause) + } - def afterAdd(ctx: ChannelHandlerContext) { + override def handlerAdded(ctx: ChannelHandlerContext) { this.currentContext = ctx } - def afterRemove(ctx: ChannelHandlerContext) {} - def write( message : QueryMessage ) : ChannelFuture = { this.decoder.queryProcessStarted() - this.currentContext.getChannel.write(message) + this.currentContext.writeAndFlush(message) } def write( message : PreparedStatementMessage ) { @@ -206,20 +220,20 @@ class MySQLConnectionHandler( } case None => { decoder.preparedStatementPrepareStarted() - this.currentContext.getChannel.write( new PreparedStatementPrepareMessage(message.statement) ) + this.currentContext.writeAndFlush( new PreparedStatementPrepareMessage(message.statement) ) } } } def write( message : HandshakeResponseMessage ) : ChannelFuture = { - this.currentContext.getChannel.write(message) + this.currentContext.writeAndFlush(message) } def write( message : QuitMessage ) : ChannelFuture = { - this.currentContext.getChannel.write(message) + this.currentContext.writeAndFlush(message) } - def disconnect: ChannelFuture = this.currentContext.getChannel.close() + def disconnect: ChannelFuture = this.currentContext.close() private def clearQueryState { this.currentColumns.clear() @@ -229,7 +243,7 @@ class MySQLConnectionHandler( def isConnected : Boolean = { if ( this.currentContext != null ) { - this.currentContext.getChannel.isConnected + this.currentContext.channel.isActive } else { false } @@ -239,7 +253,7 @@ class MySQLConnectionHandler( decoder.preparedStatementExecuteStarted(columnsCount, parameters.size) this.currentColumns.clear() this.currentParameters.clear() - this.currentContext.getChannel.write(new PreparedStatementExecuteMessage( statementId, values, parameters )) + this.currentContext.writeAndFlush(new PreparedStatementExecuteMessage( statementId, values, parameters )) } private def onPreparedStatementPrepareResponse( message : PreparedStatementPrepareResponse ) { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 253f42a0..c0204b21 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -23,15 +23,18 @@ import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer -import org.jboss.netty.channel.{Channel, ChannelHandlerContext} -import org.jboss.netty.handler.codec.frame.FrameDecoder + +import com.github.mauricio.async.db.mysql.MySQLHelper +import io.netty.handler.codec.ByteToMessageDecoder +import io.netty.channel.ChannelHandlerContext +import io.netty.buffer.ByteBuf +import java.nio.ByteOrder object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] } -class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { +class MySQLFrameDecoder(charset: Charset) extends ByteToMessageDecoder { private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) @@ -54,7 +57,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { private var hasReadColumnsCount = false - def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): AnyRef = { + def decode(ctx: ChannelHandlerContext, buffer: ByteBuf, out: java.util.List[Object]): Unit = { if (buffer.readableBytes() > 4) { buffer.markReaderIndex() @@ -71,8 +74,8 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { throw new NegativeMessageSizeException(messageType, size) } - val slice = buffer.readSlice(size) - + // TODO: Remove once https://github.com/netty/netty/issues/1704 is fixed + val slice = buffer.readSlice(size).order(ByteOrder.LITTLE_ENDIAN) //val dump = MySQLHelper.dumpAsHex(slice) //log.debug(s"Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) @@ -134,8 +137,9 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { if (slice.readableBytes() != 0) { throw new BufferNotFullyConsumedException(slice) } - - return result + if (result != null) { + out.add(result) + } } else { val result = decoder.decode(slice) @@ -160,20 +164,18 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { if (slice.readableBytes() != 0) { throw new BufferNotFullyConsumedException(slice) } - - return result + if (result != null) { + out.add(result) + } } - } else { buffer.resetReaderIndex() } } - - return null } - private def decodeQueryResult(slice: ChannelBuffer): AnyRef = { + private def decodeQueryResult(slice: ByteBuf): AnyRef = { if (!hasReadColumnsCount) { this.hasReadColumnsCount = true this.totalColumns = slice.readBinaryLength @@ -188,7 +190,7 @@ class MySQLFrameDecoder(charset: Charset) extends FrameDecoder { if (this.totalColumns == this.processedColumns) { if (this.isPreparedStatementExecute) { - val row = slice.readSlice(slice.readableBytes()) + val row = slice.readBytes(slice.readableBytes()) row.readByte() // reads initial 00 at message new BinaryRowMessage(row) } else { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala index 6cab9419..6663dc96 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.ResultSet import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} -import org.jboss.netty.channel.ChannelHandlerContext +import io.netty.channel.ChannelHandlerContext trait MySQLHandlerDelegate { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 4133bd05..f89d9e7d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -23,16 +23,15 @@ import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.{ChannelUtils, Log} import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer -import org.jboss.netty.channel.{Channel, ChannelHandlerContext} -import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import scala.annotation.switch +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.MessageToMessageEncoder object MySQLOneToOneEncoder { val log = Log.get[MySQLOneToOneEncoder] } -class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends OneToOneEncoder { +class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends MessageToMessageEncoder[Any] { private final val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private final val queryEncoder = new QueryMessageEncoder(charset) @@ -42,7 +41,7 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten private var sequence = 1 - def encode(ctx: ChannelHandlerContext, channel: Channel, msg: Any): ChannelBuffer = { + def encode(ctx: ChannelHandlerContext, msg: Any, out: java.util.List[Object]): Unit = { msg match { case message: ClientMessage => { @@ -73,7 +72,7 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten sequence += 1 - result + out.add(result) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala index fb7bc3dc..dcd66d45 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.column +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.column.ColumnDecoder -import org.jboss.netty.buffer.ChannelBuffer import java.nio.charset.Charset object ByteArrayColumnDecoder extends ColumnDecoder { - override def decode(value: ChannelBuffer, charset: Charset): Any = { + override def decode(value: ByteBuf, charset: Charset): Any = { val bytes = new Array[Byte](value.readableBytes()) value.readBytes(bytes) bytes diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala index e62505de..70fc3f01 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnDefinitionDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.codec.DecoderRegistry object ColumnDefinitionDecoder { @@ -29,7 +29,7 @@ object ColumnDefinitionDecoder { class ColumnDefinitionDecoder(charset: Charset, registry : DecoderRegistry) extends MessageDecoder { - override def decode(buffer: ChannelBuffer): ColumnDefinitionMessage = { + override def decode(buffer: ByteBuf): ColumnDefinitionMessage = { val catalog = buffer.readLengthEncodedString(charset) val schema = buffer.readLengthEncodedString(charset) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala index 21b4174a..1dfa615f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ColumnProcessingFinishedDecoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{ColumnProcessingFinishedMessage, ServerMessage} -import org.jboss.netty.buffer.ChannelBuffer object ColumnProcessingFinishedDecoder extends MessageDecoder { - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { new ColumnProcessingFinishedMessage( EOFMessageDecoder.decode(buffer) ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala index 2c4f016c..318d71c1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/EOFMessageDecoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.EOFMessage -import org.jboss.netty.buffer.ChannelBuffer object EOFMessageDecoder extends MessageDecoder { - def decode(buffer: ChannelBuffer): EOFMessage = { + def decode(buffer: ByteBuf): EOFMessage = { new EOFMessage( buffer.readUnsignedShort(), buffer.readUnsignedShort() ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala index 922a2701..4cc36c27 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ErrorDecoder.scala @@ -16,15 +16,15 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{ErrorMessage, ServerMessage} import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer import scala.language.implicitConversions class ErrorDecoder( charset : Charset ) extends MessageDecoder { - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { new ErrorMessage( buffer.readShort(), diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala index e6a25fd0..6d842900 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{HandshakeMessage, ServerMessage} import com.github.mauricio.async.db.util.{Log, ChannelUtils} import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer object HandshakeV10Decoder { final val log = Log.get[HandshakeV10Decoder] @@ -32,7 +32,7 @@ class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { import HandshakeV10Decoder._ - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { val serverVersion = ChannelUtils.readCString(buffer, charset) val connectionId = buffer.readInt() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala index 136d7e41..246b29ab 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/MessageDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.ServerMessage -import org.jboss.netty.buffer.ChannelBuffer trait MessageDecoder { - def decode( buffer : ChannelBuffer ) : ServerMessage + def decode( buffer : ByteBuf ) : ServerMessage } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala index 37864e8a..f2e56376 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/OkDecoder.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{OkMessage, ServerMessage} import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer class OkDecoder( charset : Charset ) extends MessageDecoder { - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { new OkMessage( buffer.readBinaryLength, diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala index e45be4cd..959ef40c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamAndColumnProcessingFinishedDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{ParamAndColumnProcessingFinishedMessage, ServerMessage} object ParamAndColumnProcessingFinishedDecoder extends MessageDecoder { - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { new ParamAndColumnProcessingFinishedMessage(EOFMessageDecoder.decode(buffer)) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala index 32a08e9e..faaec179 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ParamProcessingFinishedDecoder.scala @@ -16,12 +16,12 @@ package com.github.mauricio.async.db.mysql.decoder -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{ParamProcessingFinishedMessage, ServerMessage} object ParamProcessingFinishedDecoder extends MessageDecoder { - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { new ParamProcessingFinishedMessage(EOFMessageDecoder.decode(buffer)) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala index 73db4f52..c252ed88 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{PreparedStatementPrepareResponse, ServerMessage} -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.MySQLHelper import com.github.mauricio.async.db.util.Log @@ -25,7 +25,7 @@ class PreparedStatementPrepareResponseDecoder extends MessageDecoder { final val log = Log.get[PreparedStatementPrepareResponseDecoder] - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { //val dump = MySQLHelper.dumpAsHex(buffer, buffer.readableBytes()) //log.debug("prepared statement response dump is \n{}", dump) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala index 977d6eec..96288bf3 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala @@ -16,10 +16,11 @@ package com.github.mauricio.async.db.mysql.decoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{ResultSetRowMessage, ServerMessage} import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer +import java.nio.ByteOrder object ResultSetRowDecoder { @@ -31,16 +32,17 @@ class ResultSetRowDecoder( charset : Charset ) extends MessageDecoder { import ResultSetRowDecoder.NULL - def decode(buffer: ChannelBuffer): ServerMessage = { + def decode(buffer: ByteBuf): ServerMessage = { val row = new ResultSetRowMessage() - while ( buffer.readable() ) { + while (buffer.isReadable() ) { if ( buffer.getUnsignedByte(buffer.readerIndex()) == NULL ) { buffer.readByte() row += null } else { val length = buffer.readBinaryLength.asInstanceOf[Int] - row += buffer.readSlice(length) + row += buffer.readBytes(length) + } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala index 2dd4032e..1446b66c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.async.db.mysql.encoder.auth.MySQLNativePasswordAuthentication import com.github.mauricio.async.db.mysql.message.client.{HandshakeResponseMessage, ClientMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.{Log, ChannelUtils} import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer object HandshakeResponseEncoder { @@ -49,7 +49,7 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e private val authenticationMethods = Map("mysql_native_password" -> new MySQLNativePasswordAuthentication(charset)) - def encode(message: ClientMessage): ChannelBuffer = { + def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[HandshakeResponseMessage] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala index cc40a4ed..e1704e4f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/MessageEncoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.client.ClientMessage -import org.jboss.netty.buffer.ChannelBuffer trait MessageEncoder { - def encode( message : ClientMessage ) : ChannelBuffer + def encode( message : ClientMessage ) : ByteBuf } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala index 7c952443..56def795 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.mysql.encoder +import io.netty.buffer.{ByteBuf, Unpooled} import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementExecuteMessage, ClientMessage} import com.github.mauricio.async.db.util.ChannelUtils -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends MessageEncoder { - def encode(message: ClientMessage): ChannelBuffer = { + def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] val buffer = ChannelUtils.packetBuffer() @@ -36,7 +36,7 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M buffer } else { val parametersBuffer = rowEncoder.encode(m.values) - ChannelBuffers.wrappedBuffer(buffer, parametersBuffer) + Unpooled.wrappedBuffer(buffer, parametersBuffer) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala index bd9def71..c1323e61 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.mysql.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementPrepareMessage, ClientMessage} -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset class PreparedStatementPrepareEncoder( charset : Charset ) extends MessageEncoder { - def encode(message: ClientMessage): ChannelBuffer = { + def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementPrepareMessage] val statement = m.statement.getBytes(charset) val buffer = ChannelUtils.packetBuffer( 4 + 1 + statement.size) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala index 93401667..a4297429 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala @@ -16,14 +16,14 @@ package com.github.mauricio.async.db.mysql.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer class QueryMessageEncoder( charset : Charset ) extends MessageEncoder { - def encode(message: ClientMessage): ChannelBuffer = { + def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[QueryMessage] val encodedQuery = m.query.getBytes( charset ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala index 7c090e26..f847c488 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala @@ -16,13 +16,13 @@ package com.github.mauricio.async.db.mysql.encoder +import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.util.ChannelUtils -import org.jboss.netty.buffer.ChannelBuffer object QuitMessageEncoder extends MessageEncoder { - def encode(message: ClientMessage): ChannelBuffer = { + def encode(message: ClientMessage): ByteBuf = { val buffer = ChannelUtils.packetBuffer(5) buffer.writeByte( ClientMessage.Quit ) buffer diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala index ff8a6836..2f40154c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/BinaryRowMessage.scala @@ -16,6 +16,6 @@ package com.github.mauricio.async.db.mysql.message.server -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf -case class BinaryRowMessage ( buffer : ChannelBuffer ) extends ServerMessage( ServerMessage.BinaryRow ) \ No newline at end of file +case class BinaryRowMessage ( buffer : ByteBuf ) extends ServerMessage( ServerMessage.BinaryRow ) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala index b798aad6..da752575 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ResultSetRowMessage.scala @@ -18,24 +18,24 @@ package com.github.mauricio.async.db.mysql.message.server import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf class ResultSetRowMessage extends ServerMessage( ServerMessage.Row ) - with mutable.Buffer[ChannelBuffer] + with mutable.Buffer[ByteBuf] { - private val buffer = new ArrayBuffer[ChannelBuffer]() + private val buffer = new ArrayBuffer[ByteBuf]() def length: Int = buffer.length - def apply(idx: Int): ChannelBuffer = buffer(idx) + def apply(idx: Int): ByteBuf = buffer(idx) - def update(n: Int, newelem: ChannelBuffer) { + def update(n: Int, newelem: ByteBuf) { buffer.update(n, newelem) } - def +=(elem: ChannelBuffer): this.type = { + def +=(elem: ByteBuf): this.type = { this.buffer += elem this } @@ -44,19 +44,19 @@ class ResultSetRowMessage this.buffer.clear() } - def +=:(elem: ChannelBuffer): this.type = { + def +=:(elem: ByteBuf): this.type = { this.buffer.+=:(elem) this } - def insertAll(n: Int, elems: Traversable[ChannelBuffer]) { + def insertAll(n: Int, elems: Traversable[ByteBuf]) { this.buffer.insertAll(n, elems) } - def remove(n: Int): ChannelBuffer = { + def remove(n: Int): ByteBuf = { this.buffer.remove(n) } - override def iterator: Iterator[ChannelBuffer] = this.buffer.iterator + override def iterator: Iterator[ByteBuf] = this.buffer.iterator } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala index 01628f54..4446e06d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/CharsetMapper.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.mysql.util import com.github.mauricio.async.db.mysql.exceptions.CharsetMappingNotAvailableException import java.nio.charset.Charset -import org.jboss.netty.util.CharsetUtil +import io.netty.util.CharsetUtil object CharsetMapper { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 173b0b98..a39fd07e 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -21,7 +21,7 @@ import org.joda.time._ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration import java.util.concurrent.TimeUnit -import org.jboss.netty.util.CharsetUtil +import io.netty.util.CharsetUtil class QuerySpec extends Specification with ConnectionHelper { @@ -134,7 +134,6 @@ class QuerySpec extends Specification with ConnectionHelper { executeQuery(connection, create) executePreparedStatement(connection, insert, bytes) val row = executeQuery(connection, select).rows.get(0) - row("id") === 1 row("some_bytes") === bytes diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala index 070f3152..6486fcd9 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala @@ -16,10 +16,10 @@ package com.github.mauricio.async.db.mysql.binary +import io.netty.buffer.Unpooled +import io.netty.util.CharsetUtil import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage -import org.jboss.netty.buffer.ChannelBuffers -import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification import java.nio.ByteOrder import com.github.mauricio.async.db.mysql.codec.DecoderRegistry @@ -41,7 +41,7 @@ class BinaryRowDecoderSpec extends Specification { "decoder a long and a string from the byte array" in { - val buffer = ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, idAndName) + val buffer = Unpooled.wrappedBuffer(idAndName).order(ByteOrder.LITTLE_ENDIAN) val result = decoder.decode(buffer, idAndNameColumns) result(0) === 1L @@ -50,7 +50,7 @@ class BinaryRowDecoderSpec extends Specification { } "decode a row with an long, a string and a null" in { - val buffer = ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, idNameAndNull) + val buffer = Unpooled.wrappedBuffer(idNameAndNull).order(ByteOrder.LITTLE_ENDIAN) val result = decoder.decode(buffer, idNameAndNullColumns) result(0) === 1L diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala index 0b216a03..78bce249 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.mysql.binary -import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification +import io.netty.util.CharsetUtil class BinaryRowEncoderSpec extends Specification { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index f1d92728..e5d0579d 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -16,15 +16,15 @@ package com.github.mauricio.async.db.mysql.codec +import io.netty.buffer.ByteBuf +import io.netty.util.CharsetUtil import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.util.ChannelUtils import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import org.jboss.netty.handler.codec.embedder.DecoderEmbedder -import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification import com.github.mauricio.async.db.mysql.message.server.OkMessage -import org.jboss.netty.buffer.ChannelBuffer import com.github.mauricio.async.db.mysql.column.ColumnTypes +import io.netty.channel.embedded.EmbeddedChannel class MySQLFrameDecoderSpec extends Specification { @@ -38,9 +38,9 @@ class MySQLFrameDecoderSpec extends Specification { val decoder = this.createPipeline() - decoder.offer(buffer) + decoder.writeInbound(buffer) - val ok = decoder.peek().asInstanceOf[OkMessage] + val ok = decoder.readInbound().asInstanceOf[OkMessage] ok.affectedRows === 10 ok.lastInsertId === 15 ok.message === "this is a test" @@ -56,9 +56,9 @@ class MySQLFrameDecoderSpec extends Specification { val decoder = createPipeline() - decoder.offer(buffer) + decoder.writeInbound(buffer) - val error = decoder.peek().asInstanceOf[ErrorMessage] + val error = decoder.readInbound().asInstanceOf[ErrorMessage] error.errorCode === 27 error.errorMessage === content @@ -69,7 +69,8 @@ class MySQLFrameDecoderSpec extends Specification { "on a query process it should correctly send an OK" in { val decoder = new MySQLFrameDecoder(charset) - val embedder = new DecoderEmbedder[ServerMessage](decoder) + val embedder = new EmbeddedChannel(decoder) + embedder.config.setAllocator(new LittleEndianByteBufAllocator) decoder.queryProcessStarted() @@ -78,8 +79,8 @@ class MySQLFrameDecoderSpec extends Specification { val buffer = createOkPacket() - embedder.offer(buffer) - embedder.peek().asInstanceOf[OkMessage].message === "this is a test" + embedder.writeInbound(buffer) must beTrue + embedder.readInbound().asInstanceOf[OkMessage].message === "this is a test" decoder.isInQuery must beFalse decoder.processingColumns must beFalse @@ -88,7 +89,8 @@ class MySQLFrameDecoderSpec extends Specification { "on query process it should correctly send an error" in { val decoder = new MySQLFrameDecoder(charset) - val embedder = new DecoderEmbedder[ServerMessage](decoder) + val embedder = new EmbeddedChannel(decoder) + embedder.config.setAllocator(new LittleEndianByteBufAllocator) decoder.queryProcessStarted() @@ -99,8 +101,8 @@ class MySQLFrameDecoderSpec extends Specification { val buffer = createErrorPacket(content) - embedder.offer(buffer) - embedder.peek().asInstanceOf[ErrorMessage].errorMessage === content + embedder.writeInbound(buffer) must beTrue + embedder.readInbound().asInstanceOf[ErrorMessage].errorMessage === content decoder.isInQuery must beFalse decoder.processingColumns must beFalse @@ -110,7 +112,8 @@ class MySQLFrameDecoderSpec extends Specification { "on query process it should correctly handle a result set" in { val decoder = new MySQLFrameDecoder(charset) - val embedder = new DecoderEmbedder[ServerMessage](decoder) + val embedder = new EmbeddedChannel(decoder) + embedder.config.setAllocator(new LittleEndianByteBufAllocator) decoder.queryProcessStarted() @@ -120,28 +123,28 @@ class MySQLFrameDecoderSpec extends Specification { columnCountBuffer.writeLength(2) columnCountBuffer.writePacketLength() - embedder.offer(columnCountBuffer) + embedder.writeInbound(columnCountBuffer) decoder.totalColumns === 2 val columnId = createColumnPacket("id", ColumnTypes.FIELD_TYPE_LONG) val columnName = createColumnPacket("name", ColumnTypes.FIELD_TYPE_VARCHAR) - embedder.offer(columnId) + embedder.writeInbound(columnId) - embedder.poll().asInstanceOf[ColumnDefinitionMessage].name === "id" + embedder.readInbound().asInstanceOf[ColumnDefinitionMessage].name === "id" decoder.processedColumns === 1 - embedder.offer(columnName) + embedder.writeInbound(columnName) - embedder.poll().asInstanceOf[ColumnDefinitionMessage].name === "name" + embedder.readInbound().asInstanceOf[ColumnDefinitionMessage].name === "name" decoder.processedColumns === 2 - embedder.offer(this.createEOFPacket()) + embedder.writeInbound(this.createEOFPacket()) - embedder.poll().asInstanceOf[ColumnProcessingFinishedMessage].eofMessage.flags === 8765 + embedder.readInbound().asInstanceOf[ColumnProcessingFinishedMessage].eofMessage.flags === 8765 decoder.processingColumns must beFalse @@ -150,22 +153,24 @@ class MySQLFrameDecoderSpec extends Specification { row.writeLenghtEncodedString("some name", charset) row.writePacketLength() - embedder.offer(row) + embedder.writeInbound(row) - embedder.poll().isInstanceOf[ResultSetRowMessage] must beTrue + embedder.readInbound().isInstanceOf[ResultSetRowMessage] must beTrue - embedder.offer(this.createEOFPacket()) + embedder.writeInbound(this.createEOFPacket()) decoder.isInQuery must beFalse } } - def createPipeline(): DecoderEmbedder[ServerMessage] = { - new DecoderEmbedder[ServerMessage](new MySQLFrameDecoder(charset)) + def createPipeline(): EmbeddedChannel = { + val channel = new EmbeddedChannel(new MySQLFrameDecoder(charset)) + channel.config.setAllocator(new LittleEndianByteBufAllocator) + channel } - def createOkPacket() : ChannelBuffer = { + def createOkPacket() : ByteBuf = { val buffer = ChannelUtils.packetBuffer() buffer.writeByte(0) buffer.writeLength(10) @@ -177,7 +182,7 @@ class MySQLFrameDecoderSpec extends Specification { buffer } - def createErrorPacket(content : String) : ChannelBuffer = { + def createErrorPacket(content : String) : ByteBuf = { val buffer = ChannelUtils.packetBuffer() buffer.writeByte(0xff) buffer.writeShort(27) @@ -188,7 +193,7 @@ class MySQLFrameDecoderSpec extends Specification { buffer } - def createColumnPacket( name : String, columnType : Int ) : ChannelBuffer = { + def createColumnPacket( name : String, columnType : Int ) : ByteBuf = { val buffer = ChannelUtils.packetBuffer() buffer.writeLenghtEncodedString("def", charset) buffer.writeLenghtEncodedString("some_schema", charset) @@ -207,7 +212,7 @@ class MySQLFrameDecoderSpec extends Specification { buffer } - def createEOFPacket() : ChannelBuffer = { + def createEOFPacket() : ByteBuf = { val buffer = ChannelUtils.packetBuffer() buffer.writeByte(0xfe) buffer.writeShort(879) 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 42a1d88e..4c6ba35c 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 @@ -28,10 +28,10 @@ import com.github.mauricio.async.db.{Configuration, Connection} import java.util.concurrent.atomic._ import messages.backend._ import messages.frontend._ -import org.jboss.netty.channel.socket.ClientSocketChannelFactory -import org.jboss.netty.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import scala.Some import scala.concurrent._ +import io.netty.util.internal.logging.{Slf4JLoggerFactory, InternalLoggerFactory} +import io.netty.channel.nio.NioEventLoopGroup object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] @@ -45,7 +45,7 @@ class PostgreSQLConnection configuration: Configuration = Configuration.Default, encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance, - socketFactory : ClientSocketChannelFactory = NettyUtils.DetaultSocketChannelFactory, + group : NioEventLoopGroup = NettyUtils.DetaultEventLoopGroup, executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends PostgreSQLConnectionDelegate @@ -58,7 +58,7 @@ class PostgreSQLConnection encoderRegistry, decoderRegistry, this, - socketFactory, + group, executionContext ) private final val currentCount = Counter.incrementAndGet() @@ -323,4 +323,4 @@ class PostgreSQLConnection override def toString: String = { s"${this.getClass.getSimpleName}{counter=${this.currentCount}}" } -} \ No newline at end of file +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala index 4bc82ff8..68f8abdc 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala @@ -21,21 +21,21 @@ import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParsersRegistry} import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer -import org.jboss.netty.channel.{ChannelHandlerContext, Channel} -import org.jboss.netty.handler.codec.frame.FrameDecoder import com.github.mauricio.async.db.exceptions.NegativeMessageSizeException +import io.netty.handler.codec.ByteToMessageDecoder +import io.netty.channel.ChannelHandlerContext +import io.netty.buffer.ByteBuf object MessageDecoder { val log = Log.get[MessageDecoder] val DefaultMaximumSize = 16777216 } -class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder.DefaultMaximumSize) extends FrameDecoder { +class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder.DefaultMaximumSize) extends ByteToMessageDecoder { private val parser = new MessageParsersRegistry(charset) - override def decode(ctx: ChannelHandlerContext, c: Channel, b: ChannelBuffer): Object = { + override def decode(ctx: ChannelHandlerContext, b: ByteBuf, out: java.util.List[Object]): Unit = { if (b.readableBytes() >= 5) { @@ -56,22 +56,22 @@ class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder if (b.readableBytes() >= length) { code match { case ServerMessage.Authentication => { - AuthenticationStartupParser.parseMessage(b) + val msg = AuthenticationStartupParser.parseMessage(b) + out.add(msg) } case _ => { - parser.parse(code, b.readSlice(length)) + val msg = parser.parse(code, b.readSlice(length)) + out.add(msg) } } } else { b.resetReaderIndex() - return null + return } - } else { - return null } } -} \ No newline at end of file +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala index 2a122f4b..b512b1a8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala @@ -23,16 +23,15 @@ import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend._ import com.github.mauricio.async.db.util.Log import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer -import org.jboss.netty.channel.{Channel, ChannelHandlerContext} -import org.jboss.netty.handler.codec.oneone.OneToOneEncoder import scala.annotation.switch +import io.netty.handler.codec.MessageToMessageEncoder +import io.netty.channel.ChannelHandlerContext object MessageEncoder { val log = Log.get[MessageEncoder] } -class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) extends OneToOneEncoder { +class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) extends MessageToMessageEncoder[Object] { private val executeEncoder = new ExecutePreparedStatementEncoder(charset, encoderRegistry) private val openEncoder = new PreparedStatementOpeningEncoder(charset, encoderRegistry) @@ -40,7 +39,7 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e private val queryEncoder = new QueryMessageEncoder(charset) private val credentialEncoder = new CredentialEncoder(charset) - override def encode(ctx: ChannelHandlerContext, channel: Channel, msg: AnyRef): ChannelBuffer = { + override def encode(ctx: ChannelHandlerContext, msg: AnyRef, out: java.util.List[Object]) = { val buffer = msg match { case message: ClientMessage => { @@ -60,7 +59,7 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e } } - buffer + out.add(buffer) } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index 92d595ab..b5356ebd 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -24,13 +24,22 @@ import com.github.mauricio.async.db.postgresql.messages.frontend._ import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import java.net.InetSocketAddress -import org.jboss.netty.bootstrap.ClientBootstrap -import org.jboss.netty.channel._ -import org.jboss.netty.channel.socket.ClientSocketChannelFactory import scala.annotation.switch import scala.concurrent._ import scala.util.Failure import scala.util.Success +import io.netty.channel._ +import io.netty.bootstrap.Bootstrap +import io.netty.channel +import scala.util.Failure +import com.github.mauricio.async.db.postgresql.messages.backend.DataRowMessage +import com.github.mauricio.async.db.postgresql.messages.backend.CommandCompleteMessage +import com.github.mauricio.async.db.postgresql.messages.backend.ProcessData +import scala.util.Success +import com.github.mauricio.async.db.postgresql.messages.backend.RowDescriptionMessage +import com.github.mauricio.async.db.postgresql.messages.backend.ParameterStatusMessage +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.CodecException object PostgreSQLConnectionHandler { final val log = Log.get[PostgreSQLConnectionHandler] @@ -42,11 +51,10 @@ class PostgreSQLConnectionHandler encoderRegistry: ColumnEncoderRegistry, decoderRegistry: ColumnDecoderRegistry, connectionDelegate : PostgreSQLConnectionDelegate, - socketFactory : ClientSocketChannelFactory, + group : EventLoopGroup, executionContext : ExecutionContext ) - extends SimpleChannelHandler - with LifeCycleAwareChannelHandler + extends SimpleChannelInboundHandler[Object] { import PostgreSQLConnectionHandler.log @@ -60,7 +68,7 @@ class PostgreSQLConnectionHandler "extra_float_digits" -> "2") private implicit final val _executionContext = executionContext - private final val bootstrap = new ClientBootstrap(this.socketFactory) + private final val bootstrap = new Bootstrap() private final val connectionFuture = Promise[PostgreSQLConnectionHandler]() private final val disconnectionPromise = Promise[PostgreSQLConnectionHandler]() private var processData : ProcessData = null @@ -68,11 +76,12 @@ class PostgreSQLConnectionHandler private var currentContext : ChannelHandlerContext = null def connect: Future[PostgreSQLConnectionHandler] = { + this.bootstrap.group(this.group) + this.bootstrap.channel(classOf[NioSocketChannel]) + this.bootstrap.handler(new ChannelInitializer[channel.Channel]() { - this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - override def getPipeline(): ChannelPipeline = { - Channels.pipeline( + override def initChannel(ch: channel.Channel): Unit = { + ch.pipeline.addLast( new MessageDecoder(configuration.charset, configuration.maximumMessageSize), new MessageEncoder(configuration.charset, encoderRegistry), PostgreSQLConnectionHandler.this) @@ -80,8 +89,7 @@ class PostgreSQLConnectionHandler }) - this.bootstrap.setOption("child.tcpNoDelay", true) - this.bootstrap.setOption("child.keepAlive", true) + this.bootstrap.option[java.lang.Boolean](ChannelOption.SO_KEEPALIVE, true) this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { case e => connectionFuture.tryFailure(e) @@ -93,8 +101,8 @@ class PostgreSQLConnectionHandler def disconnect: Future[PostgreSQLConnectionHandler] = { if ( this.isConnected ) { - this.currentContext.getChannel.write(CloseMessage).onComplete { - case Success(writeFuture) => writeFuture.getChannel.close().onComplete { + this.currentContext.channel.writeAndFlush(CloseMessage).onComplete { + case Success(writeFuture) => writeFuture.channel.close().onComplete { case Success(closeFuture) => this.disconnectionPromise.trySuccess(this) case Failure(e) => this.disconnectionPromise.tryFailure(e) } @@ -107,19 +115,19 @@ class PostgreSQLConnectionHandler def isConnected: Boolean = { if (this.currentContext != null) { - this.currentContext.getChannel.isConnected + this.currentContext.channel.isActive } else { false } } - override def channelConnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { - e.getChannel().write(new StartupMessage(this.properties)) + override def channelActive(ctx: ChannelHandlerContext): Unit = { + ctx.writeAndFlush(new StartupMessage(this.properties)) } - override def messageReceived(ctx: ChannelHandlerContext, e: MessageEvent): Unit = { + override def channelRead0(ctx: ChannelHandlerContext, msg: Object): Unit = { - e.getMessage() match { + msg match { case m: ServerMessage => { @@ -174,8 +182,8 @@ class PostgreSQLConnectionHandler } case _ => { - log.error("Unknown message type - {}", e.getMessage) - val exception = new IllegalArgumentException("Unknown message type - %s".format(e.getMessage())) + log.error("Unknown message type - {}", msg) + val exception = new IllegalArgumentException("Unknown message type - %s".format(msg)) exception.fillInStackTrace() connectionDelegate.onError(exception) } @@ -184,26 +192,24 @@ class PostgreSQLConnectionHandler } - override def exceptionCaught(ctx: ChannelHandlerContext, e: ExceptionEvent) { - connectionDelegate.onError(e.getCause) + override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + // unwrap CodecException if needed + cause match { + case t: CodecException => connectionDelegate.onError(t.getCause) + case _ => connectionDelegate.onError(cause) + } } - override def channelDisconnected(ctx: ChannelHandlerContext, e: ChannelStateEvent): Unit = { - log.info("Connection disconnected - {}", ctx.getChannel.getRemoteAddress) + override def channelInactive(ctx: ChannelHandlerContext): Unit = { + log.info("Connection disconnected - {}", ctx.channel.remoteAddress) } - def beforeAdd(ctx: ChannelHandlerContext) { + override def handlerAdded(ctx: ChannelHandlerContext) { this.currentContext = ctx } - def afterAdd(ctx: ChannelHandlerContext) {} - - def beforeRemove(ctx: ChannelHandlerContext) {} - - def afterRemove(ctx: ChannelHandlerContext) {} - def write( message : ClientMessage ) { - this.currentContext.getChannel.write(message) + this.currentContext.writeAndFlush(message) } -} \ No newline at end of file +} 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 df40394b..5bff6e6c 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 @@ -20,8 +20,8 @@ import com.github.mauricio.async.db.column._ import com.github.mauricio.async.db.postgresql.column.ColumnTypes._ import scala.annotation.switch import java.nio.charset.Charset -import org.jboss.netty.util.CharsetUtil -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.util.CharsetUtil +import io.netty.buffer.ByteBuf object PostgreSQLColumnDecoderRegistry { val Instance = new PostgreSQLColumnDecoderRegistry() @@ -44,7 +44,7 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e private final val timeArrayDecoder = new ArrayDecoder(TimeEncoderDecoder.Instance) private final val timeWithTimestampArrayDecoder = new ArrayDecoder(TimeWithTimezoneEncoderDecoder) - override def decode(kind: Int, value: ChannelBuffer, charset: Charset): Any = { + override def decode(kind: Int, value: ByteBuf, charset: Charset): Any = { decoderFor(kind).decode(value, charset) } 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 a4e7cc6c..b5bd7ec1 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 @@ -20,7 +20,6 @@ import com.github.mauricio.async.db.column._ import org.joda.time._ import scala.Some import scala.collection.JavaConversions._ -import org.jboss.netty.util.CharsetUtil import java.nio.charset.Charset object PostgreSQLColumnEncoderRegistry { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala index 87fac1b0..a2534c3d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CloseMessageEncoder.scala @@ -17,12 +17,12 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.frontend.ClientMessage -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import io.netty.buffer.{Unpooled, ByteBuf} object CloseMessageEncoder extends Encoder { - override def encode(message: ClientMessage): ChannelBuffer = { - val buffer = ChannelBuffers.buffer(5) + override def encode(message: ClientMessage): ByteBuf = { + val buffer = Unpooled.buffer(5) buffer.writeByte('X') buffer.writeInt(4) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index a6ee3a11..f1f70279 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -21,11 +21,11 @@ import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMess import com.github.mauricio.async.db.postgresql.util.PasswordHelper import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import io.netty.buffer.{Unpooled, ByteBuf} class CredentialEncoder(charset: Charset) extends Encoder { - def encode(message: ClientMessage): ChannelBuffer = { + def encode(message: ClientMessage): ByteBuf = { val credentialMessage = message.asInstanceOf[CredentialMessage] @@ -42,7 +42,7 @@ class CredentialEncoder(charset: Charset) extends Encoder { } } - val buffer = ChannelBuffers.dynamicBuffer(1 + 4 + password.size + 1) + val buffer = Unpooled.buffer(1 + 4 + password.size + 1) buffer.writeByte(ServerMessage.PasswordMessage) buffer.writeInt(0) buffer.writeBytes(password) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala index 9f948e64..a6b4f714 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/Encoder.scala @@ -17,10 +17,10 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.frontend.ClientMessage -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf trait Encoder { - def encode(message: ClientMessage): ChannelBuffer + def encode(message: ClientMessage): ByteBuf } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 48244807..6b425c81 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementExecuteMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import io.netty.buffer.ByteBuf class ExecutePreparedStatementEncoder( charset: Charset, @@ -30,7 +30,7 @@ class ExecutePreparedStatementEncoder( with PreparedStatementEncoderHelper { - def encode(message: ClientMessage): ChannelBuffer = { + def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala index c88dc552..38ba9057 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.postgresql.encoders -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.util.{Log, ChannelUtils} import com.github.mauricio.async.db.column.ColumnEncoderRegistry import java.nio.charset.Charset +import io.netty.buffer.{Unpooled, ByteBuf} object PreparedStatementEncoderHelper { final val log = Log.get[PreparedStatementEncoderHelper] @@ -36,9 +36,9 @@ trait PreparedStatementEncoderHelper { encoder: ColumnEncoderRegistry, charset: Charset, writeDescribe: Boolean = false - ): ChannelBuffer = { + ): ByteBuf = { - val bindBuffer = ChannelBuffers.dynamicBuffer(1024) + val bindBuffer = Unpooled.buffer(1024) bindBuffer.writeByte(ServerMessage.Bind) bindBuffer.writeInt(0) @@ -77,7 +77,7 @@ trait PreparedStatementEncoderHelper { } val executeLength = 1 + 4 + statementIdBytes.length + 1 + 4 - val executeBuffer = ChannelBuffers.buffer(executeLength) + val executeBuffer = Unpooled.buffer(executeLength) executeBuffer.writeByte(ServerMessage.Execute) executeBuffer.writeInt(executeLength - 1) executeBuffer.writeBytes(statementIdBytes) @@ -85,18 +85,18 @@ trait PreparedStatementEncoderHelper { executeBuffer.writeInt(0) val closeLength = 1 + 4 + 1 + statementIdBytes.length + 1 - val closeBuffer = ChannelBuffers.buffer(closeLength) + val closeBuffer = Unpooled.buffer(closeLength) closeBuffer.writeByte(ServerMessage.CloseStatementOrPortal) closeBuffer.writeInt(closeLength - 1) closeBuffer.writeByte('P') closeBuffer.writeBytes(statementIdBytes) closeBuffer.writeByte(0) - val syncBuffer = ChannelBuffers.buffer(5) + val syncBuffer = Unpooled.buffer(5) syncBuffer.writeByte(ServerMessage.Sync) syncBuffer.writeInt(4) - ChannelBuffers.wrappedBuffer(bindBuffer, executeBuffer, syncBuffer, closeBuffer) + Unpooled.wrappedBuffer(bindBuffer, executeBuffer, syncBuffer, closeBuffer) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 8711b750..d71028da 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -21,21 +21,21 @@ import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementOpeningMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import io.netty.buffer.{Unpooled, ByteBuf} class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder with PreparedStatementEncoderHelper { - override def encode(message: ClientMessage): ChannelBuffer = { + override def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementOpeningMessage] val statementIdBytes = m.statementId.toString.getBytes(charset) val columnCount = m.valueTypes.size - val parseBuffer = ChannelBuffers.dynamicBuffer(1024) + val parseBuffer = Unpooled.buffer(1024) parseBuffer.writeByte(ServerMessage.Parse) parseBuffer.writeInt(0) @@ -55,7 +55,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR val executeBuffer = writeExecutePortal(statementIdBytes, m.values, encoder, charset, true) - ChannelBuffers.wrappedBuffer(parseBuffer, executeBuffer) + Unpooled.wrappedBuffer(parseBuffer, executeBuffer) } } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index 3d3b5db6..c85ae8b9 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -20,15 +20,15 @@ import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, ClientMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import io.netty.buffer.{Unpooled, ByteBuf} class QueryMessageEncoder(charset: Charset) extends Encoder { - override def encode(message: ClientMessage): ChannelBuffer = { + override def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[QueryMessage] - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeByte(ServerMessage.Query) buffer.writeInt(0) ChannelUtils.writeCString(m.query, buffer, charset) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index eeb8be99..4e0f11a0 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -19,17 +19,17 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, StartupMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} +import io.netty.buffer.{Unpooled, ByteBuf} class StartupMessageEncoder(charset: Charset) extends Encoder { //private val log = Log.getByName("StartupMessageEncoder") - override def encode(message: ClientMessage): ChannelBuffer = { + override def encode(message: ClientMessage): ByteBuf = { val startup = message.asInstanceOf[StartupMessage] - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeInt(0) buffer.writeShort(3) buffer.writeShort(0) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala index 3b73f78d..80959083 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/DataRowMessage.scala @@ -16,6 +16,6 @@ package com.github.mauricio.async.db.postgresql.messages.backend -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf -case class DataRowMessage(val values: Array[ChannelBuffer]) extends ServerMessage(ServerMessage.DataRow) \ No newline at end of file +case class DataRowMessage(val values: Array[ByteBuf]) extends ServerMessage(ServerMessage.DataRow) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala index 88dd0e84..edac9f6f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/AuthenticationStartupParser.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.async.db.postgresql.messages.backend.{AuthenticationChallengeMD5, AuthenticationChallengeCleartextMessage, AuthenticationOkMessage, ServerMessage} -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object AuthenticationStartupParser extends MessageParser { @@ -31,7 +31,7 @@ object AuthenticationStartupParser extends MessageParser { val AuthenticationGSSContinue = 8 val AuthenticationSSPI = 9 - override def parseMessage(b: ChannelBuffer): ServerMessage = { + override def parseMessage(b: ByteBuf): ServerMessage = { val authenticationType = b.readInt() diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala index d57366dc..772b69d3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/BackendKeyDataParser.scala @@ -17,11 +17,11 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ProcessData, ServerMessage} -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object BackendKeyDataParser extends MessageParser { - override def parseMessage(b: ChannelBuffer): ServerMessage = { + override def parseMessage(b: ByteBuf): ServerMessage = { new ProcessData(b.readInt(), b.readInt()) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 20ab7509..9d7ec1d2 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -19,11 +19,11 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, ServerMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf class CommandCompleteParser(charset: Charset) extends MessageParser { - override def parseMessage(b: ChannelBuffer): ServerMessage = { + override def parseMessage(b: ByteBuf): ServerMessage = { val result = ChannelUtils.readCString(b, charset) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala index 4d8fa592..3e2b8b1d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/DataRowParser.scala @@ -17,13 +17,13 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{DataRowMessage, ServerMessage} -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object DataRowParser extends MessageParser { - def parseMessage(buffer: ChannelBuffer): ServerMessage = { + def parseMessage(buffer: ByteBuf): ServerMessage = { - val row = new Array[ChannelBuffer](buffer.readShort()) + val row = new Array[ByteBuf](buffer.readShort()) 0.until(row.length).foreach { column => @@ -32,9 +32,7 @@ object DataRowParser extends MessageParser { row(column) = if (length == -1) { null } else { - val slice = buffer.slice(buffer.readerIndex(), length) - buffer.readerIndex(buffer.readerIndex() + length) - slice + buffer.readBytes(length) } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 0a9a645e..001c91ff 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -19,15 +19,15 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf abstract class InformationParser(charset: Charset) extends MessageParser { - override def parseMessage(b: ChannelBuffer): ServerMessage = { + override def parseMessage(b: ByteBuf): ServerMessage = { val fields = scala.collection.mutable.Map[Char, String]() - while (b.readable()) { + while (b.isReadable()) { val kind = b.readByte() if (kind != 0) { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala index 5791b3c3..176c3f4d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParser.scala @@ -17,10 +17,10 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf trait MessageParser { - def parseMessage(buffer: ChannelBuffer): ServerMessage + def parseMessage(buffer: ByteBuf): ServerMessage } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala index 40e94f73..6770d6dc 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.exceptions.ParserNotAvailableException import com.github.mauricio.async.db.postgresql.messages.backend._ import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf class MessageParsersRegistry(charset: Charset) { @@ -49,7 +49,7 @@ class MessageParsersRegistry(charset: Charset) { } } - def parse(t: Byte, b: ChannelBuffer): ServerMessage = { + def parse(t: Byte, b: ByteBuf): ServerMessage = { this.parserFor(t).parseMessage(b) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index 74b57efb..85c0de5b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -19,13 +19,13 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, ServerMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf class ParameterStatusParser(charset: Charset) extends MessageParser { import ChannelUtils._ - override def parseMessage(b: ChannelBuffer): ServerMessage = { + override def parseMessage(b: ByteBuf): ServerMessage = { new ParameterStatusMessage(readCString(b, charset), readCString(b, charset)) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala index 7c553c67..70357bac 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReadyForQueryParser.scala @@ -17,11 +17,11 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ReadyForQueryMessage, ServerMessage} -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object ReadyForQueryParser extends MessageParser { - override def parseMessage(b: ChannelBuffer): ServerMessage = { + override def parseMessage(b: ByteBuf): ServerMessage = { new ReadyForQueryMessage(b.readByte().asInstanceOf[Char]) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala index f75baafa..088da761 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ReturningMessageParser.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend._ -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf object ReturningMessageParser { @@ -31,6 +31,6 @@ object ReturningMessageParser { class ReturningMessageParser(val message: ServerMessage) extends MessageParser { - def parseMessage(buffer: ChannelBuffer): ServerMessage = this.message + def parseMessage(buffer: ByteBuf): ServerMessage = this.message } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 9f2903d8..068cc884 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, PostgreSQLColumnData, ServerMessage} import com.github.mauricio.async.db.util.ChannelUtils import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffer +import io.netty.buffer.ByteBuf /** @@ -61,7 +61,7 @@ The format code being used for the field. Currently will be zero (text) or one ( class RowDescriptionParser(charset: Charset) extends MessageParser { - override def parseMessage(b: ChannelBuffer): ServerMessage = { + override def parseMessage(b: ByteBuf): ServerMessage = { val columnsCount = b.readShort() val columns = new Array[PostgreSQLColumnData](columnsCount) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala index d2c655ed..f7e13c9f 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala @@ -18,9 +18,9 @@ package com.github.mauricio.async.db.general import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, ColumnTypes} import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumnData -import org.jboss.netty.buffer.{ChannelBuffers, ChannelBuffer} -import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification +import io.netty.util.CharsetUtil +import io.netty.buffer.{Unpooled, ByteBuf} class MutableResultSetSpec extends Specification { @@ -76,14 +76,14 @@ class MutableResultSetSpec extends Specification { } - def toBuffer( content : String ) : ChannelBuffer = { - val buffer = ChannelBuffers.dynamicBuffer() + def toBuffer( content : String ) : ByteBuf = { + val buffer = Unpooled.buffer() buffer.writeBytes( content.getBytes(charset) ) buffer } - def toBuffer( value : Int ) : ChannelBuffer = { - val buffer = ChannelBuffers.dynamicBuffer() + def toBuffer( value : Int ) : ByteBuf = { + val buffer = Unpooled.buffer() buffer.writeBytes(value.toString.getBytes(charset)) buffer } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index 3773c153..14f0bed2 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -19,10 +19,11 @@ package com.github.mauricio.postgresql import com.github.mauricio.async.db.postgresql.codec.MessageDecoder import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException} import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ErrorMessage} -import org.jboss.netty.buffer.ChannelBuffers -import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification import com.github.mauricio.async.db.exceptions.NegativeMessageSizeException +import io.netty.util.CharsetUtil +import io.netty.buffer.Unpooled +import java.util class MessageDecoderSpec extends Specification { @@ -32,32 +33,33 @@ class MessageDecoderSpec extends Specification { "not try to decode if there is not enought data available" in { - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeByte('R') buffer.writeByte(1) buffer.writeByte(2) + val out = new util.ArrayList[Object]() - this.decoder.decode(null, null, buffer) must beNull + this.decoder.decode(null, buffer, out) + out.isEmpty } "should not try to decode if there is a type and lenght but it's not long enough" in { - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeByte('R') buffer.writeInt(30) buffer.writeBytes("my-name".getBytes(CharsetUtil.UTF_8)) - List( - this.decoder.decode(null, null, buffer) must beNull, - buffer.readerIndex() === 0 - ) + val out = new util.ArrayList[Object]() + this.decoder.decode(null, buffer, out) + buffer.readerIndex() === 0 } "should correctly decode a message" in { - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() val text = "This is an error message" val textBytes = text.getBytes(CharsetUtil.UTF_8) @@ -66,28 +68,31 @@ class MessageDecoderSpec extends Specification { buffer.writeByte('M') buffer.writeBytes(textBytes) buffer.writeByte(0) - - val result = this.decoder.decode(null, null, buffer).asInstanceOf[ErrorMessage] - + val out = new util.ArrayList[Object]() + this.decoder.decode(null, buffer, out) + out.size === 1 + val result = out.get(0).asInstanceOf[ErrorMessage] result.message === text buffer.readerIndex() === (textBytes.length + 4 + 1 + 1 + 1) } "should raise an exception if the length is negative" in { - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeByte( ServerMessage.Close ) buffer.writeInt( 2 ) + val out = new util.ArrayList[Object]() - this.decoder.decode(null, null, buffer) must throwA[NegativeMessageSizeException] + this.decoder.decode(null, buffer, out) must throwA[NegativeMessageSizeException] } "should raise an exception if the length is too big" in { - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeByte( ServerMessage.Close ) buffer.writeInt( MessageDecoder.DefaultMaximumSize + 10 ) + val out = new util.ArrayList[Object]() - this.decoder.decode(null, null, buffer) must throwA[MessageTooLongException] + this.decoder.decode(null, buffer, out) must throwA[MessageTooLongException] } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala index d70aa94d..37e9e575 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserESpec.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ErrorMessage} -import org.jboss.netty.buffer.ChannelBuffers -import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification +import io.netty.util.CharsetUtil +import io.netty.buffer.Unpooled class ParserESpec extends Specification { @@ -29,7 +29,7 @@ class ParserESpec extends Specification { val content = "this is my error message" val error = content.getBytes(CharsetUtil.UTF_8) - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeByte('M') buffer.writeBytes(error) buffer.writeByte(0) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala index 326588a1..e6b29dd4 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala @@ -17,8 +17,8 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ProcessData} -import org.jboss.netty.buffer.ChannelBuffers import org.specs2.mutable.Specification +import io.netty.buffer.Unpooled class ParserKSpec extends Specification { @@ -28,7 +28,7 @@ class ParserKSpec extends Specification { "correctly parse the message" in { - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeInt(10) buffer.writeInt(20) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala index 4ed277af..e1fa3baf 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserSSpec.scala @@ -18,9 +18,9 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, ParameterStatusMessage} import java.nio.charset.Charset -import org.jboss.netty.buffer.ChannelBuffers -import org.jboss.netty.util.CharsetUtil import org.specs2.mutable.Specification +import io.netty.buffer.Unpooled +import io.netty.util.CharsetUtil class ParserSSpec extends Specification { @@ -33,7 +33,7 @@ class ParserSSpec extends Specification { val key = "application-name" val value = "my-cool-application" - val buffer = ChannelBuffers.dynamicBuffer() + val buffer = Unpooled.buffer() buffer.writeBytes(key.getBytes(Charset.forName("UTF-8"))) buffer.writeByte(0) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala index 6b6de329..7959789c 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/PasswordHelperSpec.scala @@ -1,7 +1,7 @@ package com.github.mauricio.async.db.postgresql.util import org.specs2.mutable.Specification -import org.jboss.netty.util.CharsetUtil +import io.netty.util.CharsetUtil /** * User: mauricio diff --git a/project/Build.scala b/project/Build.scala index 11daa50f..9229f4a2 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -55,7 +55,7 @@ object Configuration { "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", "org.scala-lang" % "scala-library" % "2.10.2", - "io.netty" % "netty" % "3.6.6.Final", + "io.netty" % "netty-all" % "4.0.5.Final", specs2Dependency ) @@ -111,4 +111,4 @@ object Configuration { ) ) -} \ No newline at end of file +} From ad4018eb39d0dc66df534a0ed4cab1ebb0c4c2e2 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 7 Aug 2013 06:36:32 +0200 Subject: [PATCH 148/357] Tiny stuf --- .../com/github/mauricio/async/db/util/ChannelUtils.scala | 4 ++-- .../async/db/mysql/codec/MySQLFrameDecoderSpec.scala | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala index 1bac1399..4142614b 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala @@ -61,7 +61,7 @@ object ChannelUtils { b.readerIndex(b.readerIndex() + count) - return result + result } def readUntilEOF( b : ByteBuf, charset : Charset ) : String = { @@ -91,7 +91,7 @@ object ChannelUtils { b.readerIndex(b.readerIndex() + count) - return result + result } def read3BytesInt( b : ByteBuf ) : Int = { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index e5d0579d..269b79db 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -70,7 +70,7 @@ class MySQLFrameDecoderSpec extends Specification { val decoder = new MySQLFrameDecoder(charset) val embedder = new EmbeddedChannel(decoder) - embedder.config.setAllocator(new LittleEndianByteBufAllocator) + embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) decoder.queryProcessStarted() @@ -90,7 +90,7 @@ class MySQLFrameDecoderSpec extends Specification { val decoder = new MySQLFrameDecoder(charset) val embedder = new EmbeddedChannel(decoder) - embedder.config.setAllocator(new LittleEndianByteBufAllocator) + embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) decoder.queryProcessStarted() @@ -113,7 +113,7 @@ class MySQLFrameDecoderSpec extends Specification { val decoder = new MySQLFrameDecoder(charset) val embedder = new EmbeddedChannel(decoder) - embedder.config.setAllocator(new LittleEndianByteBufAllocator) + embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) decoder.queryProcessStarted() @@ -166,7 +166,7 @@ class MySQLFrameDecoderSpec extends Specification { def createPipeline(): EmbeddedChannel = { val channel = new EmbeddedChannel(new MySQLFrameDecoder(charset)) - channel.config.setAllocator(new LittleEndianByteBufAllocator) + channel.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) channel } From d703065f32dd71c8a01d85926bab8b80f761d9c5 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 7 Aug 2013 07:02:44 +0200 Subject: [PATCH 149/357] Only start EventLoop if needed --- .../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 2ecd6944..d17c15e5 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 @@ -19,6 +19,6 @@ import io.netty.channel.nio.NioEventLoopGroup object NettyUtils { - val DetaultEventLoopGroup = new NioEventLoopGroup() + lazy val DetaultEventLoopGroup = new NioEventLoopGroup() } \ No newline at end of file From 239874c14cd65aece271f130a978419f23744bce Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 7 Aug 2013 09:01:50 +0200 Subject: [PATCH 150/357] Allow to pass EventLoop in --- .../com/github/mauricio/async/db/mysql/MySQLConnection.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 42ff05b2..dff220a2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -30,8 +30,7 @@ import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure import scala.util.Success -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.ChannelHandlerContext +import io.netty.channel.{EventLoopGroup, ChannelHandlerContext} object MySQLConnection { final val log = Log.get[MySQLConnection] @@ -42,7 +41,7 @@ object MySQLConnection { class MySQLConnection( configuration: Configuration, charsetMapper: CharsetMapper = CharsetMapper.Instance, - group : NioEventLoopGroup = NettyUtils.DetaultEventLoopGroup, + group : EventLoopGroup = NettyUtils.DetaultEventLoopGroup, executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends MySQLHandlerDelegate From feabd1d4b1881850e099284e051839d6c37a1cf6 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 7 Aug 2013 09:03:28 +0200 Subject: [PATCH 151/357] Allow to pass EventLoop in --- .../mauricio/async/db/postgresql/PostgreSQLConnection.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4c6ba35c..8f037cef 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 @@ -31,7 +31,7 @@ import messages.frontend._ import scala.Some import scala.concurrent._ import io.netty.util.internal.logging.{Slf4JLoggerFactory, InternalLoggerFactory} -import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.EventLoopGroup object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] @@ -45,7 +45,7 @@ class PostgreSQLConnection configuration: Configuration = Configuration.Default, encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance, - group : NioEventLoopGroup = NettyUtils.DetaultEventLoopGroup, + group : EventLoopGroup = NettyUtils.DetaultEventLoopGroup, executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends PostgreSQLConnectionDelegate From 528d86ee572669295e6c6a008174efd002992c39 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 7 Aug 2013 23:32:36 -0300 Subject: [PATCH 152/357] Fixing wrong conditional that was causing the code to evaluate a query when all it should have done was accepting the connection --- .../mauricio/async/db/util/NettyUtils.scala | 2 +- .../async/db/mysql/MySQLConnection.scala | 43 +++++---- .../mysql/codec/MySQLConnectionHandler.scala | 5 +- .../db/mysql/codec/MySQLFrameDecoder.scala | 41 +++++---- ...paredStatementPrepareResponseDecoder.scala | 5 +- .../async/db/mysql/ConcurrentlyRunTest.scala | 92 +++++++++++++++++++ .../mysql/codec/MySQLFrameDecoderSpec.scala | 8 +- 7 files changed, 150 insertions(+), 46 deletions(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConcurrentlyRunTest.scala 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 d17c15e5..5dc2a237 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 @@ -19,6 +19,6 @@ import io.netty.channel.nio.NioEventLoopGroup object NettyUtils { - lazy val DetaultEventLoopGroup = new NioEventLoopGroup() + lazy val DetaultEventLoopGroup = new NioEventLoopGroup(0, DaemonThreadsFactory) } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index dff220a2..0dba9545 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db._ -import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException +import com.github.mauricio.async.db.exceptions.{ConnectionNotConnectedException, ConnectionStillRunningQueryException} import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.mysql.message.client._ @@ -54,9 +54,16 @@ class MySQLConnection( charsetMapper.toInt(configuration.charset) private final val connectionCount = MySQLConnection.Counter.incrementAndGet() + private final val connectionId = s"[mysql-connection-$connectionCount]" private implicit val internalPool = executionContext - private final val connectionHandler = new MySQLConnectionHandler(configuration, charsetMapper, this, group, executionContext) + private final val connectionHandler = new MySQLConnectionHandler( + configuration, + charsetMapper, + this, + group, + executionContext, + connectionId) private final val connectionPromise = Promise[Connection]() private final val disconnectionPromise = Promise[Connection]() @@ -98,17 +105,17 @@ class MySQLConnection( } override def connected(ctx: ChannelHandlerContext) { - log.debug("Connected to {}", ctx.channel.remoteAddress) + log.debug(s"$connectionId Connected to {}", ctx.channel.remoteAddress) this.connected = true } override def exceptionCaught(throwable: Throwable) { - log.error("Transport failure", throwable) + log.error(s"$connectionId Transport failure ", throwable) setException(throwable) } override def onError(message: ErrorMessage) { - log.error("Received an error message -> {}", message) + log.error(s"$connectionId Received an error message -> {}", message) val exception = new MySQLException(message) exception.fillInStackTrace() this.setException(exception) @@ -121,20 +128,21 @@ class MySQLConnection( } override def onOk(message: OkMessage) { - this.connectionPromise.trySuccess(this) - - if (this.isQuerying) { - this.succeedQueryPromise( - new MySQLQueryResult( - message.affectedRows, - message.message, - message.lastInsertId, - message.statusFlags, - message.warnings + if ( !this.connectionPromise.isCompleted ) { + this.connectionPromise.success(this) + } else { + if (this.isQuerying) { + this.succeedQueryPromise( + new MySQLQueryResult( + message.affectedRows, + message.message, + message.lastInsertId, + message.statusFlags, + message.warnings + ) ) - ) + } } - } def onEOF(message: EOFMessage) { @@ -152,7 +160,6 @@ class MySQLConnection( } override def onHandshake(message: HandshakeMessage) { - this.serverVersion = Version(message.serverVersion) this.connectionHandler.write(new HandshakeResponseMessage( diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 4d16ce1a..d3d400e2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -58,7 +58,8 @@ class MySQLConnectionHandler( charsetMapper: CharsetMapper, handlerDelegate: MySQLHandlerDelegate, group : EventLoopGroup, - executionContext : ExecutionContext + executionContext : ExecutionContext, + connectionId : String ) extends SimpleChannelInboundHandler[Object] { @@ -66,7 +67,7 @@ class MySQLConnectionHandler( private final val bootstrap = new Bootstrap().group(this.group) private final val connectionPromise = Promise[MySQLConnectionHandler] - private final val decoder = new MySQLFrameDecoder(configuration.charset) + private final val decoder = new MySQLFrameDecoder(configuration.charset, connectionId) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) private final val currentParameters = new ArrayBuffer[ColumnDefinitionMessage]() private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index c0204b21..18ea6c79 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -22,20 +22,22 @@ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log -import java.nio.charset.Charset - -import com.github.mauricio.async.db.mysql.MySQLHelper -import io.netty.handler.codec.ByteToMessageDecoder -import io.netty.channel.ChannelHandlerContext import io.netty.buffer.ByteBuf +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.ByteToMessageDecoder import java.nio.ByteOrder +import java.nio.charset.Charset +import java.util.concurrent.atomic.AtomicInteger + object MySQLFrameDecoder { val log = Log.get[MySQLFrameDecoder] } -class MySQLFrameDecoder(charset: Charset) extends ByteToMessageDecoder { +class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToMessageDecoder { + + private final val messagesCount = new AtomicInteger() private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) private final val okDecoder = new OkDecoder(charset) @@ -43,19 +45,19 @@ class MySQLFrameDecoder(charset: Charset) extends ByteToMessageDecoder { private final val rowDecoder = new ResultSetRowDecoder(charset) private final val preparedStatementPrepareDecoder = new PreparedStatementPrepareResponseDecoder() - private[codec] var processingColumns = false - private[codec] var processingParams = false - private[codec] var isInQuery = false - private[codec] var isPreparedStatementPrepare = false - private[codec] var isPreparedStatementExecute = false - private[codec] var isPreparedStatementExecuteRows = false + @volatile private[codec] var processingColumns = false + @volatile private[codec] var processingParams = false + @volatile private[codec] var isInQuery = false + @volatile private[codec] var isPreparedStatementPrepare = false + @volatile private[codec] var isPreparedStatementExecute = false + @volatile private[codec] var isPreparedStatementExecuteRows = false - private[codec] var totalParams = 0L - private[codec] var processedParams = 0L - private[codec] var totalColumns = 0L - private[codec] var processedColumns = 0L + @volatile private[codec] var totalParams = 0L + @volatile private[codec] var processedParams = 0L + @volatile private[codec] var totalColumns = 0L + @volatile private[codec] var processedColumns = 0L - private var hasReadColumnsCount = false + @volatile private var hasReadColumnsCount = false def decode(ctx: ChannelHandlerContext, buffer: ByteBuf, out: java.util.List[Object]): Unit = { if (buffer.readableBytes() > 4) { @@ -68,6 +70,8 @@ class MySQLFrameDecoder(charset: Charset) extends ByteToMessageDecoder { if (buffer.readableBytes() >= size) { + messagesCount.incrementAndGet() + val messageType = buffer.getByte(buffer.readerIndex()) if (size < 0) { @@ -77,7 +81,7 @@ class MySQLFrameDecoder(charset: Charset) extends ByteToMessageDecoder { // TODO: Remove once https://github.com/netty/netty/issues/1704 is fixed val slice = buffer.readSlice(size).order(ByteOrder.LITTLE_ENDIAN) //val dump = MySQLHelper.dumpAsHex(slice) - //log.debug(s"Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) + //log.debug(s"$connectionId [${messagesCount.get()}] Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) slice.readByte() @@ -232,6 +236,7 @@ class MySQLFrameDecoder(charset: Charset) extends ByteToMessageDecoder { this.isPreparedStatementExecuteRows = false this.isInQuery = false this.processingColumns = false + this.processingParams = false this.totalColumns = 0 this.processedColumns = 0 this.totalParams = 0 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala index c252ed88..bb3f1174 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala @@ -16,10 +16,9 @@ package com.github.mauricio.async.db.mysql.decoder -import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{PreparedStatementPrepareResponse, ServerMessage} -import com.github.mauricio.async.db.mysql.MySQLHelper import com.github.mauricio.async.db.util.Log +import io.netty.buffer.ByteBuf class PreparedStatementPrepareResponseDecoder extends MessageDecoder { @@ -27,7 +26,7 @@ class PreparedStatementPrepareResponseDecoder extends MessageDecoder { def decode(buffer: ByteBuf): ServerMessage = { - //val dump = MySQLHelper.dumpAsHex(buffer, buffer.readableBytes()) + //val dump = MySQLHelper.dumpAsHex(buffer) //log.debug("prepared statement response dump is \n{}", dump) val statementId = Array[Byte]( buffer.readByte(), buffer.readByte(), buffer.readByte(), buffer.readByte() ) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConcurrentlyRunTest.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConcurrentlyRunTest.scala new file mode 100644 index 00000000..d67fb189 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConcurrentlyRunTest.scala @@ -0,0 +1,92 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.util.Log +import java.util.concurrent.atomic.AtomicInteger + +/** + * Mainly a way to try to figure out why sometimes MySQL will fail with a bad prepared statement response message. + */ + +object ConcurrentlyRunTest extends ConnectionHelper with Runnable { + + private val log = Log.getByName(this.getClass.getName) + private val counter = new AtomicInteger() + private val failures = new AtomicInteger() + + def run() { + 1.until(50).foreach(x => execute(counter.incrementAndGet())) + } + + def main(args : Array[String]) { + + log.info("Starting executing code") + + val threads = 1.until(10).map(x => new Thread(this)) + + threads.foreach {t => t.start()} + + while ( !threads.forall(t => t.isAlive) ) { + Thread.sleep(5000) + } + + log.info(s"Finished executing code, failed execution ${failures.get()} times") + + } + + + def execute(count : Int) { + try { + log.info(s"====> run $count") + val create = """CREATE TEMPORARY TABLE posts ( + | id INT NOT NULL AUTO_INCREMENT, + | some_text TEXT not null, + | some_date DATE, + | primary key (id) )""".stripMargin + + val insert = "insert into posts (some_text) values (?)" + val select = "select * from posts limit 100" + + withConnection { + connection => + executeQuery(connection, create) + + executePreparedStatement(connection, insert, "this is some text here") + + val row = executeQuery(connection, select).rows.get(0) + assert(row("id") == 1) + assert(row("some_text") == "this is some text here") + assert(row("some_date") == null) + + val queryRow = executePreparedStatement(connection, select).rows.get(0) + + assert(queryRow("id") == 1) + assert(queryRow("some_text") == "this is some text here") + assert(queryRow("some_date") == null) + + } + } catch { + case e : Exception => { + failures.incrementAndGet() + log.error( s"Failed to execute on run $count - ${e.getMessage}", e) + } + } + + } + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index 269b79db..00a3c977 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -68,7 +68,7 @@ class MySQLFrameDecoderSpec extends Specification { "on a query process it should correctly send an OK" in { - val decoder = new MySQLFrameDecoder(charset) + val decoder = new MySQLFrameDecoder(charset, "[mysql-connection]") val embedder = new EmbeddedChannel(decoder) embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) @@ -88,7 +88,7 @@ class MySQLFrameDecoderSpec extends Specification { "on query process it should correctly send an error" in { - val decoder = new MySQLFrameDecoder(charset) + val decoder = new MySQLFrameDecoder(charset, "[mysql-connection]") val embedder = new EmbeddedChannel(decoder) embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) @@ -111,7 +111,7 @@ class MySQLFrameDecoderSpec extends Specification { "on query process it should correctly handle a result set" in { - val decoder = new MySQLFrameDecoder(charset) + val decoder = new MySQLFrameDecoder(charset, "[mysql-connection]") val embedder = new EmbeddedChannel(decoder) embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) @@ -165,7 +165,7 @@ class MySQLFrameDecoderSpec extends Specification { } def createPipeline(): EmbeddedChannel = { - val channel = new EmbeddedChannel(new MySQLFrameDecoder(charset)) + val channel = new EmbeddedChannel(new MySQLFrameDecoder(charset, "[mysql-connection]")) channel.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) channel } From 9df4129a480a86466a965c52c398c4f8db77ca6c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 7 Aug 2013 23:35:13 -0300 Subject: [PATCH 153/357] Including #netty4 migration to changelog and @normanmaurer as contributor --- CHANGELOG.md | 1 + README.markdown | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 901f01b3..06bef540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Allow the ClientSocketChannelFactory and ExecutionContext to be given at the connections instead of always using the driver provided ones - #38 +* Upgraded to Netty 4 - @normanmaurer ## 0.2.4 - 2013-07-06 diff --git a/README.markdown b/README.markdown index 20b2347d..31b58d91 100644 --- a/README.markdown +++ b/README.markdown @@ -211,6 +211,7 @@ Check the blog post above for more details and the project's ScalaDocs. * [devsprint](https://github.com/devsprint) * [fwbrasil](https://github.com/fwbrasil) * [magro](https://github.com/magro) +* [normanmaurer](https://github.com/normanmaurer) * [theon](https://github.com/theon) ## Contributing From 9fd4385173fe60bae2fc4ab71b4b0828863e2aad Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 8 Aug 2013 12:22:31 -0300 Subject: [PATCH 154/357] Removing volatiles --- .../db/mysql/codec/MySQLFrameDecoder.scala | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 18ea6c79..5d9eb23e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -45,19 +45,19 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM private final val rowDecoder = new ResultSetRowDecoder(charset) private final val preparedStatementPrepareDecoder = new PreparedStatementPrepareResponseDecoder() - @volatile private[codec] var processingColumns = false - @volatile private[codec] var processingParams = false - @volatile private[codec] var isInQuery = false - @volatile private[codec] var isPreparedStatementPrepare = false - @volatile private[codec] var isPreparedStatementExecute = false - @volatile private[codec] var isPreparedStatementExecuteRows = false - - @volatile private[codec] var totalParams = 0L - @volatile private[codec] var processedParams = 0L - @volatile private[codec] var totalColumns = 0L - @volatile private[codec] var processedColumns = 0L - - @volatile private var hasReadColumnsCount = false + private[codec] var processingColumns = false + private[codec] var processingParams = false + private[codec] var isInQuery = false + private[codec] var isPreparedStatementPrepare = false + private[codec] var isPreparedStatementExecute = false + private[codec] var isPreparedStatementExecuteRows = false + + private[codec] var totalParams = 0L + private[codec] var processedParams = 0L + private[codec] var totalColumns = 0L + private[codec] var processedColumns = 0L + + private var hasReadColumnsCount = false def decode(ctx: ChannelHandlerContext, buffer: ByteBuf, out: java.util.List[Object]): Unit = { if (buffer.readableBytes() > 4) { From 04572e0d85da882ca92dab6fe3742ca44431097a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 8 Aug 2013 12:54:09 -0300 Subject: [PATCH 155/357] Starting 0.2.6 development --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 9229f4a2..4765d03a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.5-SNAPSHOT" + val commonVersion = "0.2.6-SNAPSHOT" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" From bf33128a2f151cf8e43c5a043a3b8f9101e7634b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 11 Aug 2013 23:22:03 -0300 Subject: [PATCH 156/357] Upgrading netty to 4.0.7 --- README.markdown | 8 ++++---- project/Build.scala | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.markdown b/README.markdown index 31b58d91..e957b42c 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.4" +"com.github.mauricio" %% "postgresql-async" % "0.2.6" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.4 + 0.2.6 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.4" +"com.github.mauricio" %% "mysql-async" % "0.2.6" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.4 + 0.2.6 ``` diff --git a/project/Build.scala b/project/Build.scala index 4765d03a..16292a66 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.6-SNAPSHOT" + val commonVersion = "0.2.6" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" @@ -55,7 +55,7 @@ object Configuration { "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", "org.scala-lang" % "scala-library" % "2.10.2", - "io.netty" % "netty-all" % "4.0.5.Final", + "io.netty" % "netty-all" % "4.0.7.Final", specs2Dependency ) From 7c624b5aface5fbca2722c81daed89713822d4d0 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 28 Aug 2013 00:03:35 -0300 Subject: [PATCH 157/357] Timestamps with timezone and microsends support - fixes #41 --- .../async/db/column/ColumnDecoder.scala | 5 +- .../db/column/ColumnDecoderRegistry.scala | 3 +- .../db/column/TimestampEncoderDecoder.scala | 3 + .../async/db/general/ColumnData.scala | 1 + .../mysql/codec/MySQLConnectionHandler.scala | 26 ++--- .../mysql/column/ByteArrayColumnDecoder.scala | 3 +- .../server/ColumnDefinitionMessage.scala | 1 + .../mysql/binary/BinaryRowDecoderSpec.scala | 4 +- .../db/postgresql/PostgreSQLConnection.scala | 2 +- .../db/postgresql/column/ArrayDecoder.scala | 20 +++- .../PostgreSQLColumnDecoderRegistry.scala | 13 +-- .../PostgreSQLTimestampEncoderDecoder.scala | 96 +++++++++++++++++++ .../backend/PostgreSQLColumnData.scala | 2 +- .../db/postgresql/PreparedStatementSpec.scala | 63 +++++++++++- .../postgresql/column/ArrayDecoderSpec.scala | 24 ++--- 15 files changed, 215 insertions(+), 51 deletions(-) create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala index 19b98bc1..a7ea19f2 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoder.scala @@ -18,10 +18,11 @@ package com.github.mauricio.async.db.column import java.nio.charset.Charset import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.general.ColumnData trait ColumnDecoder { - def decode(value: ByteBuf, charset : Charset): Any = { + def decode( kind : ColumnData, value : ByteBuf, charset : Charset ) : Any = { val bytes = new Array[Byte](value.readableBytes()) value.readBytes(bytes) decode(new String(bytes, charset)) @@ -29,4 +30,6 @@ trait ColumnDecoder { def decode( value : String ) : Any + def supportsStringDecoding : Boolean = true + } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala index 6ceac0fb..5f3af8ab 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/ColumnDecoderRegistry.scala @@ -18,9 +18,10 @@ package com.github.mauricio.async.db.column import java.nio.charset.Charset import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.general.ColumnData trait ColumnDecoderRegistry { - def decode(kind: Int, value: ByteBuf, charset : Charset) : Any + def decode(kind: ColumnData, value: ByteBuf, charset : Charset) : Any } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala index 99ceb4d2..0613dcf2 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala @@ -30,10 +30,13 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { private val optional = new DateTimeFormatterBuilder() .appendPattern(".SSSSSS").toParser + private val optionalTimeZone = new DateTimeFormatterBuilder() + .appendPattern("Z").toParser private val format = new DateTimeFormatterBuilder() .appendPattern("yyyy-MM-dd HH:mm:ss") .appendOptional(optional) + .appendOptional(optionalTimeZone) .toFormatter def formatter = format diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala index 1e544bd1..4799c8da 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ColumnData.scala @@ -20,5 +20,6 @@ trait ColumnData { def name : String def dataType : Int + def dataTypeSize : Long } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index d3d400e2..5b8accff 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -24,30 +24,16 @@ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBufAllocator +import io.netty.channel._ +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.CodecException import java.net.InetSocketAddress -import java.nio.ByteOrder import scala.Some import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent._ -import io.netty.channel._ -import io.netty.bootstrap.Bootstrap -import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage -import com.github.mauricio.async.db.mysql.message.client.HandshakeResponseMessage -import com.github.mauricio.async.db.mysql.message.server.ErrorMessage -import com.github.mauricio.async.db.mysql.message.server.PreparedStatementPrepareResponse -import com.github.mauricio.async.db.mysql.message.client.QueryMessage -import scala.Some -import com.github.mauricio.async.db.mysql.message.server.OkMessage -import com.github.mauricio.async.db.mysql.message.client.PreparedStatementMessage -import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage -import com.github.mauricio.async.db.mysql.message.client.PreparedStatementExecuteMessage -import com.github.mauricio.async.db.mysql.message.server.BinaryRowMessage -import com.github.mauricio.async.db.mysql.message.server.EOFMessage -import com.github.mauricio.async.db.mysql.message.client.PreparedStatementPrepareMessage -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.buffer.ByteBufAllocator -import io.netty.handler.codec.CodecException object MySQLConnectionHandler { val log = Log.get[MySQLConnectionHandler] @@ -157,7 +143,7 @@ class MySQLConnectionHandler( null } else { val columnDescription = this.currentQuery.columnTypes(x) - columnDescription.textDecoder.decode(message(x), configuration.charset) + columnDescription.textDecoder.decode(columnDescription, message(x), configuration.charset) } x += 1 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala index dcd66d45..b7956c6a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/column/ByteArrayColumnDecoder.scala @@ -19,10 +19,11 @@ package com.github.mauricio.async.db.mysql.column import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.column.ColumnDecoder import java.nio.charset.Charset +import com.github.mauricio.async.db.general.ColumnData object ByteArrayColumnDecoder extends ColumnDecoder { - override def decode(value: ByteBuf, charset: Charset): Any = { + override def decode(kind: ColumnData , value: ByteBuf, charset: Charset): Any = { val bytes = new Array[Byte](value.readableBytes()) value.readBytes(bytes) bytes diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala index 82dcc47d..d88cd82f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/ColumnDefinitionMessage.scala @@ -41,6 +41,7 @@ case class ColumnDefinitionMessage( with ColumnData { def dataType: Int = this.columnType + def dataTypeSize : Long = this.columnLength override def toString: String = { val columnTypeName = ColumnTypes.Mapping.getOrElse(columnType, columnType) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala index 6486fcd9..e463be69 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoderSpec.scala @@ -43,7 +43,7 @@ class BinaryRowDecoderSpec extends Specification { val buffer = Unpooled.wrappedBuffer(idAndName).order(ByteOrder.LITTLE_ENDIAN) val result = decoder.decode(buffer, idAndNameColumns) - + buffer.release() result(0) === 1L result(1) === "joe" @@ -52,7 +52,7 @@ class BinaryRowDecoderSpec extends Specification { "decode a row with an long, a string and a null" in { val buffer = Unpooled.wrappedBuffer(idNameAndNull).order(ByteOrder.LITTLE_ENDIAN) val result = decoder.decode(buffer, idNameAndNullColumns) - + buffer.release() result(0) === 1L result(1) === "joe" result(2) must beNull 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 8f037cef..de52c1ae 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 @@ -217,7 +217,7 @@ class PostgreSQLConnection null } else { val columnType = this.currentQuery.get.columnTypes(x) - this.decoderRegistry.decode(columnType.dataType, m.values(x), configuration.charset) + this.decoderRegistry.decode(columnType, m.values(x), configuration.charset) } x += 1 } 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 3e058c0f..d69eeba4 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 @@ -20,10 +20,17 @@ 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 com.github.mauricio.async.db.general.ColumnData +import io.netty.buffer.{Unpooled, ByteBuf} +import java.nio.charset.Charset -class ArrayDecoder(private val encoder: ColumnDecoder) extends ColumnDecoder { +class ArrayDecoder(private val decoder: ColumnDecoder) extends ColumnDecoder { - override def decode(value: String): IndexedSeq[Any] = { + override def decode( kind : ColumnData, buffer : ByteBuf, charset : Charset ): IndexedSeq[Any] = { + + val bytes = new Array[Byte](buffer.readableBytes()) + buffer.readBytes(bytes) + val value = new String(bytes, charset) val stack = new Stack[ArrayBuffer[Any]]() var current: ArrayBuffer[Any] = null @@ -34,7 +41,12 @@ class ArrayDecoder(private val encoder: ColumnDecoder) extends ColumnDecoder { } override def elementFound(element: String) { - current += encoder.decode(element) + val result = if ( decoder.supportsStringDecoding ) { + decoder.decode(element) + } else { + decoder.decode(kind, Unpooled.wrappedBuffer( element.getBytes(charset) ), charset) + } + current += result } override def nullElementFound { @@ -60,4 +72,6 @@ class ArrayDecoder(private val encoder: ColumnDecoder) extends ColumnDecoder { result } + def decode( value : String ) : Any = throw new UnsupportedOperationException("Should not be called") + } 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 5bff6e6c..d5da1513 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 @@ -22,6 +22,7 @@ import scala.annotation.switch import java.nio.charset.Charset import io.netty.util.CharsetUtil import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.general.ColumnData object PostgreSQLColumnDecoderRegistry { val Instance = new PostgreSQLColumnDecoderRegistry() @@ -38,14 +39,14 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e private final val bigDecimalArrayDecoder = new ArrayDecoder(BigDecimalEncoderDecoder) private final val floatArrayDecoder = new ArrayDecoder(FloatEncoderDecoder) private final val doubleArrayDecoder = new ArrayDecoder(DoubleEncoderDecoder) - private final val timestampArrayDecoder = new ArrayDecoder(TimestampEncoderDecoder.Instance) - private final val timestampWithTimezoneArrayDecoder = new ArrayDecoder(TimestampWithTimezoneEncoderDecoder) + private final val timestampArrayDecoder = new ArrayDecoder(PostgreSQLTimestampEncoderDecoder) + private final val timestampWithTimezoneArrayDecoder = new ArrayDecoder(PostgreSQLTimestampEncoderDecoder) private final val dateArrayDecoder = new ArrayDecoder(DateEncoderDecoder) private final val timeArrayDecoder = new ArrayDecoder(TimeEncoderDecoder.Instance) private final val timeWithTimestampArrayDecoder = new ArrayDecoder(TimeWithTimezoneEncoderDecoder) - override def decode(kind: Int, value: ByteBuf, charset: Charset): Any = { - decoderFor(kind).decode(value, charset) + override def decode(kind: ColumnData, value: ByteBuf, charset: Charset): Any = { + decoderFor(kind.dataType).decode(kind, value, charset) } def decoderFor(kind: Int): ColumnDecoder = { @@ -83,10 +84,10 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e case Bpchar => StringEncoderDecoder case BpcharArray => this.stringArrayDecoder - case Timestamp => TimestampEncoderDecoder.Instance + case Timestamp => PostgreSQLTimestampEncoderDecoder case TimestampArray => this.timestampArrayDecoder - case TimestampWithTimezone => TimestampWithTimezoneEncoderDecoder + case TimestampWithTimezone => PostgreSQLTimestampEncoderDecoder case TimestampWithTimezoneArray => this.timestampWithTimezoneArrayDecoder case Date => DateEncoderDecoder diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala new file mode 100644 index 00000000..9ef83b2c --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala @@ -0,0 +1,96 @@ +/* + * 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.column + +import com.github.mauricio.async.db.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException +import com.github.mauricio.async.db.general.ColumnData +import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumnData +import com.github.mauricio.async.db.util.Log +import io.netty.buffer.ByteBuf +import java.nio.charset.Charset +import java.sql.Timestamp +import java.util.{Calendar, Date} +import org.joda.time.format.DateTimeFormatterBuilder +import org.joda.time.{ReadableDateTime, DateTime} + +object PostgreSQLTimestampEncoderDecoder extends ColumnEncoderDecoder { + + private val log = Log.getByName(this.getClass.getName) + + private val optionalTimeZone = new DateTimeFormatterBuilder() + .appendPattern("Z").toParser + + private val internalFormatters = 1.until(6).inclusive.map { + index => + new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss") + .appendPattern("." + ("S" * index )) + .appendOptional(optionalTimeZone) + .toFormatter + } + + private val internalFormatterWithoutSeconds = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss") + .appendOptional(optionalTimeZone) + .toFormatter + + def formatter = internalFormatters(5) + + override def decode( kind : ColumnData, value : ByteBuf, charset : Charset ) : Any = { + val bytes = new Array[Byte](value.readableBytes()) + value.readBytes(bytes) + + val text = new String(bytes, charset) + + val columnType = kind.asInstanceOf[PostgreSQLColumnData] + + val format = columnType.dataType match { + case ColumnTypes.Timestamp | ColumnTypes.TimestampArray | ColumnTypes.TimestampWithTimezoneArray => { + if ( text.contains(".") ) { + internalFormatters(5) + } else { + internalFormatterWithoutSeconds + } + } + case ColumnTypes.TimestampWithTimezone => { + if ( columnType.dataTypeModifier > 0 ) { + internalFormatters(columnType.dataTypeModifier - 1) + } else { + internalFormatterWithoutSeconds + } + } + } + + format.parseDateTime(text) + } + + override def decode(value : String) : Any = throw new UnsupportedOperationException("this method should not have been called") + + override def encode(value: Any): String = { + value match { + case t: Timestamp => this.formatter.print(new DateTime(t)) + case t: Date => this.formatter.print(new DateTime(t)) + case t: Calendar => this.formatter.print(new DateTime(t)) + case t: ReadableDateTime => this.formatter.print(t) + case _ => throw new DateEncoderNotAvailableException(value) + } + } + + override def supportsStringDecoding : Boolean = false + +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala index aab7f147..029ddce7 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala @@ -23,6 +23,6 @@ class PostgreSQLColumnData( val tableObjectId: Int, val columnNumber: Int, val dataType: Int, - val dataTypeSize: Int, + val dataTypeSize: Long, val dataTypeModifier: Int, val fieldFormat: Int) extends ColumnData \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 9356ea6f..5e438e44 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -18,10 +18,13 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification import com.github.mauricio.async.db.postgresql.exceptions.InsufficientParametersException -import org.joda.time.LocalDate +import org.joda.time.{DateTime, LocalDate} +import com.github.mauricio.async.db.util.Log class PreparedStatementSpec extends Specification with DatabaseTestHelper { + val log = Log.get[PreparedStatementSpec] + val filler = List.fill(64)(" ").mkString("") val messagesCreate = """CREATE TEMP TABLE messages @@ -115,6 +118,64 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } } + "support timestamp with timezone" in { + withHandler { + handler => + + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment timestamp with time zone NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + + executeDdl(handler, create) + executeQuery(handler, "INSERT INTO messages (moment) VALUES ('1999-01-08 04:05:06 -3:00')") + val rows = executePreparedStatement(handler, "SELECT * FROM messages").rows.get + + rows.length === 1 + + val dateTime = rows(0)("moment").asInstanceOf[DateTime] + + dateTime.getZone.toTimeZone.getRawOffset === -10800000 + + } + } + + "support timestamp with timezone and microseconds" in { + + 1.until(6).inclusive.map { + index => + withHandler { + handler => + + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment timestamp(%d) with time zone NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""".format(index) + + log.debug("create is {}", create) + + executeDdl(handler, create) + + val seconds = (index.toString * index).toLong + + executeQuery(handler, "INSERT INTO messages (moment) VALUES ('1999-01-08 04:05:06.%d -3:00')".format(seconds)) + val rows = executePreparedStatement(handler, "SELECT * FROM messages").rows.get + + rows.length === 1 + + val dateTime = rows(0)("moment").asInstanceOf[DateTime] + + dateTime.getZone.toTimeZone.getRawOffset === -10800000 + } + + + } + } + } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala index 75fdbb68..e91a8462 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoderSpec.scala @@ -17,30 +17,26 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.column.IntegerEncoderDecoder +import io.netty.buffer.Unpooled +import io.netty.util.CharsetUtil import org.specs2.mutable.Specification class ArrayDecoderSpec extends Specification { + def execute( data : String ) : Any = { + val numbers = data.getBytes( CharsetUtil.UTF_8 ) + val encoder = new ArrayDecoder(IntegerEncoderDecoder) + encoder.decode(null, Unpooled.wrappedBuffer(numbers), CharsetUtil.UTF_8) + } + "encoder/decoder" should { "parse an array of numbers" in { - - val numbers = "{1,2,3}" - val encoder = new ArrayDecoder(IntegerEncoderDecoder) - - val result = encoder.decode(numbers) - - result === List(1, 2, 3) + execute("{1,2,3}") === List(1, 2, 3) } "parse an array of array of numbers" in { - - val numbers = "{{1,2,3},{4,5,6}}" - val encoder = new ArrayDecoder(IntegerEncoderDecoder) - - val result = encoder.decode(numbers) - - result === List(List(1, 2, 3), List(4, 5, 6)) + execute("{{1,2,3},{4,5,6}}") === List(List(1, 2, 3), List(4, 5, 6)) } } From 3a482e5725e12d25e3c81d01dd97cc1090c98e79 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 30 Aug 2013 21:15:18 -0300 Subject: [PATCH 158/357] Making sure column names are available for MySQL (fixes #42) and adding tests for time objects on PostgreSQL --- .../async/db/general/MutableResultSet.scala | 2 +- .../mauricio/async/db/mysql/QuerySpec.scala | 21 +++ .../db/postgresql/PreparedStatementSpec.scala | 58 ------- .../async/db/postgresql/TimeAndDateSpec.scala | 162 ++++++++++++++++++ 4 files changed, 184 insertions(+), 59 deletions(-) create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index c0d0e18d..de4d2cd7 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -35,7 +35,7 @@ class MutableResultSet[T <: ColumnData]( ( this.columnTypes(index).name, index ) ).toMap - override def columnNames : IndexedSeq[String] = this.columnTypes.map( data => data.name ) + val columnNames : IndexedSeq[String] = this.columnMapping.keys.toIndexedSeq override def length: Int = this.rows.length diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index a39fd07e..9fceac03 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -136,9 +136,30 @@ class QuerySpec extends Specification with ConnectionHelper { val row = executeQuery(connection, select).rows.get(0) row("id") === 1 row("some_bytes") === bytes + } + } + + "have column names on result set" in { + + val create = """CREATE TEMPORARY TABLE posts ( + | id INT NOT NULL AUTO_INCREMENT, + | some_bytes BLOB not null, + | primary key (id) )""".stripMargin + val columns = List("id", "some_bytes") + val select = "SELECT * FROM posts" + withConnection { + connection => + executeQuery(connection, create) + + val preparedResult = executePreparedStatement(connection, select).rows.get + preparedResult.columnNames === columns + + val result = executeQuery(connection, select).rows.get + result.columnNames === columns } + } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 5e438e44..53d8f944 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -118,64 +118,6 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } } - "support timestamp with timezone" in { - withHandler { - handler => - - val create = """CREATE TEMP TABLE messages - ( - id bigserial NOT NULL, - moment timestamp with time zone NOT NULL, - CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) - )""" - - executeDdl(handler, create) - executeQuery(handler, "INSERT INTO messages (moment) VALUES ('1999-01-08 04:05:06 -3:00')") - val rows = executePreparedStatement(handler, "SELECT * FROM messages").rows.get - - rows.length === 1 - - val dateTime = rows(0)("moment").asInstanceOf[DateTime] - - dateTime.getZone.toTimeZone.getRawOffset === -10800000 - - } - } - - "support timestamp with timezone and microseconds" in { - - 1.until(6).inclusive.map { - index => - withHandler { - handler => - - val create = """CREATE TEMP TABLE messages - ( - id bigserial NOT NULL, - moment timestamp(%d) with time zone NOT NULL, - CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) - )""".format(index) - - log.debug("create is {}", create) - - executeDdl(handler, create) - - val seconds = (index.toString * index).toLong - - executeQuery(handler, "INSERT INTO messages (moment) VALUES ('1999-01-08 04:05:06.%d -3:00')".format(seconds)) - val rows = executePreparedStatement(handler, "SELECT * FROM messages").rows.get - - rows.length === 1 - - val dateTime = rows(0)("moment").asInstanceOf[DateTime] - - dateTime.getZone.toTimeZone.getRawOffset === -10800000 - } - - - } - } - } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala new file mode 100644 index 00000000..f1e2a7a0 --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -0,0 +1,162 @@ +/* + * 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 + +import org.specs2.mutable.Specification +import org.joda.time.{LocalTime, DateTime} + +class TimeAndDateSpec extends Specification with DatabaseTestHelper { + + "when processing times and dates" should { + + "support a time object" in { + + withHandler { + handler => + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment time NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + + executeDdl(handler, create) + executeQuery(handler, "INSERT INTO messages (moment) VALUES ('04:05:06')") + + val rows = executePreparedStatement(handler, "select * from messages").rows.get + + val time = rows(0)("moment").asInstanceOf[LocalTime] + + time.getHourOfDay === 4 + time.getMinuteOfHour === 5 + time.getSecondOfMinute === 6 + } + + } + + "support a time object with microseconds" in { + + withHandler { + handler => + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment time(6) NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + + executeDdl(handler, create) + executeQuery(handler, "INSERT INTO messages (moment) VALUES ('04:05:06.134')") + + val rows = executePreparedStatement(handler, "select * from messages").rows.get + + val time = rows(0)("moment").asInstanceOf[LocalTime] + + time.getHourOfDay === 4 + time.getMinuteOfHour === 5 + time.getSecondOfMinute === 6 + time.getMillisOfSecond === 134 + } + + } + + "support a time with timezone object" in { + + pending("need to find a way to implement this") + + withHandler { + handler => + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment time with time zone NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + + executeDdl(handler, create) + executeQuery(handler, "INSERT INTO messages (moment) VALUES ('04:05:06 -3:00')") + + val rows = executePreparedStatement(handler, "select * from messages").rows.get + + val time = rows(0)("moment").asInstanceOf[LocalTime] + + time.getHourOfDay === 4 + time.getMinuteOfHour === 5 + time.getSecondOfMinute === 6 + } + + } + + "support timestamp with timezone" in { + withHandler { + handler => + + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment timestamp with time zone NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + + executeDdl(handler, create) + executeQuery(handler, "INSERT INTO messages (moment) VALUES ('1999-01-08 04:05:06 -3:00')") + val rows = executePreparedStatement(handler, "SELECT * FROM messages").rows.get + + rows.length === 1 + + val dateTime = rows(0)("moment").asInstanceOf[DateTime] + + dateTime.getZone.toTimeZone.getRawOffset === -10800000 + + } + } + + "support timestamp with timezone and microseconds" in { + + 1.until(6).inclusive.map { + index => + withHandler { + handler => + + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment timestamp(%d) with time zone NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""".format(index) + + executeDdl(handler, create) + + val seconds = (index.toString * index).toLong + + executeQuery(handler, "INSERT INTO messages (moment) VALUES ('1999-01-08 04:05:06.%d -3:00')".format(seconds)) + val rows = executePreparedStatement(handler, "SELECT * FROM messages").rows.get + + rows.length === 1 + + val dateTime = rows(0)("moment").asInstanceOf[DateTime] + + dateTime.getZone.toTimeZone.getRawOffset === -10800000 + } + + + } + } + + } + +} From 0c03b73f4e2939e7ed4931f4e91e1c43a27f5c85 Mon Sep 17 00:00:00 2001 From: kxbmap Date: Sat, 7 Sep 2013 15:46:19 +0900 Subject: [PATCH 159/357] Add build.properties file --- project/build.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 project/build.properties diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 00000000..0974fce4 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.0 From 68b80bc721b28d76894aa0aae9a0d5f1fb76751e Mon Sep 17 00:00:00 2001 From: kxbmap Date: Sat, 7 Sep 2013 16:13:25 +0900 Subject: [PATCH 160/357] Depends slf4j-api --- project/Build.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 16292a66..fac1af3b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -48,19 +48,22 @@ object Configuration { val commonVersion = "0.2.6" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" + val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" val commonDependencies = Seq( "commons-pool" % "commons-pool" % "1.6", - "ch.qos.logback" % "logback-classic" % "1.0.13", + "org.slf4j" % "slf4j-api" % "1.7.5", "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", "org.scala-lang" % "scala-library" % "2.10.2", "io.netty" % "netty-all" % "4.0.7.Final", - specs2Dependency + specs2Dependency, + logbackDependency ) val implementationDependencies = Seq( - specs2Dependency + specs2Dependency, + logbackDependency ) val baseSettings = Defaults.defaultSettings ++ Seq( From eb7032b3c40c592dfa78b3a03184d1dbe64e1457 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 8 Sep 2013 23:53:29 -0300 Subject: [PATCH 161/357] Upgrading netty, fixes #45 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index fac1af3b..2a3d801d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -56,7 +56,7 @@ object Configuration { "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", "org.scala-lang" % "scala-library" % "2.10.2", - "io.netty" % "netty-all" % "4.0.7.Final", + "io.netty" % "netty-all" % "4.0.9.Final", specs2Dependency, logbackDependency ) From cbbe443a023f81a2664d096feea4fffefa30d749 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 9 Sep 2013 00:10:52 -0300 Subject: [PATCH 162/357] Buffer allocator doesn't pool buffer objects --- .../async/db/mysql/codec/LittleEndianByteBufAllocator.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala index fafc58ab..40b51f24 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala @@ -28,6 +28,7 @@ object LittleEndianByteBufAllocator { class LittleEndianByteBufAllocator extends ByteBufAllocator { private val allocator = new UnpooledByteBufAllocator(false) + def isDirectBufferPooled: Boolean = false def buffer() = littleEndian(allocator.buffer()) @@ -66,4 +67,5 @@ class LittleEndianByteBufAllocator extends ByteBufAllocator { def compositeDirectBuffer(maxNumComponents: Int): CompositeByteBuf = allocator.compositeDirectBuffer(maxNumComponents) private def littleEndian(b: ByteBuf) = b.order(ByteOrder.LITTLE_ENDIAN) + } From 936785f58cabf73d19363d85d1b3e14e11bde91a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 9 Sep 2013 00:21:27 -0300 Subject: [PATCH 163/357] Closing 0.2.7 --- CHANGELOG.md | 9 ++++++++- project/Build.scala | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06bef540..f1b304f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog -## 0.2.5 - SNAPSHOT +## 0.2.7 - 2013-09-09 + +* Upgrading Netty to 4.0.9 +* Removing direct dependency on `logback` and making it depend on SFL4J only, upgrading JodaTime - by @kxbmap +* MySQL doesn't set columnNames in QueryResult - #42 +* Timestamps with microseconds fail to be parsed on PostgreSQL - #41 + +## 0.2.5 * Allow the ClientSocketChannelFactory and ExecutionContext to be given at the connections instead of always using the driver provided ones - #38 diff --git a/project/Build.scala b/project/Build.scala index 2a3d801d..1966635d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.6" + val commonVersion = "0.2.7" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" From 67a60d9ef4d031b4395c9872d87984834755161f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 9 Sep 2013 00:22:45 -0300 Subject: [PATCH 164/357] Startig 0.2.8 development --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 1966635d..b2fd6eac 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.7" + val commonVersion = "0.2.8-SNAPSHOT" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" From 65b70532aa2a9c6f75512f996d7b754c1f697d65 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 9 Sep 2013 07:53:06 -0300 Subject: [PATCH 165/357] Updating version at the docs --- README.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.markdown b/README.markdown index e957b42c..8c713d80 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.6" +"com.github.mauricio" %% "postgresql-async" % "0.2.7" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.6 + 0.2.7 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.6" +"com.github.mauricio" %% "mysql-async" % "0.2.7" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.6 + 0.2.7 ``` From 8765606e3214b9bf5e713acbdf1194da88ace78c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 14 Sep 2013 09:37:53 -0300 Subject: [PATCH 166/357] Use the rows count as the affected rows for MySQL also - fixes #46 --- .../com/github/mauricio/async/db/mysql/MySQLConnection.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 0dba9545..2d420f2e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -207,7 +207,7 @@ class MySQLConnection( if (this.isQuerying) { this.succeedQueryPromise( new MySQLQueryResult( - 0, + resultSet.size, null, -1, message.flags, From b8adc6cdd0730a281b66e314ee40e713f7b302de Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Sep 2013 19:57:43 -0300 Subject: [PATCH 167/357] Adding support for MySQL BIT type - fixes #48 --- ...annelUtils.scala => ByteBufferUtils.scala} | 2 +- .../async/db/util/ChannelWrapper.scala | 6 ++--- .../mauricio/async/db/util/NettyUtils.scala | 2 +- .../async/db/util/ChannelUtilsSpec.scala | 10 +++---- .../async/db/mysql/MySQLConnection.scala | 2 +- .../db/mysql/binary/BinaryRowEncoder.scala | 6 ++--- .../db/mysql/codec/DecoderRegistry.scala | 2 ++ .../db/mysql/codec/MySQLFrameDecoder.scala | 2 +- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 4 +-- .../mysql/decoder/HandshakeV10Decoder.scala | 6 ++--- .../encoder/HandshakeResponseEncoder.scala | 10 +++---- .../PreparedStatementExecuteEncoder.scala | 4 +-- .../PreparedStatementPrepareEncoder.scala | 4 +-- .../mysql/encoder/QueryMessageEncoder.scala | 4 +-- .../db/mysql/encoder/QuitMessageEncoder.scala | 4 +-- .../mauricio/async/db/mysql/QuerySpec.scala | 27 +++++++++++++++++++ .../mysql/codec/MySQLFrameDecoderSpec.scala | 14 +++++----- .../db/postgresql/PostgreSQLConnection.scala | 2 +- .../encoders/CredentialEncoder.scala | 4 +-- .../ExecutePreparedStatementEncoder.scala | 2 +- .../PreparedStatementEncoderHelper.scala | 4 +-- .../PreparedStatementOpeningEncoder.scala | 4 +-- .../encoders/QueryMessageEncoder.scala | 6 ++--- .../encoders/StartupMessageEncoder.scala | 10 +++---- .../parsers/CommandCompleteParser.scala | 4 +-- .../parsers/InformationParser.scala | 4 +-- .../parsers/ParameterStatusParser.scala | 4 +-- .../parsers/RowDescriptionParser.scala | 4 +-- 28 files changed, 93 insertions(+), 64 deletions(-) rename db-async-common/src/main/scala/com/github/mauricio/async/db/util/{ChannelUtils.scala => ByteBufferUtils.scala} (99%) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ByteBufferUtils.scala similarity index 99% rename from db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/util/ByteBufferUtils.scala index 4142614b..1cd73dce 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ByteBufferUtils.scala @@ -20,7 +20,7 @@ import java.nio.charset.Charset import java.nio.ByteOrder import io.netty.buffer.{Unpooled, ByteBuf} -object ChannelUtils { +object ByteBufferUtils { def writeLength(buffer: ByteBuf) { diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index bbe38ed1..229dabff 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -39,9 +39,9 @@ class ChannelWrapper( val buffer : ByteBuf ) extends AnyVal { new String( bytes, charset ) } - def readCString( charset : Charset ) = ChannelUtils.readCString(buffer, charset) + def readCString( charset : Charset ) = ByteBufferUtils.readCString(buffer, charset) - def readUntilEOF( charset: Charset ) = ChannelUtils.readUntilEOF(buffer, charset) + def readUntilEOF( charset: Charset ) = ByteBufferUtils.readUntilEOF(buffer, charset) def read3BytesInt : Int = { val first = buffer.readByte() @@ -106,7 +106,7 @@ class ChannelWrapper( val buffer : ByteBuf ) extends AnyVal { } def writePacketLength( sequence : Int = 0 ) { - ChannelUtils.writePacketLength(buffer, sequence ) + ByteBufferUtils.writePacketLength(buffer, sequence ) } } 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 5dc2a237..d43b14f9 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 @@ -19,6 +19,6 @@ import io.netty.channel.nio.NioEventLoopGroup object NettyUtils { - lazy val DetaultEventLoopGroup = new NioEventLoopGroup(0, DaemonThreadsFactory) + lazy val DefaultEventLoopGroup = new NioEventLoopGroup(0, DaemonThreadsFactory) } \ No newline at end of file diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala index 11998ba2..e6238d38 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/ChannelUtilsSpec.scala @@ -30,9 +30,9 @@ class ChannelUtilsSpec extends Specification { val content = "some text" val buffer = Unpooled.buffer() - ChannelUtils.writeCString(content, buffer, charset) + ByteBufferUtils.writeCString(content, buffer, charset) - ChannelUtils.readCString(buffer, charset) === content + ByteBufferUtils.readCString(buffer, charset) === content buffer.readableBytes() === 0 } @@ -40,9 +40,9 @@ class ChannelUtilsSpec extends Specification { val content = "some text" val buffer = Unpooled.buffer() - ChannelUtils.writeCString(content, buffer, charset) + ByteBufferUtils.writeCString(content, buffer, charset) - ChannelUtils.readUntilEOF(buffer, charset) === content + ByteBufferUtils.readUntilEOF(buffer, charset) === content buffer.readableBytes() === 0 } @@ -53,7 +53,7 @@ class ChannelUtilsSpec extends Specification { buffer.writeBytes(content.getBytes(charset)) - ChannelUtils.readUntilEOF(buffer, charset) === content + ByteBufferUtils.readUntilEOF(buffer, charset) === content buffer.readableBytes() === 0 } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 2d420f2e..dad1575b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -41,7 +41,7 @@ object MySQLConnection { class MySQLConnection( configuration: Configuration, charsetMapper: CharsetMapper = CharsetMapper.Instance, - group : EventLoopGroup = NettyUtils.DetaultEventLoopGroup, + group : EventLoopGroup = NettyUtils.DefaultEventLoopGroup, executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends MySQLHandlerDelegate diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 75d7e67d..c904259e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -69,9 +69,9 @@ class BinaryRowEncoder( charset : Charset ) { val nullBitsCount = (values.size + 7) / 8 val nullBits = new Array[Byte](nullBitsCount) - val bitMapBuffer = ChannelUtils.mysqlBuffer(1 + nullBitsCount) - val parameterTypesBuffer = ChannelUtils.mysqlBuffer(values.size * 2) - val parameterValuesBuffer = ChannelUtils.mysqlBuffer() + val bitMapBuffer = ByteBufferUtils.mysqlBuffer(1 + nullBitsCount) + val parameterTypesBuffer = ByteBufferUtils.mysqlBuffer(values.size * 2) + val parameterValuesBuffer = ByteBufferUtils.mysqlBuffer() var index = 0 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala index df14afd2..246c5a8d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala @@ -49,6 +49,7 @@ class DecoderRegistry(charset: Charset) { this.stringDecoder } } + case ColumnTypes.FIELD_TYPE_BIT => ByteArrayDecoder case ColumnTypes.FIELD_TYPE_LONGLONG => LongDecoder case ColumnTypes.FIELD_TYPE_LONG | ColumnTypes.FIELD_TYPE_INT24 => IntegerDecoder case ColumnTypes.FIELD_TYPE_YEAR | ColumnTypes.FIELD_TYPE_SHORT => ShortDecoder @@ -87,6 +88,7 @@ class DecoderRegistry(charset: Charset) { ColumnTypes.FIELD_TYPE_STRING | ColumnTypes.FIELD_TYPE_ENUM => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_YEAR => ShortEncoderDecoder + case ColumnTypes.FIELD_TYPE_BIT => ByteArrayColumnDecoder case ColumnTypes.FIELD_TYPE_BLOB => { if (charsetCode == CharsetMapper.Binary) { ByteArrayColumnDecoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 5d9eb23e..5c26c3a2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.exceptions._ import com.github.mauricio.async.db.mysql.decoder._ import com.github.mauricio.async.db.mysql.message.server._ -import com.github.mauricio.async.db.util.ChannelUtils.read3BytesInt +import com.github.mauricio.async.db.util.ByteBufferUtils.read3BytesInt import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.util.Log import io.netty.buffer.ByteBuf diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index f89d9e7d..819f5604 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import com.github.mauricio.async.db.mysql.encoder._ import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.mysql.util.CharsetMapper -import com.github.mauricio.async.db.util.{ChannelUtils, Log} +import com.github.mauricio.async.db.util.{ByteBufferUtils, Log} import java.nio.charset.Charset import scala.annotation.switch import io.netty.channel.ChannelHandlerContext @@ -68,7 +68,7 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten val result = encoder.encode(message) - ChannelUtils.writePacketLength(result, sequence) + ByteBufferUtils.writePacketLength(result, sequence) sequence += 1 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala index 6d842900..81cf2900 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.mysql.decoder import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.server.{HandshakeMessage, ServerMessage} -import com.github.mauricio.async.db.util.{Log, ChannelUtils} +import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import java.nio.charset.Charset object HandshakeV10Decoder { @@ -34,7 +34,7 @@ class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { def decode(buffer: ByteBuf): ServerMessage = { - val serverVersion = ChannelUtils.readCString(buffer, charset) + val serverVersion = ByteBufferUtils.readCString(buffer, charset) val connectionId = buffer.readInt() var seed = new Array[Byte]( SeedSize + SeedComplementSize ) @@ -56,7 +56,7 @@ class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { buffer.readerIndex(buffer.readerIndex() + Padding) buffer.readBytes(seed, SeedSize, SeedComplementSize) buffer.readByte() - authenticationMethod = Some(ChannelUtils.readUntilEOF(buffer, charset)) + authenticationMethod = Some(ByteBufferUtils.readUntilEOF(buffer, charset)) } new HandshakeMessage( diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala index 1446b66c..e74b050e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodEx import com.github.mauricio.async.db.mysql.encoder.auth.MySQLNativePasswordAuthentication import com.github.mauricio.async.db.mysql.message.client.{HandshakeResponseMessage, ClientMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper -import com.github.mauricio.async.db.util.{Log, ChannelUtils} +import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import java.nio.charset.Charset object HandshakeResponseEncoder { @@ -66,13 +66,13 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e clientCapabilities |= CLIENT_CONNECT_WITH_DB } - val buffer = ChannelUtils.packetBuffer() + val buffer = ByteBufferUtils.packetBuffer() buffer.writeInt(clientCapabilities) buffer.writeInt(MAX_3_BYTES) buffer.writeByte(charsetMapper.toInt(charset)) buffer.writeBytes(PADDING) - ChannelUtils.writeCString( m.username, buffer, charset ) + ByteBufferUtils.writeCString( m.username, buffer, charset ) if ( m.password.isDefined ) { val method = m.authenticationMethod.get @@ -86,11 +86,11 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e } if ( m.database.isDefined ) { - ChannelUtils.writeCString( m.database.get, buffer, charset ) + ByteBufferUtils.writeCString( m.database.get, buffer, charset ) } if ( m.authenticationMethod.isDefined ) { - ChannelUtils.writeCString( m.authenticationMethod.get, buffer, charset ) + ByteBufferUtils.writeCString( m.authenticationMethod.get, buffer, charset ) } else { buffer.writeByte(0) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala index 56def795..e21b15f6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala @@ -19,14 +19,14 @@ package com.github.mauricio.async.db.mysql.encoder import io.netty.buffer.{ByteBuf, Unpooled} import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementExecuteMessage, ClientMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends MessageEncoder { def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] - val buffer = ChannelUtils.packetBuffer() + val buffer = ByteBufferUtils.packetBuffer() buffer.writeByte( m.kind ) buffer.writeBytes(m.statementId) buffer.writeByte(0x00) // no cursor diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala index c1323e61..bfdb5812 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementPrepareEncoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.mysql.encoder import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementPrepareMessage, ClientMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset class PreparedStatementPrepareEncoder( charset : Charset ) extends MessageEncoder { @@ -26,7 +26,7 @@ class PreparedStatementPrepareEncoder( charset : Charset ) extends MessageEncode def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementPrepareMessage] val statement = m.statement.getBytes(charset) - val buffer = ChannelUtils.packetBuffer( 4 + 1 + statement.size) + val buffer = ByteBufferUtils.packetBuffer( 4 + 1 + statement.size) buffer.writeByte( m.kind ) buffer.writeBytes( statement ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala index a4297429..a8e0ec1f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QueryMessageEncoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.mysql.encoder import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.client.{QueryMessage, ClientMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset class QueryMessageEncoder( charset : Charset ) extends MessageEncoder { @@ -27,7 +27,7 @@ class QueryMessageEncoder( charset : Charset ) extends MessageEncoder { val m = message.asInstanceOf[QueryMessage] val encodedQuery = m.query.getBytes( charset ) - val buffer = ChannelUtils.packetBuffer(4 + 1 + encodedQuery.length ) + val buffer = ByteBufferUtils.packetBuffer(4 + 1 + encodedQuery.length ) buffer.writeByte( ClientMessage.Query ) buffer.writeBytes( encodedQuery ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala index f847c488..1b4add07 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/QuitMessageEncoder.scala @@ -18,12 +18,12 @@ package com.github.mauricio.async.db.mysql.encoder import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.mysql.message.client.ClientMessage -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils object QuitMessageEncoder extends MessageEncoder { def encode(message: ClientMessage): ByteBuf = { - val buffer = ChannelUtils.packetBuffer(5) + val buffer = ByteBufferUtils.packetBuffer(5) buffer.writeByte( ClientMessage.Quit ) buffer } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 9fceac03..9aa6203c 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -162,6 +162,33 @@ class QuerySpec extends Specification with ConnectionHelper { } + "support BIT type" in { + + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL AUTO_INCREMENT, + | bit_column BIT(20), + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (bit_column) VALUES (b'10000000')" + val select = "SELECT * FROM POSTS" + + withConnection { + connection => + executeQuery(connection, create) + executeQuery(connection, insert) + + val rows = executeQuery(connection, select).rows.get + rows(0)("bit_column") === Array(0,0,-128) + + val preparedRows = executePreparedStatement(connection, select).rows.get + preparedRows(0)("bit_column") === Array(0,0,-128) + + } + + } + } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index 00a3c977..5ff9a563 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.mysql.codec import io.netty.buffer.ByteBuf import io.netty.util.CharsetUtil import com.github.mauricio.async.db.mysql.message.server._ -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import org.specs2.mutable.Specification import com.github.mauricio.async.db.mysql.message.server.OkMessage @@ -119,7 +119,7 @@ class MySQLFrameDecoderSpec extends Specification { decoder.totalColumns === 0 - val columnCountBuffer = ChannelUtils.packetBuffer() + val columnCountBuffer = ByteBufferUtils.packetBuffer() columnCountBuffer.writeLength(2) columnCountBuffer.writePacketLength() @@ -148,7 +148,7 @@ class MySQLFrameDecoderSpec extends Specification { decoder.processingColumns must beFalse - val row = ChannelUtils.packetBuffer() + val row = ByteBufferUtils.packetBuffer() row.writeLenghtEncodedString("1", charset) row.writeLenghtEncodedString("some name", charset) row.writePacketLength() @@ -171,7 +171,7 @@ class MySQLFrameDecoderSpec extends Specification { } def createOkPacket() : ByteBuf = { - val buffer = ChannelUtils.packetBuffer() + val buffer = ByteBufferUtils.packetBuffer() buffer.writeByte(0) buffer.writeLength(10) buffer.writeLength(15) @@ -183,7 +183,7 @@ class MySQLFrameDecoderSpec extends Specification { } def createErrorPacket(content : String) : ByteBuf = { - val buffer = ChannelUtils.packetBuffer() + val buffer = ByteBufferUtils.packetBuffer() buffer.writeByte(0xff) buffer.writeShort(27) buffer.writeByte('H') @@ -194,7 +194,7 @@ class MySQLFrameDecoderSpec extends Specification { } def createColumnPacket( name : String, columnType : Int ) : ByteBuf = { - val buffer = ChannelUtils.packetBuffer() + val buffer = ByteBufferUtils.packetBuffer() buffer.writeLenghtEncodedString("def", charset) buffer.writeLenghtEncodedString("some_schema", charset) buffer.writeLenghtEncodedString("some_table", charset) @@ -213,7 +213,7 @@ class MySQLFrameDecoderSpec extends Specification { } def createEOFPacket() : ByteBuf = { - val buffer = ChannelUtils.packetBuffer() + val buffer = ByteBufferUtils.packetBuffer() buffer.writeByte(0xfe) buffer.writeShort(879) buffer.writeShort(8765) 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 de52c1ae..ecd545de 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 @@ -45,7 +45,7 @@ class PostgreSQLConnection configuration: Configuration = Configuration.Default, encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance, - group : EventLoopGroup = NettyUtils.DetaultEventLoopGroup, + group : EventLoopGroup = NettyUtils.DefaultEventLoopGroup, executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends PostgreSQLConnectionDelegate diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala index f1f70279..3a8f4c5b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/CredentialEncoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, AuthenticationResponseType} import com.github.mauricio.async.db.postgresql.messages.frontend.{CredentialMessage, ClientMessage} import com.github.mauricio.async.db.postgresql.util.PasswordHelper -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} @@ -48,7 +48,7 @@ class CredentialEncoder(charset: Charset) extends Encoder { buffer.writeBytes(password) buffer.writeByte(0) - ChannelUtils.writeLength(buffer) + ByteBufferUtils.writeLength(buffer) buffer } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 6b425c81..1b49138a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementExecuteMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.ByteBuf diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala index 38ba9057..1ad9242a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -import com.github.mauricio.async.db.util.{Log, ChannelUtils} +import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import com.github.mauricio.async.db.column.ColumnEncoderRegistry import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} @@ -64,7 +64,7 @@ trait PreparedStatementEncoderHelper { bindBuffer.writeShort(0) - ChannelUtils.writeLength(bindBuffer) + ByteBufferUtils.writeLength(bindBuffer) if ( writeDescribe ) { val describeLength = 1 + 4 + 1 + statementIdBytes.length + 1 diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index d71028da..f78a2321 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementOpeningMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} @@ -51,7 +51,7 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR parseBuffer.writeInt(kind) } - ChannelUtils.writeLength(parseBuffer) + ByteBufferUtils.writeLength(parseBuffer) val executeBuffer = writeExecutePortal(statementIdBytes, m.values, encoder, charset, true) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index c85ae8b9..579e86b9 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, ClientMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} @@ -31,9 +31,9 @@ class QueryMessageEncoder(charset: Charset) extends Encoder { val buffer = Unpooled.buffer() buffer.writeByte(ServerMessage.Query) buffer.writeInt(0) - ChannelUtils.writeCString(m.query, buffer, charset) + ByteBufferUtils.writeCString(m.query, buffer, charset) - ChannelUtils.writeLength(buffer) + ByteBufferUtils.writeLength(buffer) buffer } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index 4e0f11a0..b8c97843 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, StartupMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} @@ -38,12 +38,12 @@ class StartupMessageEncoder(charset: Charset) extends Encoder { pair => pair._2 match { case value: String => { - ChannelUtils.writeCString(pair._1, buffer, charset) - ChannelUtils.writeCString(value, buffer, charset) + ByteBufferUtils.writeCString(pair._1, buffer, charset) + ByteBufferUtils.writeCString(value, buffer, charset) } case Some(value) => { - ChannelUtils.writeCString(pair._1, buffer, charset) - ChannelUtils.writeCString(value.toString, buffer, charset) + ByteBufferUtils.writeCString(pair._1, buffer, charset) + ByteBufferUtils.writeCString(value.toString, buffer, charset) } case _ => {} } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala index 9d7ec1d2..04c9edb4 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/CommandCompleteParser.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{CommandCompleteMessage, ServerMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.ByteBuf @@ -25,7 +25,7 @@ class CommandCompleteParser(charset: Charset) extends MessageParser { override def parseMessage(b: ByteBuf): ServerMessage = { - val result = ChannelUtils.readCString(b, charset) + val result = ByteBufferUtils.readCString(b, charset) val indexOfRowCount = result.lastIndexOf(" ") diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala index 001c91ff..48b39cec 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/InformationParser.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.ByteBuf @@ -33,7 +33,7 @@ abstract class InformationParser(charset: Charset) extends MessageParser { if (kind != 0) { fields.put( kind.toChar, - ChannelUtils.readCString(b, charset) + ByteBufferUtils.readCString(b, charset) ) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala index 85c0de5b..f1cb601e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/ParameterStatusParser.scala @@ -17,13 +17,13 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{ParameterStatusMessage, ServerMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.ByteBuf class ParameterStatusParser(charset: Charset) extends MessageParser { - import ChannelUtils._ + import ByteBufferUtils._ override def parseMessage(b: ByteBuf): ServerMessage = { new ParameterStatusMessage(readCString(b, charset), readCString(b, charset)) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala index 068cc884..260aaa7c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/RowDescriptionParser.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.parsers import com.github.mauricio.async.db.postgresql.messages.backend.{RowDescriptionMessage, PostgreSQLColumnData, ServerMessage} -import com.github.mauricio.async.db.util.ChannelUtils +import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.ByteBuf @@ -69,7 +69,7 @@ class RowDescriptionParser(charset: Charset) extends MessageParser { 0.until(columnsCount).foreach { index => columns(index) = new PostgreSQLColumnData( - name = ChannelUtils.readCString(b, charset), + name = ByteBufferUtils.readCString(b, charset), tableObjectId = b.readInt(), columnNumber = b.readShort(), dataType = b.readInt(), From c60defaf619bfd4f3de8984c35895f91584ce987 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Sep 2013 20:09:38 -0300 Subject: [PATCH 168/357] Validate the number of parameters for prepared statements - fixes #47 --- .../InsufficientParametersException.scala | 2 +- .../async/db/mysql/MySQLConnection.scala | 16 +++++++++++++++- .../mauricio/async/db/mysql/QuerySpec.scala | 12 ++++++++++++ .../db/postgresql/PostgreSQLConnection.scala | 2 +- .../db/postgresql/PreparedStatementSpec.scala | 2 +- 5 files changed, 30 insertions(+), 4 deletions(-) rename {postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql => db-async-common/src/main/scala/com/github/mauricio/async/db}/exceptions/InsufficientParametersException.scala (95%) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/InsufficientParametersException.scala similarity index 95% rename from postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala rename to db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/InsufficientParametersException.scala index d3163c15..41746dc8 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/exceptions/InsufficientParametersException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/InsufficientParametersException.scala @@ -14,7 +14,7 @@ * under the License. */ -package com.github.mauricio.async.db.postgresql.exceptions +package com.github.mauricio.async.db.exceptions import com.github.mauricio.async.db.exceptions.DatabaseException diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index dad1575b..fa4b046b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db._ -import com.github.mauricio.async.db.exceptions.{ConnectionNotConnectedException, ConnectionStillRunningQueryException} +import com.github.mauricio.async.db.exceptions._ import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.mysql.message.client._ @@ -31,6 +31,16 @@ import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure import scala.util.Success import io.netty.channel.{EventLoopGroup, ChannelHandlerContext} +import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage +import com.github.mauricio.async.db.mysql.message.client.HandshakeResponseMessage +import com.github.mauricio.async.db.mysql.message.server.ErrorMessage +import com.github.mauricio.async.db.mysql.message.client.QueryMessage +import scala.util.Failure +import scala.Some +import com.github.mauricio.async.db.mysql.message.server.OkMessage +import com.github.mauricio.async.db.mysql.message.client.PreparedStatementMessage +import scala.util.Success +import com.github.mauricio.async.db.mysql.message.server.EOFMessage object MySQLConnection { final val log = Log.get[MySQLConnection] @@ -224,6 +234,10 @@ class MySQLConnection( def sendPreparedStatement(query: String, values: Seq[Any]): Future[QueryResult] = { this.validateIsReadyForQuery() + val totalParameters = query.foldLeft(0) { (acc, c) => if ( c == '?' ) acc + 1 else acc } + if ( values.length != totalParameters ) { + throw new InsufficientParametersException(totalParameters, values) + } val promise = Promise[QueryResult] this.queryPromise = promise this.connectionHandler.write(new PreparedStatementMessage(query, values)) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 9aa6203c..70d74950 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -22,6 +22,7 @@ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration import java.util.concurrent.TimeUnit import io.netty.util.CharsetUtil +import com.github.mauricio.async.db.exceptions.InsufficientParametersException class QuerySpec extends Specification with ConnectionHelper { @@ -184,7 +185,18 @@ class QuerySpec extends Specification with ConnectionHelper { val preparedRows = executePreparedStatement(connection, select).rows.get preparedRows(0)("bit_column") === Array(0,0,-128) + } + + } + "fail if number of args required is different than the number of provided parameters" in { + + withConnection { + connection => + executePreparedStatement( + connection, + "select * from some_table where c = ? and b = ?", + "one", "two", "three") must throwAn[InsufficientParametersException] } } 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 ecd545de..f442950b 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 @@ -18,7 +18,7 @@ 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.ConnectionStillRunningQueryException +import com.github.mauricio.async.db.exceptions.{InsufficientParametersException, ConnectionStillRunningQueryException} import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler} import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, PostgreSQLColumnEncoderRegistry} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 53d8f944..d81774f1 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -17,9 +17,9 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification -import com.github.mauricio.async.db.postgresql.exceptions.InsufficientParametersException import org.joda.time.{DateTime, LocalDate} import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.exceptions.InsufficientParametersException class PreparedStatementSpec extends Specification with DatabaseTestHelper { From 76d2c8987b1db35c73a27e6337be5a3e85f934e6 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Sep 2013 20:13:56 -0300 Subject: [PATCH 169/357] Removing unused import --- .../async/db/exceptions/InsufficientParametersException.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/InsufficientParametersException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/InsufficientParametersException.scala index 41746dc8..7a9054ee 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/InsufficientParametersException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/InsufficientParametersException.scala @@ -16,8 +16,6 @@ package com.github.mauricio.async.db.exceptions -import com.github.mauricio.async.db.exceptions.DatabaseException - /** * * Raised when the user gives more or less parameters than the query takes. Each parameter is a ? From c9e4b11a535e98702000b3957d6f0f6992e1c5f3 Mon Sep 17 00:00:00 2001 From: Artem Date: Sun, 22 Sep 2013 15:17:06 +0400 Subject: [PATCH 170/357] Fix the URLParser import in the example --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 8c713d80..3118b244 100644 --- a/README.markdown +++ b/README.markdown @@ -162,8 +162,8 @@ it [here](http://mauricio.github.io/2013/04/29/async-database-access-with-postgr In short, what you would usually do is: ```scala import com.github.mauricio.async.db.postgresql.PostgreSQLConnection +import com.github.mauricio.async.db.postgresql.util.URLParser import com.github.mauricio.async.db.util.ExecutorServiceUtils.CachedExecutionContext -import com.github.mauricio.async.db.util.URLParser import com.github.mauricio.async.db.{RowData, QueryResult, Connection} import scala.concurrent.duration._ import scala.concurrent.{Await, Future} From 6fcd6c9f0b4133571b9cb400bd4b27c335fcd334 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 24 Sep 2013 21:49:01 -0300 Subject: [PATCH 171/357] Changing parameters counter to use .count --- .../com/github/mauricio/async/db/mysql/MySQLConnection.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index fa4b046b..851615da 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -234,7 +234,7 @@ class MySQLConnection( def sendPreparedStatement(query: String, values: Seq[Any]): Future[QueryResult] = { this.validateIsReadyForQuery() - val totalParameters = query.foldLeft(0) { (acc, c) => if ( c == '?' ) acc + 1 else acc } + val totalParameters = query.count( _ == '?') if ( values.length != totalParameters ) { throw new InsufficientParametersException(totalParameters, values) } From 8a10f23149d7c5ab9125e4ca669b4044850ce388 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 24 Sep 2013 21:51:44 -0300 Subject: [PATCH 172/357] Closing version 0.2.8 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1b304f4..561ec0d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.8 - 2013-09-24 + +* Validate the number of parameters for prepared statements - fixes #47 +* Adding support for MySQL BIT type - fixes #48 +* Use the rows count as the affected rows for MySQL also - fixes #46 + ## 0.2.7 - 2013-09-09 * Upgrading Netty to 4.0.9 From 9ab719ec66b0bc29d25f40c6eaa69bc8a53e399a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 24 Sep 2013 21:56:53 -0300 Subject: [PATCH 173/357] Tagging 0.2.8 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index b2fd6eac..ab87eb93 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.8-SNAPSHOT" + val commonVersion = "0.2.8" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" From 86c2b3fcdb58105c80b41794f6551da539d2b165 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 24 Sep 2013 22:00:04 -0300 Subject: [PATCH 174/357] Starting out 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 3118b244..602a54f7 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.7" +"com.github.mauricio" %% "postgresql-async" % "0.2.8" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.7 + 0.2.8 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.7" +"com.github.mauricio" %% "mysql-async" % "0.2.8" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.7 + 0.2.8 ``` diff --git a/project/Build.scala b/project/Build.scala index ab87eb93..a2369c58 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.8" + val commonVersion = "0.2.9-SNAPSHOT" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" From 904286f6d90ed50dd7634f3984d352c78f61a8d0 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Sun, 6 Oct 2013 23:03:22 +0900 Subject: [PATCH 175/357] Test case for "postgresql-async cannnot parse timestamp value which is set by current_timestamp" --- .../async/db/postgresql/TimeAndDateSpec.scala | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index f1e2a7a0..490f50e2 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -120,8 +120,9 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { val dateTime = rows(0)("moment").asInstanceOf[DateTime] - dateTime.getZone.toTimeZone.getRawOffset === -10800000 - + // Note: Since this assertion depends on Brazil locale, I think epoch time assertion is preferred + // dateTime.getZone.toTimeZone.getRawOffset === -10800000 + dateTime.getMillis === 915779106000L } } @@ -150,13 +151,53 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { val dateTime = rows(0)("moment").asInstanceOf[DateTime] - dateTime.getZone.toTimeZone.getRawOffset === -10800000 + // Note: Since this assertion depends on Brazil locale, I think epoch time assertion is preferred + // dateTime.getZone.toTimeZone.getRawOffset === -10800000 + dateTime.getMillis must be_>=(915779106000L) + dateTime.getMillis must be_<(915779107000L) } } } + /* TODO postgresql-async cannnot parse timestamp value which is set by current_timestamp + +[info] ! support current_timestamp with timezone +[error] IllegalArgumentException: Invalid format: "2013-10-06 22:56:21.459635+09" is malformed at ".459635+09" (DateTimeFormatter.java:871) +[error] org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:871) +[error] com.github.mauricio.async.db.postgresql.column.PostgreSQLTimestampEncoderDecoder$.decode(PostgreSQLTimestampEncoderDecoder.scala:79) +[error] com.github.mauricio.async.db.postgresql.column.PostgreSQLColumnDecoderRegistry.decode(PostgreSQLColumnDecoderRegistry.scala:49) +[error] com.github.mauricio.async.db.postgresql.PostgreSQLConnection.onDataRow(PostgreSQLConnection.scala:220) +[error] com.github.mauricio.async.db.postgresql.codec.PostgreSQLConnectionHandler.channelRead0(PostgreSQLConnectionHandler.scala:149) +[error] io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:98) +[error] io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337) + */ + "support current_timestamp with timezone" in { + withHandler { + handler => + + val millis = System.currentTimeMillis + + val create = """CREATE TEMP TABLE messages + ( + id bigserial NOT NULL, + moment timestamp with time zone NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + )""" + + executeDdl(handler, create) + executeQuery(handler, "INSERT INTO messages (moment) VALUES (current_timestamp)") + val rows = executePreparedStatement(handler, "SELECT * FROM messages").rows.get + + rows.length === 1 + + val dateTime = rows(0)("moment").asInstanceOf[DateTime] + + dateTime.getMillis must be_>=(millis) + } + } + } } From 98a1bb8e28e137217154792086c117161750ed75 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Tue, 29 Oct 2013 17:37:51 -0400 Subject: [PATCH 176/357] Add AsyncObjectPool.use to combine take and giveBack --- .../async/db/pool/AsyncObjectPool.scala | 15 ++++++++++++++ .../async/db/pool/ConnectionPool.scala | 20 ++++--------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala index f3f42680..39179737 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala @@ -62,4 +62,19 @@ trait AsyncObjectPool[T] { def close : Future[AsyncObjectPool[T]] + /** + * + * Retrieve and use an object from the pool for a single computation, returning it when the operation completes. + * + * @param f function that uses the object + * @return f wrapped with take and giveBack + */ + + def use[A](f : T => Future[A])(implicit executionContext : scala.concurrent.ExecutionContext) : Future[A] = + take.flatMap { item => + f(item).andThen { case _ => + giveBack(item) + } + } + } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala index 4f55fb28..be0c2a86 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala @@ -78,14 +78,8 @@ class ConnectionPool[T <: Connection]( * @return */ - def sendQuery(query: String): Future[QueryResult] = { - this.take.flatMap( { - connection => - connection.sendQuery(query).andThen( { - case c => this.giveBack(connection) - })(executionContext) - })(executionContext) - } + def sendQuery(query: String): Future[QueryResult] = + this.use(_.sendQuery(query))(executionContext) /** * @@ -98,13 +92,7 @@ class ConnectionPool[T <: Connection]( * @return */ - def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { - this.take.flatMap( { - connection => - connection.sendPreparedStatement(query, values).andThen( { - case c => this.giveBack(connection) - })(executionContext) - })(executionContext) - } + def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = + this.use(_.sendPreparedStatement(query, values))(executionContext) } From 64ffaf042755457989f6fd81afbf68de0b16d3c8 Mon Sep 17 00:00:00 2001 From: kxbmap Date: Thu, 31 Oct 2013 03:51:56 +0900 Subject: [PATCH 177/357] Add support for MySQL BINARY/VARBINARY types --- .../db/mysql/codec/DecoderRegistry.scala | 14 +++--- .../mauricio/async/db/mysql/QuerySpec.scala | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala index 246c5a8d..798f9231 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/DecoderRegistry.scala @@ -36,13 +36,13 @@ class DecoderRegistry(charset: Charset) { (columnType: @switch) match { case ColumnTypes.FIELD_TYPE_VARCHAR | - ColumnTypes.FIELD_TYPE_VAR_STRING | - ColumnTypes.FIELD_TYPE_STRING | ColumnTypes.FIELD_TYPE_ENUM => this.stringDecoder case ColumnTypes.FIELD_TYPE_BLOB | ColumnTypes.FIELD_TYPE_LONG_BLOB | ColumnTypes.FIELD_TYPE_MEDIUM_BLOB | - ColumnTypes.FIELD_TYPE_TINY_BLOB => { + ColumnTypes.FIELD_TYPE_TINY_BLOB | + ColumnTypes.FIELD_TYPE_VAR_STRING | + ColumnTypes.FIELD_TYPE_STRING => { if (charsetCode == CharsetMapper.Binary) { ByteArrayDecoder } else { @@ -83,13 +83,13 @@ class DecoderRegistry(charset: Charset) { case ColumnTypes.FIELD_TYPE_SHORT => ShortEncoderDecoder case ColumnTypes.FIELD_TYPE_TIME => TextTimeDecoder case ColumnTypes.FIELD_TYPE_TINY => TextByteDecoder - case ColumnTypes.FIELD_TYPE_VAR_STRING | - ColumnTypes.FIELD_TYPE_VARCHAR | - ColumnTypes.FIELD_TYPE_STRING | + case ColumnTypes.FIELD_TYPE_VARCHAR | ColumnTypes.FIELD_TYPE_ENUM => StringEncoderDecoder case ColumnTypes.FIELD_TYPE_YEAR => ShortEncoderDecoder case ColumnTypes.FIELD_TYPE_BIT => ByteArrayColumnDecoder - case ColumnTypes.FIELD_TYPE_BLOB => { + case ColumnTypes.FIELD_TYPE_BLOB | + ColumnTypes.FIELD_TYPE_VAR_STRING | + ColumnTypes.FIELD_TYPE_STRING => { if (charsetCode == CharsetMapper.Binary) { ByteArrayColumnDecoder } else { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 70d74950..9c169dde 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -189,6 +189,55 @@ class QuerySpec extends Specification with ConnectionHelper { } + "support BINARY type" in { + + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL AUTO_INCREMENT, + | binary_column BINARY(20), + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (binary_column) VALUES (?)" + val select = "SELECT * FROM POSTS" + val bytes = (1 to 10).map(_.toByte).toArray + val padding = Array.fill[Byte](10)(0) + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, bytes) + val row = executeQuery(connection, select).rows.get(0) + row("id") === 1 + row("binary_column") === bytes ++ padding + } + + } + + "support VARBINARY type" in { + + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL AUTO_INCREMENT, + | varbinary_column VARBINARY(20), + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (varbinary_column) VALUES (?)" + val select = "SELECT * FROM POSTS" + val bytes = (1 to 10).map(_.toByte).toArray + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, bytes) + val row = executeQuery(connection, select).rows.get(0) + row("id") === 1 + row("varbinary_column") === bytes + } + + } + "fail if number of args required is different than the number of provided parameters" in { withConnection { From 48d327376041ee1b57c0c5a5ebc6ac6a256a6799 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 31 Oct 2013 02:03:04 -0300 Subject: [PATCH 178/357] Fixing issue with current_date since it comes in without a millis count --- Procfile | 2 +- .../PostgreSQLTimestampEncoderDecoder.scala | 16 ++++++++++------ .../messages/backend/PostgreSQLColumnData.scala | 16 ++++++++-------- .../async/db/postgresql/TimeAndDateSpec.scala | 14 +------------- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/Procfile b/Procfile index e30234e4..6c1b0717 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -postgresql: /usr/local/Cellar/postgresql/9.2.4/bin/postgres -D /Users/mauricio/databases/postgresql +postgresql: postgres -D /Users/mauricio/databases/postgresql mysql: mysqld --log-warnings --console \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala index 9ef83b2c..43f9fdc7 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala @@ -61,17 +61,13 @@ object PostgreSQLTimestampEncoderDecoder extends ColumnEncoderDecoder { val format = columnType.dataType match { case ColumnTypes.Timestamp | ColumnTypes.TimestampArray | ColumnTypes.TimestampWithTimezoneArray => { - if ( text.contains(".") ) { - internalFormatters(5) - } else { - internalFormatterWithoutSeconds - } + selectFormatter(text) } case ColumnTypes.TimestampWithTimezone => { if ( columnType.dataTypeModifier > 0 ) { internalFormatters(columnType.dataTypeModifier - 1) } else { - internalFormatterWithoutSeconds + selectFormatter(text) } } } @@ -79,6 +75,14 @@ object PostgreSQLTimestampEncoderDecoder extends ColumnEncoderDecoder { format.parseDateTime(text) } + private def selectFormatter( text : String ) = { + if ( text.contains(".") ) { + internalFormatters(5) + } else { + internalFormatterWithoutSeconds + } + } + override def decode(value : String) : Any = throw new UnsupportedOperationException("this method should not have been called") override def encode(value: Any): String = { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala index 029ddce7..ab76d945 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/PostgreSQLColumnData.scala @@ -18,11 +18,11 @@ package com.github.mauricio.async.db.postgresql.messages.backend import com.github.mauricio.async.db.general.ColumnData -class PostgreSQLColumnData( - val name: String, - val tableObjectId: Int, - val columnNumber: Int, - val dataType: Int, - val dataTypeSize: Long, - val dataTypeModifier: Int, - val fieldFormat: Int) extends ColumnData \ No newline at end of file +case class PostgreSQLColumnData( + name: String, + tableObjectId: Int, + columnNumber: Int, + dataType: Int, + dataTypeSize: Long, + dataTypeModifier: Int, + fieldFormat: Int) extends ColumnData \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index 490f50e2..ba798a0e 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -161,18 +161,6 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { } } - /* TODO postgresql-async cannnot parse timestamp value which is set by current_timestamp - -[info] ! support current_timestamp with timezone -[error] IllegalArgumentException: Invalid format: "2013-10-06 22:56:21.459635+09" is malformed at ".459635+09" (DateTimeFormatter.java:871) -[error] org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:871) -[error] com.github.mauricio.async.db.postgresql.column.PostgreSQLTimestampEncoderDecoder$.decode(PostgreSQLTimestampEncoderDecoder.scala:79) -[error] com.github.mauricio.async.db.postgresql.column.PostgreSQLColumnDecoderRegistry.decode(PostgreSQLColumnDecoderRegistry.scala:49) -[error] com.github.mauricio.async.db.postgresql.PostgreSQLConnection.onDataRow(PostgreSQLConnection.scala:220) -[error] com.github.mauricio.async.db.postgresql.codec.PostgreSQLConnectionHandler.channelRead0(PostgreSQLConnectionHandler.scala:149) -[error] io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:98) -[error] io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337) - */ "support current_timestamp with timezone" in { withHandler { handler => @@ -194,7 +182,7 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { val dateTime = rows(0)("moment").asInstanceOf[DateTime] - dateTime.getMillis must be_>=(millis) + dateTime.getMillis must beCloseTo(millis, 100) } } From 32648eacae32af5bd639f70b159b53432fa25069 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 1 Nov 2013 23:16:45 -0300 Subject: [PATCH 179/357] Including spec with an explicit timezone setting --- .../codec/PostgreSQLConnectionHandler.scala | 3 +-- .../db/postgresql/DatabaseTestHelper.scala | 11 +++++++++++ .../async/db/postgresql/TimeAndDateSpec.scala | 17 +++++++++++++++++ project/Build.scala | 5 +++-- script/prepare_build.sh | 1 + 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index b5356ebd..3157b7a4 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -138,6 +138,7 @@ class PostgreSQLConnectionHandler case ServerMessage.BindComplete => { } case ServerMessage.Authentication => { + log.debug("Authentication response received {}", m) connectionDelegate.onAuthenticationResponse(m.asInstanceOf[AuthenticationMessage]) } case ServerMessage.CommandComplete => { @@ -164,13 +165,11 @@ class PostgreSQLConnectionHandler connectionDelegate.onParameterStatus(m.asInstanceOf[ParameterStatusMessage]) } case ServerMessage.ParseComplete => { - log.debug("Parse complete received - {}", m) } case ServerMessage.ReadyForQuery => { connectionDelegate.onReadyForQuery() } case ServerMessage.RowDescription => { - log.debug("Row description received - {}", m) connectionDelegate.onRowDescription(m.asInstanceOf[RowDescriptionMessage]) } case _ => { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index 60a689e6..40b35549 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -32,6 +32,8 @@ trait DatabaseTestHelper { def databaseName = Some("netty_driver_test") + def timeTestDatabase = Some("netty_driver_time_test") + def databasePort = 5432 def defaultConfiguration = new Configuration( @@ -39,10 +41,19 @@ trait DatabaseTestHelper { username = "postgres", database = databaseName) + def timeTestConfiguration = new Configuration( + port = databasePort, + username = "postgres", + database = timeTestDatabase) + def withHandler[T](fn: (PostgreSQLConnection) => T): T = { withHandler(this.defaultConfiguration, fn) } + def withTimeHandler[T](fn: (PostgreSQLConnection) => T): T = { + withHandler(this.timeTestConfiguration, fn) + } + def withHandler[T](configuration: Configuration, fn: (PostgreSQLConnection) => T): T = { val handler = new PostgreSQLConnection(configuration) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index ba798a0e..f95b3f1a 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -186,6 +186,23 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { } } + "handle a change in timezone inside the connection" in { + + withTimeHandler { + conn => + val date1 = new DateTime(2190319) + + await(conn.sendPreparedStatement(s"alter database ${databaseName.get} set timezone to 'GMT'")) + await(conn.sendPreparedStatement("CREATE TEMP TABLE TEST(T TIMESTAMP)")) + await(conn.sendPreparedStatement("INSERT INTO TEST(T) VALUES(?)", Seq(date1))) + val result = await(conn.sendPreparedStatement("SELECT T FROM TEST")) + val date2 = result.rows.get.head(0) + + date2 === date1 + } + + } + } } diff --git a/project/Build.scala b/project/Build.scala index a2369c58..ac0d4950 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -56,7 +56,8 @@ object Configuration { "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", "org.scala-lang" % "scala-library" % "2.10.2", - "io.netty" % "netty-all" % "4.0.9.Final", + "io.netty" % "netty-all" % "4.0.11.Final", + "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, logbackDependency ) @@ -74,7 +75,7 @@ object Configuration { :+ "-feature" , scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), - scalaVersion := "2.10.2", + scalaVersion := "2.10.3", javacOptions := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8"), organization := "com.github.mauricio", version := commonVersion, diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 3b3a3546..091c6ee7 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -7,6 +7,7 @@ mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async'@'localhost' IDENT echo "preparing postgresql configs" psql -c 'create database netty_driver_test;' -U postgres +psql -c 'create database netty_driver_time_test;' -U postgres psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres From 944aa87f6c6d66336143101145d168612817b901 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Wed, 30 Oct 2013 22:03:20 -0400 Subject: [PATCH 180/357] Add support for postgres interval type as Period Internally postgres stores (months, days, seconds), for which it may make sense to use Duration instead. Unfortunately, it externally presents an interface that looks more like Period, so this approach makes parsing easier. --- .../db/postgresql/column/ColumnTypes.scala | 4 +- .../PostgreSQLColumnDecoderRegistry.scala | 4 + .../PostgreSQLColumnEncoderRegistry.scala | 5 +- .../PostgreSQLIntervalEncoderDecoder.scala | 119 ++++++++++++++++++ .../async/db/postgresql/TimeAndDateSpec.scala | 17 ++- .../db/postgresql/column/IntervalSpec.scala | 53 ++++++++ 6 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala 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 675ad32f..945b3019 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 @@ -51,6 +51,8 @@ object ColumnTypes { final val TimeArray = 1183 final val TimeWithTimezone = 1266 final val TimeWithTimezoneArray = 1270 + final val Interval = 1186 + final val IntervalArray = 1187 final val Boolean = 16 final val BooleanArray = 1000 @@ -120,4 +122,4 @@ object ColumnTypes { public static final int XML = 142; public static final int XML_ARRAY = 143; -*/ \ No newline at end of file +*/ 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 d5da1513..6b6ea7dd 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 @@ -44,6 +44,7 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e private final val dateArrayDecoder = new ArrayDecoder(DateEncoderDecoder) private final val timeArrayDecoder = new ArrayDecoder(TimeEncoderDecoder.Instance) private final val timeWithTimestampArrayDecoder = new ArrayDecoder(TimeWithTimezoneEncoderDecoder) + private final val intervalArrayDecoder = new ArrayDecoder(PostgreSQLIntervalEncoderDecoder) override def decode(kind: ColumnData, value: ByteBuf, charset: Charset): Any = { decoderFor(kind.dataType).decode(kind, value, charset) @@ -99,6 +100,9 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e case TimeWithTimezone => TimeWithTimezoneEncoderDecoder case TimeWithTimezoneArray => this.timeWithTimestampArrayDecoder + case Interval => PostgreSQLIntervalEncoderDecoder + case IntervalArray => this.intervalArrayDecoder + case OIDArray => this.stringArrayDecoder case MoneyArray => this.stringArrayDecoder case NameArray => this.stringArrayDecoder 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 b5bd7ec1..f6b3a710 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 @@ -55,6 +55,9 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[ReadableDateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[ReadableInstant] -> (DateEncoderDecoder -> ColumnTypes.Date), + classOf[ReadablePeriod] -> (PostgreSQLIntervalEncoderDecoder -> ColumnTypes.Interval), + classOf[ReadableDuration] -> (PostgreSQLIntervalEncoderDecoder -> ColumnTypes.Interval), + classOf[java.util.Date] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.sql.Date] -> ( DateEncoderDecoder -> ColumnTypes.Date ), classOf[java.sql.Time] -> ( SQLTimeEncoder -> ColumnTypes.Time ), @@ -173,4 +176,4 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { } } } -} \ No newline at end of file +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala new file mode 100644 index 00000000..4b98b737 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala @@ -0,0 +1,119 @@ +/* + * Copyright 2013 Maurício Linhares + * Copyright 2013 Dylan Simon + * + * 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.column + +import com.github.mauricio.async.db.column.ColumnEncoderDecoder +import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException +import com.github.mauricio.async.db.util.Log +import org.joda.time.{Period, ReadablePeriod, ReadableDuration} +import org.joda.time.format.{ISOPeriodFormat, PeriodFormatterBuilder} + +object PostgreSQLIntervalEncoderDecoder extends ColumnEncoderDecoder { + + private val log = Log.getByName(this.getClass.getName) + + /* Postgres accepts all ISO8601 formats. */ + private val formatter = ISOPeriodFormat.standard + + override def encode(value : Any) : String = { + value match { + case t : ReadablePeriod => formatter.print(t) + case t : ReadableDuration => t.toString // defaults to ISO8601 + case _ => throw new DateEncoderNotAvailableException(value) + } + } + + /* these should only be used for parsing: */ + private def postgresYMDBuilder(builder : PeriodFormatterBuilder) = builder + .appendYears .appendSuffix(" year", " years").appendSeparator(" ") + .appendMonths .appendSuffix(" mon", " mons" ).appendSeparator(" ") + .appendDays .appendSuffix(" day", " days" ).appendSeparator(" ") + + private val postgres_verboseParser = + postgresYMDBuilder(new PeriodFormatterBuilder().appendLiteral("@ ")) + .appendHours .appendSuffix(" hour", " hours").appendSeparator(" ") + .appendMinutes.appendSuffix(" min", " mins" ).appendSeparator(" ") + .appendSecondsWithOptionalMillis.appendSuffix(" sec", " secs") + .toFormatter + + private def postgresHMSBuilder(builder : PeriodFormatterBuilder) = builder + // .printZeroAlways // really all-or-nothing + .rejectSignedValues(true) // XXX: sign should apply to all + .appendHours .appendSeparator(":") + .appendMinutes.appendSeparator(":") + .appendSecondsWithOptionalMillis + + private val secsParser = new PeriodFormatterBuilder() + .appendSecondsWithOptionalMillis + .toFormatter + + private val postgresParser = + postgresHMSBuilder(postgresYMDBuilder(new PeriodFormatterBuilder())) + .toFormatter + + /* These sql_standard parsers don't handle negative signs correctly. */ + private def sqlDTBuilder(builder : PeriodFormatterBuilder) = + postgresHMSBuilder(builder + .appendDays.appendSeparator(" ")) + + private val sqlDTParser = + sqlDTBuilder(new PeriodFormatterBuilder()) + .toFormatter + + private val sqlParser = + sqlDTBuilder(new PeriodFormatterBuilder() + .printZeroAlways + .rejectSignedValues(true) // XXX: sign should apply to both + .appendYears.appendSeparator("-").appendMonths + .rejectSignedValues(false) + .printZeroNever + .appendSeparator(" ")) + .toFormatter + + /* This supports all positive intervals, and intervalstyle of postgres_verbose, and iso_8601 perfectly. + * If intervalstyle is set to postgres or sql_standard, some negative intervals may be rejected. + */ + def decode(value : String) : Period = { + if (value.isEmpty) /* huh? */ + Period.ZERO + else { + val format = ( + if (value(0).equals('P')) /* iso_8601 */ + formatter + else if (value.startsWith("@ ")) + postgres_verboseParser + else { + /* try to guess based on what comes after the first number */ + val i = value.indexWhere(!_.isDigit, if ("-+".contains(value(0))) 1 else 0) + if (i <= 0 || value(i).equals('.')) /* just a number */ + secsParser /* postgres treats this as seconds */ + else if (value(i).equals('-')) /* sql_standard: Y-M */ + sqlParser + else if (value(i).equals(' ') && i+1 < value.length && value(i+1).isDigit) /* sql_standard: D H:M:S */ + sqlDTParser + else + postgresParser + } + ) + if (value.endsWith(" ago")) /* only really applies to postgres_verbose, but shouldn't hurt */ + format.parsePeriod(value.stripSuffix(" ago")).negated + else + format.parsePeriod(value) + } + } +} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index f95b3f1a..6074a67e 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification -import org.joda.time.{LocalTime, DateTime} +import org.joda.time.{LocalTime, DateTime, Period} class TimeAndDateSpec extends Specification with DatabaseTestHelper { @@ -200,7 +200,22 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { date2 === date1 } + } + + "support intervals" in { + withHandler { + handler => + executeDdl(handler, "CREATE TEMP TABLE intervals (duration interval NOT NULL)") + + val p = new Period(1,2,0,4,5,6,7,8) /* postgres normalizes weeks */ + executePreparedStatement(handler, "INSERT INTO intervals (duration) VALUES (?)", Array(p)) + val rows = executeQuery(handler, "SELECT duration FROM intervals").rows.get + + rows.length === 1 + + rows(0)(0) === p + } } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala new file mode 100644 index 00000000..011836a2 --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala @@ -0,0 +1,53 @@ +/* + * Copyright 2013 Maurício Linhares + * Copyright 2013 Dylan Simon + * + * 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.column + +import org.specs2.mutable.Specification + +class IntervalSpec extends Specification { + + "interval encoder/decoder" should { + + def decode(s : String) : Any = PostgreSQLIntervalEncoderDecoder.decode(s) + def encode(i : Any) : String = PostgreSQLIntervalEncoderDecoder.encode(i) + def both(s : String) : String = encode(decode(s)) + + "parse and encode example intervals" in { + Seq("1-2", "1 year 2 mons", "@ 1 year 2 mons", "@ 1 year 2 mons", "P1Y2M") forall { + both(_) === "P1Y2M" + } + Seq("3 4:05:06", "3 days 04:05:06", "@ 3 days 4 hours 5 mins 6 secs", "P3DT4H5M6S") forall { + both(_) === "P3DT4H5M6S" + } + Seq("1-2 +3 4:05:06", "1 year 2 mons +3 days 04:05:06", "@ 1 year 2 mons 3 days 4 hours 5 mins 6 secs", "P1Y2M3DT4H5M6S") forall { + both(_) === "P1Y2M3DT4H5M6S" + } + Seq("@ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago", "P-1Y-2M3DT-4H-5M-6S") forall { + both(_) === "P-1Y-2M3DT-4H-5M-6S" + } + } + + "parse and encode example intervals" in { + Seq("-1-2 +3 -4:05:06", "-1 year -2 mons +3 days -04:05:06") forall { + both(_) === "P-1Y-2M3DT-4H-5M-6S" + } + }.pendingUntilFixed("with mixed/grouped negations") + + } + +} From 71514d5b389f934647685c05f9461de6063a9fef Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Wed, 30 Oct 2013 23:07:24 -0400 Subject: [PATCH 181/357] Add support for simple negative HMS intervals --- .../PostgreSQLIntervalEncoderDecoder.scala | 16 +++++++++------- .../db/postgresql/column/IntervalSpec.scala | 2 ++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala index 4b98b737..3f25ad76 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLIntervalEncoderDecoder.scala @@ -54,12 +54,12 @@ object PostgreSQLIntervalEncoderDecoder extends ColumnEncoderDecoder { private def postgresHMSBuilder(builder : PeriodFormatterBuilder) = builder // .printZeroAlways // really all-or-nothing .rejectSignedValues(true) // XXX: sign should apply to all - .appendHours .appendSeparator(":") - .appendMinutes.appendSeparator(":") + .appendHours .appendSuffix(":") + .appendMinutes.appendSuffix(":") .appendSecondsWithOptionalMillis - private val secsParser = new PeriodFormatterBuilder() - .appendSecondsWithOptionalMillis + private val hmsParser = + postgresHMSBuilder(new PeriodFormatterBuilder()) .toFormatter private val postgresParser = @@ -100,8 +100,8 @@ object PostgreSQLIntervalEncoderDecoder extends ColumnEncoderDecoder { else { /* try to guess based on what comes after the first number */ val i = value.indexWhere(!_.isDigit, if ("-+".contains(value(0))) 1 else 0) - if (i <= 0 || value(i).equals('.')) /* just a number */ - secsParser /* postgres treats this as seconds */ + if (i < 0 || ":.".contains(value(i))) /* simple HMS (to support group negation) */ + hmsParser else if (value(i).equals('-')) /* sql_standard: Y-M */ sqlParser else if (value(i).equals(' ') && i+1 < value.length && value(i+1).isDigit) /* sql_standard: D H:M:S */ @@ -110,7 +110,9 @@ object PostgreSQLIntervalEncoderDecoder extends ColumnEncoderDecoder { postgresParser } ) - if (value.endsWith(" ago")) /* only really applies to postgres_verbose, but shouldn't hurt */ + if ((format eq hmsParser) && value(0).equals('-')) + format.parsePeriod(value.substring(1)).negated + else if (value.endsWith(" ago")) /* only really applies to postgres_verbose, but shouldn't hurt */ format.parsePeriod(value.stripSuffix(" ago")).negated else format.parsePeriod(value) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala index 011836a2..491e2403 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/IntervalSpec.scala @@ -40,6 +40,8 @@ class IntervalSpec extends Specification { Seq("@ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago", "P-1Y-2M3DT-4H-5M-6S") forall { both(_) === "P-1Y-2M3DT-4H-5M-6S" } + both("-1.234") === "PT-1.234S" + both("-4:05:06") === "PT-4H-5M-6S" } "parse and encode example intervals" in { From 23f407cdf1b0d0557a6f0e6e9421ed3339ab93f9 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 2 Nov 2013 00:30:50 -0300 Subject: [PATCH 182/357] Make sure a LocalDateTime is returned for timestamps without timezones (as the driver documentation states) - fixes #52 --- .../db/column/TimestampEncoderDecoder.scala | 9 ++++---- .../TimestampWithTimezoneEncoderDecoder.scala | 5 +++++ .../PostgreSQLColumnEncoderRegistry.scala | 1 + .../PostgreSQLTimestampEncoderDecoder.scala | 18 +++++++++------- .../async/db/postgresql/TimeAndDateSpec.scala | 21 ++++++++++++++++--- project/Build.scala | 2 +- script/prepare_build.sh | 1 + 7 files changed, 41 insertions(+), 16 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala index 0613dcf2..78719082 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala @@ -19,8 +19,8 @@ package com.github.mauricio.async.db.column import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException import java.sql.Timestamp import java.util.{Calendar, Date} -import org.joda.time.format.{DateTimeFormatter, DateTimeFormatterBuilder, DateTimeFormat} -import org.joda.time.{ReadableDateTime, DateTime} +import org.joda.time._ +import org.joda.time.format.DateTimeFormatterBuilder object TimestampEncoderDecoder { val Instance = new TimestampEncoderDecoder() @@ -41,8 +41,8 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { def formatter = format - override def decode(value: String): DateTime = { - formatter.parseDateTime(value) + override def decode(value: String): Any = { + formatter.parseLocalDateTime(value) } override def encode(value: Any): String = { @@ -50,6 +50,7 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { case t: Timestamp => this.formatter.print(new DateTime(t)) case t: Date => this.formatter.print(new DateTime(t)) case t: Calendar => this.formatter.print(new DateTime(t)) + case t: LocalDateTime => this.formatter.print(t) case t: ReadableDateTime => this.formatter.print(t) case _ => throw new DateEncoderNotAvailableException(value) } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampWithTimezoneEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampWithTimezoneEncoderDecoder.scala index 181c502e..258f23ee 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampWithTimezoneEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampWithTimezoneEncoderDecoder.scala @@ -16,6 +16,7 @@ package com.github.mauricio.async.db.column +import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat object TimestampWithTimezoneEncoderDecoder extends TimestampEncoderDecoder { @@ -24,4 +25,8 @@ object TimestampWithTimezoneEncoderDecoder extends TimestampEncoderDecoder { override def formatter = format + override def decode(value: String): Any = { + formatter.parseDateTime(value) + } + } 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 f6b3a710..2ec2c1a4 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 @@ -51,6 +51,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[java.math.BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), classOf[LocalDate] -> ( DateEncoderDecoder -> ColumnTypes.Date ), + classOf[LocalDateTime] -> (TimestampEncoderDecoder.Instance -> ColumnTypes.TimestampWithTimezone), classOf[DateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[ReadableDateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[ReadableInstant] -> (DateEncoderDecoder -> ColumnTypes.Date), diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala index 43f9fdc7..7c6c426d 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLTimestampEncoderDecoder.scala @@ -25,8 +25,8 @@ import io.netty.buffer.ByteBuf import java.nio.charset.Charset import java.sql.Timestamp import java.util.{Calendar, Date} +import org.joda.time._ import org.joda.time.format.DateTimeFormatterBuilder -import org.joda.time.{ReadableDateTime, DateTime} object PostgreSQLTimestampEncoderDecoder extends ColumnEncoderDecoder { @@ -59,20 +59,21 @@ object PostgreSQLTimestampEncoderDecoder extends ColumnEncoderDecoder { val columnType = kind.asInstanceOf[PostgreSQLColumnData] - val format = columnType.dataType match { - case ColumnTypes.Timestamp | ColumnTypes.TimestampArray | ColumnTypes.TimestampWithTimezoneArray => { - selectFormatter(text) + columnType.dataType match { + case ColumnTypes.Timestamp | ColumnTypes.TimestampArray => { + selectFormatter(text).parseLocalDateTime(text) + } + case ColumnTypes.TimestampWithTimezoneArray => { + selectFormatter(text).parseDateTime(text) } case ColumnTypes.TimestampWithTimezone => { if ( columnType.dataTypeModifier > 0 ) { - internalFormatters(columnType.dataTypeModifier - 1) + internalFormatters(columnType.dataTypeModifier - 1).parseDateTime(text) } else { - selectFormatter(text) + selectFormatter(text).parseDateTime(text) } } } - - format.parseDateTime(text) } private def selectFormatter( text : String ) = { @@ -90,6 +91,7 @@ object PostgreSQLTimestampEncoderDecoder extends ColumnEncoderDecoder { case t: Timestamp => this.formatter.print(new DateTime(t)) case t: Date => this.formatter.print(new DateTime(t)) case t: Calendar => this.formatter.print(new DateTime(t)) + case t: LocalDateTime => this.formatter.print(t) case t: ReadableDateTime => this.formatter.print(t) case _ => throw new DateEncoderNotAvailableException(value) } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index 6074a67e..9c40e5e7 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification -import org.joda.time.{LocalTime, DateTime, Period} +import org.joda.time._ class TimeAndDateSpec extends Specification with DatabaseTestHelper { @@ -186,18 +186,33 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { } } - "handle a change in timezone inside the connection" in { + "handle sending a time with timezone and return a LocalDateTime for a timestamp without timezone column" in { withTimeHandler { conn => val date1 = new DateTime(2190319) - await(conn.sendPreparedStatement(s"alter database ${databaseName.get} set timezone to 'GMT'")) await(conn.sendPreparedStatement("CREATE TEMP TABLE TEST(T TIMESTAMP)")) await(conn.sendPreparedStatement("INSERT INTO TEST(T) VALUES(?)", Seq(date1))) val result = await(conn.sendPreparedStatement("SELECT T FROM TEST")) val date2 = result.rows.get.head(0) + date2 === date1.toDateTime(DateTimeZone.UTC).toLocalDateTime + } + + } + + "handle sending a date with timezone and retrieving the date with the same time zone" in { + + withTimeHandler { + conn => + val date1 = new DateTime(2190319) + + await(conn.sendPreparedStatement("CREATE TEMP TABLE TEST(T TIMESTAMP WITH TIME ZONE)")) + await(conn.sendPreparedStatement("INSERT INTO TEST(T) VALUES(?)", Seq(date1))) + val result = await(conn.sendPreparedStatement("SELECT T FROM TEST")) + val date2 = result.rows.get.head(0) + date2 === date1 } } diff --git a/project/Build.scala b/project/Build.scala index ac0d4950..5d0bf5f6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -55,7 +55,7 @@ object Configuration { "org.slf4j" % "slf4j-api" % "1.7.5", "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", - "org.scala-lang" % "scala-library" % "2.10.2", + "org.scala-lang" % "scala-library" % "2.10.3", "io.netty" % "netty-all" % "4.0.11.Final", "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 091c6ee7..7e724fa6 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -8,6 +8,7 @@ echo "preparing postgresql configs" psql -c 'create database netty_driver_test;' -U postgres psql -c 'create database netty_driver_time_test;' -U postgres +psql -c "alter database netty_driver_time_test set timezone to 'GMT'" -U postgres psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres From 29659aac8f209332f7feb9e234c911192d760d2e Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Tue, 29 Oct 2013 21:04:43 -0400 Subject: [PATCH 183/357] Add Connection.inTransaction to wrap queries in a transaction block There is an unfortunate tension between the desired argument type for this function in plain Connections (=> Future[A]) and in ConnectionPools (T => Future[A]), and so it currently uses a compromise (Connection => Future[A]) which is a bit odd in the former case and too general in the latter. One option is to take this function out of Connection and put it on a new SimpleConnection trait, with a separate function on ConnectionPool. --- .../github/mauricio/async/db/Connection.scala | 21 +++++++++++++++++++ .../async/db/pool/ConnectionPool.scala | 13 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala index 62084e14..1b58bdcc 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/Connection.scala @@ -115,4 +115,25 @@ trait Connection { def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] + /** + * + * Executes an (asynchronous) function within a transaction block. + * If the function completes successfully, the transaction is committed, otherwise it is aborted. + * + * @param f operation to execute on this connection + * @return result of f, conditional on transaction operations succeeding + */ + + def inTransaction[A](f : Connection => Future[A])(implicit executionContext : scala.concurrent.ExecutionContext) : Future[A] = { + this.sendQuery("BEGIN").flatMap { _ => + val p = scala.concurrent.Promise[A]() + f(this).onComplete { r => + this.sendQuery(if (r.isFailure) "ROLLBACK" else "COMMIT").onComplete { + case scala.util.Failure(e) if r.isSuccess => p.failure(e) + case _ => p.complete(r) + } + } + p.future + } + } } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala index be0c2a86..2a3add4b 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/ConnectionPool.scala @@ -95,4 +95,17 @@ class ConnectionPool[T <: Connection]( def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = this.use(_.sendPreparedStatement(query, values))(executionContext) + /** + * + * Picks one connection and executes an (asynchronous) function on it within a transaction block. + * If the function completes successfully, the transaction is committed, otherwise it is aborted. + * Either way, the connection is returned to the pool on completion. + * + * @param f operation to execute on a connection + * @return result of f, conditional on transaction operations succeeding + */ + + override def inTransaction[A](f : Connection => Future[A])(implicit context : ExecutionContext = executionContext) : Future[A] = + this.use(_.inTransaction[A](f)(context))(executionContext) + } From 3edda7a1ee306e4b42fc0b19e6d20a3a62d0d0bc Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Wed, 30 Oct 2013 13:04:32 -0400 Subject: [PATCH 184/357] Transaction tests for postgres --- .../async/db/postgresql/TransactionSpec.scala | 89 +++++++++++++++++++ .../SingleThreadedAsyncObjectPoolSpec.scala | 2 +- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala new file mode 100644 index 00000000..29db58aa --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala @@ -0,0 +1,89 @@ +package com.github.mauricio.async.db.postgresql + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.exceptions.DatabaseException +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.control.Exception.catching + +class TransactionSpec extends Specification with DatabaseTestHelper { + + val log = Log.get[TransactionSpec] + + val tableCreate = "CREATE TEMP TABLE transaction_test (x integer PRIMARY KEY)" + def tableInsert(x : Int) = "INSERT INTO transaction_test VALUES (" + x.toString + ")" + val tableSelect = "SELECT x FROM transaction_test ORDER BY x" + + "transactions" should { + + "commit simple inserts" in { + withHandler { handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { conn => + conn.sendQuery(tableInsert(1)).flatMap { _ => + conn.sendQuery(tableInsert(2)) + } + }) + + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 2 + rows(0)(0) === 1 + rows(1)(0) === 2 + } + } + + "rollback on error" in { + withHandler { handler => + executeDdl(handler, tableCreate) + catching(classOf[DatabaseException]).opt( + await(handler.inTransaction { conn => + conn.sendQuery(tableInsert(1)).flatMap { _ => + conn.sendQuery(tableInsert(1)) + } + }) + ) === None + + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 0 + } + + } + + "rollback explicitly" in { + withHandler { handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { conn => + conn.sendQuery(tableInsert(1)).flatMap { _ => + conn.sendQuery("ROLLBACK") + } + }) + + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 0 + } + + } + + "rollback to savepoint" in { + withHandler { handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { conn => + conn.sendQuery(tableInsert(1)).flatMap { _ => + conn.sendQuery("SAVEPOINT one").flatMap { _ => + conn.sendQuery(tableInsert(2)).flatMap { _ => + conn.sendQuery("ROLLBACK TO SAVEPOINT one") + } + } + } + }) + + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 1 + rows(0)(0) === 1 + } + + } + + } + +} 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 88f68d68..d99a60d1 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 @@ -84,7 +84,7 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH } - "it shoudl remove idle connections once the time limit has been reached" in { + "it should remove idle connections once the time limit has been reached" in { withPool({ pool => From 4db17a8754d67bb45cf797ebe60cb571e2d8f39d Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Sun, 3 Nov 2013 17:45:01 -0500 Subject: [PATCH 185/357] Make PostgresSQLConnection defensively thread safe Race conditions should be properly caught, but will still throw a ConnectionStillRunningQueryException. Basically this just involves making setQueryPromise check for an existing promise and reordering operations to happen within this mutex. readyForQuery is no longer necessary as it is fully redundant with queryPromise. For Issue #59. --- ...ConnectionStillRunningQueryException.scala | 6 +-- .../db/postgresql/PostgreSQLConnection.scala | 48 ++++++++----------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionStillRunningQueryException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionStillRunningQueryException.scala index aaf028d6..a6302410 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionStillRunningQueryException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionStillRunningQueryException.scala @@ -16,8 +16,8 @@ package com.github.mauricio.async.db.exceptions -class ConnectionStillRunningQueryException( connectionCount : Long, readyForQuery : Boolean ) - extends DatabaseException ( "[%s] - There is a query still being run here - readyForQuery -> %s".format( +class ConnectionStillRunningQueryException( connectionCount : Long, caughtRace : Boolean) + extends DatabaseException ( "[%s] - There is a query still being run here - race -> %s".format( connectionCount, - readyForQuery + caughtRace )) 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 f442950b..88ff733f 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 @@ -25,7 +25,7 @@ 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._ +import java.util.concurrent.atomic.{AtomicLong,AtomicInteger,AtomicReference} import messages.backend._ import messages.frontend._ import scala.Some @@ -65,7 +65,6 @@ class PostgreSQLConnection private final val preparedStatementsCounter = new AtomicInteger() private final implicit val internalExecutionContext = executionContext - private var readyForQuery = false private val parameterStatus = new scala.collection.mutable.HashMap[String, String]() private val parsedStatements = new scala.collection.mutable.HashMap[String, PreparedStatementHolder]() private var authenticated = false @@ -80,7 +79,7 @@ class PostgreSQLConnection private var queryResult: Option[QueryResult] = None - def isReadyForQuery: Boolean = this.readyForQuery + def isReadyForQuery: Boolean = this.queryPromise.isEmpty def connect: Future[Connection] = { this.connectionHandler.connect.onFailure { @@ -98,7 +97,6 @@ class PostgreSQLConnection override def sendQuery(query: String): Future[QueryResult] = { validateQuery(query) - this.readyForQuery = false val promise = Promise[QueryResult]() this.setQueryPromise(promise) @@ -132,7 +130,6 @@ class PostgreSQLConnection throw new InsufficientParametersException(paramsCount, values) } - this.readyForQuery = false val promise = Promise[QueryResult]() this.setQueryPromise(promise) this.currentPreparedStatement = Some(query) @@ -172,19 +169,15 @@ class PostgreSQLConnection this.disconnect } - this.failQueryPromise(e) - this.currentPreparedStatement = None + this.failQueryPromise(e) } override def onReadyForQuery() { this.connectionFuture.trySuccess(this) - queryResult.map(this.succeedQueryPromise) - - this.queryResult = None this.recentError = false - this.readyForQuery = true + queryResult.map(this.succeedQueryPromise) } override def onError(m: ErrorMessage) { @@ -269,15 +262,18 @@ class PostgreSQLConnection authenticationMessage.challengeType) } } + + private[this] def notReadyForQueryError(errorMessage : String, race : Boolean) = { + log.error(errorMessage) + throw new ConnectionStillRunningQueryException( + this.currentCount, + race + ) + } def validateIfItIsReadyForQuery(errorMessage: String) = - if (this.queryPromise.isDefined) { - log.error(errorMessage) - throw new ConnectionStillRunningQueryException( - this.currentCount, - this.readyForQuery - ) - } + if (this.queryPromise.isDefined) + notReadyForQueryError(errorMessage, false) private def validateQuery(query: String) { this.validateIfItIsReadyForQuery("Can't run query because there is one query pending already") @@ -290,7 +286,8 @@ class PostgreSQLConnection private def queryPromise: Option[Promise[QueryResult]] = queryPromiseReference.get() private def setQueryPromise(promise: Promise[QueryResult]) { - this.queryPromiseReference.set(Some(promise)) + if (!this.queryPromiseReference.compareAndSet(None, Some(promise))) + notReadyForQueryError("Can't run query due to a race with another started query", true) } private def clearQueryPromise { @@ -298,21 +295,18 @@ class PostgreSQLConnection } private def failQueryPromise(t: Throwable) { - val promise = this.queryPromise - - if (promise.isDefined) { + this.queryPromise.foreach { promise => this.clearQueryPromise log.error("Setting error on future {}", promise) - promise.get.failure(t) + promise.failure(t) } } private def succeedQueryPromise(result: QueryResult) { - val promise = this.queryPromise - - if (promise.isDefined) { + this.queryResult = None + this.queryPromise.foreach { promise => this.clearQueryPromise - promise.get.success(result) + promise.success(result) } } From 25892ec95cfa829e42a51fc1e9058875ca46e9c9 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Sun, 3 Nov 2013 18:02:12 -0500 Subject: [PATCH 186/357] Make MySQLConnection defensively thread-safe Use the same approach as in PostgreSQLConnection, where queryPromise is an AtomicReference. Previously it would check queryPromise.isComplete. However, there was no way that a non-null promise could be complete as the only two places it was completed (in {succeed,fail}QueryPromise) immediately set it to null beforehand. This should be reviewed/tested. Issue #59. Updated clearQueryPromise in both to return the old promise, in order to avoid racing completions. --- .../async/db/mysql/MySQLConnection.scala | 37 +++++++++++-------- .../db/postgresql/PostgreSQLConnection.scala | 12 +++--- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 851615da..8a8bf773 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -25,7 +25,7 @@ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ -import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.{AtomicLong,AtomicReference} import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} import scala.util.Failure @@ -78,7 +78,7 @@ class MySQLConnection( private final val connectionPromise = Promise[Connection]() private final val disconnectionPromise = Promise[Connection]() - private var queryPromise: Promise[QueryResult] = null + private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None) private var connected = false private var _lastException : Throwable = null private var serverVersion : Version = null @@ -185,33 +185,28 @@ class MySQLConnection( def sendQuery(query: String): Future[QueryResult] = { this.validateIsReadyForQuery() val promise = Promise[QueryResult] - this.queryPromise = promise + this.setQueryPromise(promise) this.connectionHandler.write(new QueryMessage(query)) promise.future } private def failQueryPromise(t: Throwable) { - if (this.isQuerying) { - val promise = this.queryPromise - this.queryPromise = null - - promise.tryFailure(t) + this.clearQueryPromise.foreach { + _.tryFailure(t) } } private def succeedQueryPromise(queryResult: QueryResult) { - if (this.isQuerying) { - val promise = this.queryPromise - this.queryPromise = null - promise.success(queryResult) + this.clearQueryPromise.foreach { + _.success(queryResult) } } - def isQuerying: Boolean = this.queryPromise != null && !this.queryPromise.isCompleted + def isQuerying: Boolean = this.queryPromise.isDefined def onResultSet(resultSet: ResultSet, message: EOFMessage) { if (this.isQuerying) { @@ -239,15 +234,25 @@ class MySQLConnection( throw new InsufficientParametersException(totalParameters, values) } val promise = Promise[QueryResult] - this.queryPromise = promise + this.setQueryPromise(promise) this.connectionHandler.write(new PreparedStatementMessage(query, values)) promise.future } private def validateIsReadyForQuery() { - if ( this.queryPromise != null && !this.queryPromise.isCompleted ) { - throw new ConnectionStillRunningQueryException(this.connectionCount, false ) + if ( isQuerying ) { + throw new ConnectionStillRunningQueryException(this.connectionCount, false) } } + private def queryPromise: Option[Promise[QueryResult]] = queryPromiseReference.get() + + private def setQueryPromise(promise: Promise[QueryResult]) { + if (!this.queryPromiseReference.compareAndSet(None, Some(promise))) + throw new ConnectionStillRunningQueryException(this.connectionCount, true) + } + + private def clearQueryPromise : Option[Promise[QueryResult]] = { + this.queryPromiseReference.getAndSet(None) + } } 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 88ff733f..17057bc6 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 @@ -290,13 +290,12 @@ class PostgreSQLConnection notReadyForQueryError("Can't run query due to a race with another started query", true) } - private def clearQueryPromise { - this.queryPromiseReference.set(None) + private def clearQueryPromise : Option[Promise[QueryResult]] = { + this.queryPromiseReference.getAndSet(None) } private def failQueryPromise(t: Throwable) { - this.queryPromise.foreach { promise => - this.clearQueryPromise + this.clearQueryPromise.foreach { promise => log.error("Setting error on future {}", promise) promise.failure(t) } @@ -304,9 +303,8 @@ class PostgreSQLConnection private def succeedQueryPromise(result: QueryResult) { this.queryResult = None - this.queryPromise.foreach { promise => - this.clearQueryPromise - promise.success(result) + this.clearQueryPromise.foreach { + _.success(result) } } From f754c1b72524974a8d011b458daac9bd4140e590 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Sun, 3 Nov 2013 18:11:27 -0500 Subject: [PATCH 187/357] Minor optimization: use foreach rather than map --- .../mauricio/async/db/postgresql/PostgreSQLConnection.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 17057bc6..4fbdfaaf 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 @@ -177,7 +177,7 @@ class PostgreSQLConnection this.connectionFuture.trySuccess(this) this.recentError = false - queryResult.map(this.succeedQueryPromise) + queryResult.foreach(this.succeedQueryPromise) } override def onError(m: ErrorMessage) { From 614de4ec6e7266123fbb7a8029058c0c6f53f9ee Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Mon, 4 Nov 2013 23:27:50 -0500 Subject: [PATCH 188/357] Decode OIDs as Long Technically OIDs are uint32, so Int is a theoretically better choice, but this would not properly preserve the unsignedness, which would then confuse input. --- .../mauricio/async/db/postgresql/column/ColumnTypes.scala | 3 ++- .../postgresql/column/PostgreSQLColumnDecoderRegistry.scala | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) 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 945b3019..e9a5e042 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 @@ -55,11 +55,12 @@ object ColumnTypes { final val IntervalArray = 1187 final val Boolean = 16 final val BooleanArray = 1000 + final val OID = 26 + final val OIDArray = 1028 final val ByteA = 17 final val ByteA_Array = 1001 - final val OIDArray = 1028 final val MoneyArray = 791 final val NameArray = 1003 final val UUIDArray = 2951 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 6b6ea7dd..734c0902 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 @@ -67,6 +67,9 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e case ColumnTypes.Integer => IntegerEncoderDecoder case IntegerArray => this.integerArrayDecoder + case OID => LongEncoderDecoder + case OIDArray => this.longArrayDecoder + case ColumnTypes.Numeric => BigDecimalEncoderDecoder case NumericArray => this.bigDecimalArrayDecoder @@ -103,7 +106,6 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e case Interval => PostgreSQLIntervalEncoderDecoder case IntervalArray => this.intervalArrayDecoder - case OIDArray => this.stringArrayDecoder case MoneyArray => this.stringArrayDecoder case NameArray => this.stringArrayDecoder case UUIDArray => this.stringArrayDecoder From 0f556e84ce3bdeb587cb1b43b029627d4649d3c6 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Tue, 5 Nov 2013 11:59:51 -0500 Subject: [PATCH 189/357] Test pulling oids from a select --- .../async/db/postgresql/PostgreSQLConnectionSpec.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 d1681586..47724a24 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 @@ -53,7 +53,7 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { time_column time, boolean_column boolean, constraint bigserial_column_pkey primary key (bigserial_column) - )""" + ) with oids""" val insert = """insert into type_test_table ( smallint_column, @@ -83,7 +83,7 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { ) """ - val select = "select * from type_test_table" + val select = "select *, oid from type_test_table" val preparedStatementCreate = """create temp table prepared_statement_test ( id bigserial not null, @@ -151,6 +151,8 @@ 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[Long] must beGreaterThan(0L) } From e3bcf1c46d82f9dad34edbf1aa93e822357e4a7a Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Sat, 9 Nov 2013 19:58:54 -0500 Subject: [PATCH 190/357] Improve URL parser to allow missing hostname/dbname Both the // and / parts of the URL are optional. Also fix a crash that would occur on any invalid URL. --- .../async/db/postgresql/util/ParserURL.scala | 8 ++-- .../async/db/postgresql/util/URLParser.scala | 2 +- .../db/postgresql/util/URLParserSpec.scala | 48 +++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) 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 index 31652262..ce5fa180 100644 --- 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 @@ -21,7 +21,7 @@ object ParserURL { val DEFAULT_PORT = "5432" - private val pgurl1 = """(jdbc:postgresql)://([^:]*|\[.+\])(?::(\d+))?/([^?]+)(?:\?user=(.*)&password=(.*))?""".r + private val pgurl1 = """(jdbc:postgresql):(?://([^/:]*|\[.+\])(?::(\d+))?)?(?:/([^/?]*))?(?:\?user=(.*)&password=(.*))?""".r private val pgurl2 = """(postgres|postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r def parse(connectionURL: String): Map[String, String] = { @@ -29,7 +29,9 @@ object ParserURL { connectionURL match { case pgurl1(protocol, server, port, dbname, username, password) => { - var result = properties + (PGHOST -> unwrapIpv6address(server)) + (PGDBNAME -> dbname) + 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(username != null) result = (result + (PGUSERNAME -> username) + (PGPASSWORD -> password)) result @@ -51,4 +53,4 @@ object ParserURL { } else server } -} \ No newline at end of file +} 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 73cc33c4..f39f24ac 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 @@ -38,7 +38,7 @@ object URLParser { username = properties.get(Username).getOrElse(Default.username), password = properties.get(Password), database = properties.get(ParserURL.PGDBNAME), - host = properties(ParserURL.PGHOST), + host = properties.getOrElse(ParserURL.PGHOST, Default.host), port = port, charset = charset ) 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 87cdd47e..1e542f52 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 @@ -124,6 +124,54 @@ class URLParserSpec extends Specification { configuration.port === 9987 } + "create a connection with a missing hostname" in { + val connectionUri = "jdbc:postgresql:/my_database?user=john&password=doe" + + val configuration = URLParser.parse(connectionUri) + + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "localhost" + configuration.port === 5432 + } + + "create a connection with a missing database name" in { + val connectionUri = "jdbc:postgresql://[::1]:9987/?user=john&password=doe" + + val configuration = URLParser.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:postgresql:" + + val configuration = URLParser.parse(connectionUri) + + configuration.username === "postgres" + configuration.password === None + configuration.database === None + configuration.host === "localhost" + configuration.port === 5432 + } + + "create a connection with an empty (invalid) url" in { + val connectionUri = "" + + val configuration = URLParser.parse(connectionUri) + + configuration.username === "postgres" + configuration.password === None + configuration.database === None + configuration.host === "localhost" + configuration.port === 5432 + } + } } From df248da75f1c1408513efeae334e838bf8721ed1 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Mon, 11 Nov 2013 12:45:11 -0500 Subject: [PATCH 191/357] Allow postgres queries to contain '??' for '?' Issue #60. Also makes query parsing about 20 times faster. --- .../db/postgresql/PostgreSQLConnection.scala | 42 ++++++++++++------- .../db/postgresql/PreparedStatementSpec.scala | 24 +++++++++++ 2 files changed, 50 insertions(+), 16 deletions(-) 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 4fbdfaaf..3d55e5a0 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 @@ -106,25 +106,35 @@ class PostgreSQLConnection promise.future } + private def prepareQueryParams(query : String) : (String, Int) = { + val result = new StringBuilder(query.length+16) + var offset = 0 + var params = 0 + while (offset < query.length) { + val next = query.indexOf('?', offset) + if (next == -1) { + result ++= query.substring(offset) + offset = query.length + } else { + result ++= query.substring(offset, next) + offset = next + 1 + if (offset < query.length && query(offset) == '?') { + result += '?' + offset += 1 + } else { + result += '$' + params += 1 + result ++= params.toString + } + } + } + (result.toString, params) + } + override def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { validateQuery(query) - var paramsCount = 0 - - val realQuery = if (query.contains("?")) { - query.foldLeft(new StringBuilder()) { - (builder, char) => - if (char == '?') { - paramsCount += 1 - builder.append("$" + paramsCount) - } else { - builder.append(char) - } - builder - }.toString() - } else { - query - } + val (realQuery, paramsCount) = prepareQueryParams(query) if (paramsCount != values.length) { throw new InsufficientParametersException(paramsCount, values) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index d81774f1..65f58725 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -39,6 +39,7 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { val messagesUpdate = "UPDATE messages SET content = ?, moment = ? WHERE id = ?" val messagesSelectOne = "SELECT id, content, moment FROM messages WHERE id = ?" val messagesSelectAll = "SELECT id, content, moment FROM messages" + val messagesSelectEscaped = "SELECT id, content, moment FROM messages WHERE content LIKE '%??%' AND id > ?" "prepared statements" should { @@ -118,6 +119,29 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } } + "support prepared statement with escaped placeholders" in { + withHandler { + handler => + + val firstContent = "Some? Moment" + val secondContent = "Some Other Moment" + val date = LocalDate.now() + + executeDdl(handler, this.messagesCreate) + executePreparedStatement(handler, this.messagesInsert, Array(Some(firstContent), None)) + executePreparedStatement(handler, this.messagesInsert, Array(Some(secondContent), Some(date))) + + val rows = executePreparedStatement(handler, this.messagesSelectEscaped, Array(0)).rows.get + + rows.length === 1 + + rows(0)("id") === 1 + rows(0)("content") === firstContent + rows(0)("moment") === null + + } + } + } } From e9c93af070eace1f269bd4964e2bd51209d69611 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Mon, 11 Nov 2013 13:12:50 -0500 Subject: [PATCH 192/357] Avoid parsing prepared statements more than once We can use PreparedStatementHolder to store information about the statement, including the parsed statement. Also avoid unnecessary lookups by storing the this handle directly. --- .../db/postgresql/PostgreSQLConnection.scala | 67 +++++-------------- .../postgresql/PreparedStatementHolder.scala | 30 ++++++++- 2 files changed, 46 insertions(+), 51 deletions(-) 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 3d55e5a0..e90cee74 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 @@ -74,7 +74,7 @@ class PostgreSQLConnection private var recentError = false private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None) private var currentQuery: Option[MutableResultSet[PostgreSQLColumnData]] = None - private var currentPreparedStatement: Option[String] = None + private var currentPreparedStatement: Option[PreparedStatementHolder] = None private var version = Version(0,0,0) private var queryResult: Option[QueryResult] = None @@ -106,61 +106,31 @@ class PostgreSQLConnection promise.future } - private def prepareQueryParams(query : String) : (String, Int) = { - val result = new StringBuilder(query.length+16) - var offset = 0 - var params = 0 - while (offset < query.length) { - val next = query.indexOf('?', offset) - if (next == -1) { - result ++= query.substring(offset) - offset = query.length - } else { - result ++= query.substring(offset, next) - offset = next + 1 - if (offset < query.length && query(offset) == '?') { - result += '?' - offset += 1 - } else { - result += '$' - params += 1 - result ++= params.toString - } - } - } - (result.toString, params) - } - override def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = { validateQuery(query) - val (realQuery, paramsCount) = prepareQueryParams(query) - - if (paramsCount != values.length) { - throw new InsufficientParametersException(paramsCount, values) - } - val promise = Promise[QueryResult]() this.setQueryPromise(promise) - this.currentPreparedStatement = Some(query) - this.isParsed(query) match { - case Some(holder) => { - this.currentQuery = Some(new MutableResultSet(holder.columnDatas)) - write(new PreparedStatementExecuteMessage(holder.statementId, realQuery, values, this.encoderRegistry)) - } - case None => { - val statementId = this.preparedStatementsCounter.incrementAndGet() - this.parsedStatements.put( query, new PreparedStatementHolder( statementId ) ) - write(new PreparedStatementOpeningMessage(statementId, realQuery, values, this.encoderRegistry)) - } + val holder = this.parsedStatements.getOrElseUpdate(query, + new PreparedStatementHolder( query, preparedStatementsCounter.incrementAndGet )) + + if (holder.paramsCount != values.length) { + this.clearQueryPromise + throw new InsufficientParametersException(holder.paramsCount, values) } - promise.future - } + this.currentPreparedStatement = Some(holder) + this.currentQuery = Some(new MutableResultSet(holder.columnDatas)) + write( + if (holder.prepared) + new PreparedStatementExecuteMessage(holder.statementId, holder.realQuery, values, this.encoderRegistry) + else { + holder.prepared = true + new PreparedStatementOpeningMessage(holder.statementId, holder.realQuery, values, this.encoderRegistry) + }) - private def isParsed(query: String): Option[PreparedStatementHolder] = { - this.parsedStatements.get(query) + promise.future } override def onError( exception : Throwable ) { @@ -234,8 +204,7 @@ class PostgreSQLConnection } private def setColumnDatas( columnDatas : Array[PostgreSQLColumnData] ) { - if (this.currentPreparedStatement.isDefined) { - val holder = this.parsedStatements(this.currentPreparedStatement.get) + this.currentPreparedStatement.foreach { holder => holder.columnDatas = columnDatas } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala index 454b7a85..41f6ac40 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala @@ -18,8 +18,34 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumnData -class PreparedStatementHolder( val statementId : Int ) { +class PreparedStatementHolder( query : String, val statementId : Int ) { + val (realQuery, paramsCount) = { + val result = new StringBuilder(query.length+16) + var offset = 0 + var params = 0 + while (offset < query.length) { + val next = query.indexOf('?', offset) + if (next == -1) { + result ++= query.substring(offset) + offset = query.length + } else { + result ++= query.substring(offset, next) + offset = next + 1 + if (offset < query.length && query(offset) == '?') { + result += '?' + offset += 1 + } else { + result += '$' + params += 1 + result ++= params.toString + } + } + } + (result.toString, params) + } + + var prepared : Boolean = false var columnDatas : Array[PostgreSQLColumnData] = Array.empty -} \ No newline at end of file +} From 11acaf4348c2459f93e976150d12b6c69ce56fc8 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 28 Nov 2013 18:52:39 -0300 Subject: [PATCH 193/357] Making sure column order is preserved, fixes #61 --- .gitignore | 2 + build.sbt | 2 +- .../async/db/general/MutableResultSet.scala | 8 ++- .../mysql/codec/MySQLConnectionHandler.scala | 11 +++- .../mauricio/async/db/mysql/QuerySpec.scala | 32 ++++++++++-- .../db/general/MutableResultSetSpec.scala | 52 ++++++++++--------- .../db/postgresql/PreparedStatementSpec.scala | 47 +++++++++++++++++ project/Build.scala | 7 +-- 8 files changed, 121 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 853884cf..44fbe0e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +out/* generate_bundles.rb .cache target/* @@ -15,3 +16,4 @@ mysql-async/target/* .ruby-version .ruby-gemset *.jar +*.iml diff --git a/build.sbt b/build.sbt index b78915ad..74ea37d6 100644 --- a/build.sbt +++ b/build.sbt @@ -1 +1 @@ -scalaVersion := "2.10.2" \ No newline at end of file +scalaVersion := "2.10.3" \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index de4d2cd7..0422a4cf 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -17,13 +17,11 @@ package com.github.mauricio.async.db.general import collection.mutable.ArrayBuffer -import com.github.mauricio.async.db.column.ColumnDecoderRegistry -import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{RowData, ResultSet} -import java.nio.charset.Charset +import com.github.mauricio.async.db.util.Log object MutableResultSet { - val log = Log.get[MutableResultSet[ColumnData]] + val log = Log.get[MutableResultSet[Nothing]] } class MutableResultSet[T <: ColumnData]( @@ -35,7 +33,7 @@ class MutableResultSet[T <: ColumnData]( ( this.columnTypes(index).name, index ) ).toMap - val columnNames : IndexedSeq[String] = this.columnMapping.keys.toIndexedSeq + val columnNames : IndexedSeq[String] = this.columnTypes.map(c => c.name) override def length: Int = this.rows.length diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 5b8accff..a5c0357d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -49,6 +49,8 @@ class MySQLConnectionHandler( ) extends SimpleChannelInboundHandler[Object] { + //import MySQLConnectionHandler.log + private implicit val internalPool = executionContext private final val bootstrap = new Bootstrap().group(this.group) @@ -248,9 +250,14 @@ class MySQLConnectionHandler( } def onColumnDefinitionFinished() { - this.currentQuery = new MutableResultSet[ColumnDefinitionMessage]( + + val columns = if ( this.currentPreparedStatementHolder != null ) { + this.currentPreparedStatementHolder.columns + } else { this.currentColumns - ) + } + + this.currentQuery = new MutableResultSet[ColumnDefinitionMessage](columns) if ( this.currentPreparedStatementHolder != null ) { this.parsedStatements.put( this.currentPreparedStatementHolder.statement, this.currentPreparedStatementHolder ) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 70d74950..86ade7ae 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -23,6 +23,8 @@ import scala.concurrent.duration.Duration import java.util.concurrent.TimeUnit import io.netty.util.CharsetUtil import com.github.mauricio.async.db.exceptions.InsufficientParametersException +import org.specs2.matcher.MatchResult +import com.github.mauricio.async.db.{QueryResult, ResultSet} class QuerySpec extends Specification with ConnectionHelper { @@ -147,18 +149,38 @@ class QuerySpec extends Specification with ConnectionHelper { | some_bytes BLOB not null, | primary key (id) )""".stripMargin - val columns = List("id", "some_bytes") + val createIdeas = """CREATE TEMPORARY TABLE ideas ( + | id INT NOT NULL AUTO_INCREMENT, + | some_idea VARCHAR(255) NOT NULL, + | primary key (id) )""".stripMargin + val select = "SELECT * FROM posts" + val selectIdeas = "SELECT * FROM ideas" + + val matcher : QueryResult => List[MatchResult[IndexedSeq[String]]] = { result => + val columns = result.rows.get.columnNames + List(columns must contain(allOf("id", "some_bytes")).inOrder, columns must have size(2)) + } + + val ideasMatcher : QueryResult => List[MatchResult[IndexedSeq[String]]] = { result => + val columns = result.rows.get.columnNames + List(columns must contain(allOf("id", "some_idea")).inOrder, columns must have size(2)) + } withConnection { connection => executeQuery(connection, create) + executeQuery(connection, createIdeas) + + matcher(executePreparedStatement(connection, select)) + ideasMatcher(executePreparedStatement(connection, selectIdeas)) + + matcher(executePreparedStatement(connection, select)) + ideasMatcher(executePreparedStatement(connection, selectIdeas)) - val preparedResult = executePreparedStatement(connection, select).rows.get - preparedResult.columnNames === columns + matcher(executeQuery(connection, select)) + ideasMatcher(executeQuery(connection, selectIdeas)) - val result = executeQuery(connection, select).rows.get - result.columnNames === columns } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala index f7e13c9f..78d36046 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/general/MutableResultSetSpec.scala @@ -27,28 +27,30 @@ class MutableResultSetSpec extends Specification { val charset = CharsetUtil.UTF_8 val decoder = new PostgreSQLColumnDecoderRegistry + def create(name: String, dataType: Int, columnNumber: Int = 0, dataTypeSize: Int = -1) = new PostgreSQLColumnData( + name = name, + tableObjectId = 0, + columnNumber = columnNumber, + dataType = dataType, + dataTypeSize = dataTypeSize, + dataTypeModifier = 0, + fieldFormat = 0 + ) + "result set" should { "correctly map column data to fields" in { val columns = Array( - new PostgreSQLColumnData( + create( name = "id", - tableObjectId = 0, - columnNumber = 0, dataType = ColumnTypes.Integer, - dataTypeSize = 4, - dataTypeModifier = 0, - fieldFormat = 0 + dataTypeSize = 4 ), - new PostgreSQLColumnData( + create( name = "name", - tableObjectId = 0, columnNumber = 5, - dataType = ColumnTypes.Varchar, - dataTypeSize = -1, - dataTypeModifier = 0, - fieldFormat = 0 + dataType = ColumnTypes.Varchar ) ) @@ -57,8 +59,8 @@ class MutableResultSetSpec extends Specification { val resultSet = new MutableResultSet(columns) - resultSet.addRow( Array( 1, text ) ) - resultSet.addRow( Array( 2, otherText ) ) + resultSet.addRow(Array(1, text)) + resultSet.addRow(Array(2, otherText)) resultSet(0)(0) === 1 resultSet(0)("id") === 1 @@ -74,18 +76,20 @@ class MutableResultSetSpec extends Specification { } - } + "should return the same order as the one given by columns" in { - def toBuffer( content : String ) : ByteBuf = { - val buffer = Unpooled.buffer() - buffer.writeBytes( content.getBytes(charset) ) - buffer - } + val columns = Array( + create("id", ColumnTypes.Integer), + create("name", ColumnTypes.Varchar), + create("birthday", ColumnTypes.Date), + create("created_at", ColumnTypes.Timestamp), + create("updated_at", ColumnTypes.Timestamp) + ) + val resultSet = new MutableResultSet(columns) + + resultSet.columnNames must contain(allOf("id", "name", "birthday", "created_at", "updated_at")).inOrder + } - def toBuffer( value : Int ) : ByteBuf = { - val buffer = Unpooled.buffer() - buffer.writeBytes(value.toString.getBytes(charset)) - buffer } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 65f58725..7775e0b9 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -92,6 +92,53 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } + "run two different prepared statements in sequence and get the right values" in { + + val create = """CREATE TEMP TABLE other_messages + ( + id bigserial NOT NULL, + other_moment date NULL, + other_content character varying(255) NOT NULL, + CONSTRAINT other_messages_bigserial_column_pkey PRIMARY KEY (id ) + )""" + + val select = "SELECT * FROM other_messages" + val insert = "INSERT INTO other_messages (other_moment, other_content) VALUES (?, ?)" + + val moment = LocalDate.now() + val otherMoment = LocalDate.now().minusDays(10) + + val message = "this is some message" + val otherMessage = "this is some other message" + + withHandler { + handler => + executeDdl(handler, this.messagesCreate) + executeDdl(handler, create) + + 1.until(4).map { + x => + executePreparedStatement(handler, this.messagesInsert, Array(message, moment)) + executePreparedStatement(handler, insert, Array(otherMoment, otherMessage)) + + val result = executePreparedStatement(handler, this.messagesSelectAll).rows.get + result.size === x + result.columnNames must contain(allOf("id", "content", "moment")).inOrder + result(x - 1)("moment") === moment + result(x - 1)("content") === message + + val otherResult = executePreparedStatement(handler, select).rows.get + otherResult.size === x + otherResult.columnNames must contain(allOf( "id", "other_moment", "other_content")).inOrder + otherResult(x - 1)("other_moment") === otherMoment + otherResult(x - 1)("other_content") === otherMessage + + } + + } + + } + "support prepared statement with Option parameters (Some/None)" in { withHandler { handler => diff --git a/project/Build.scala b/project/Build.scala index 5d0bf5f6..ffc4bf3c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -46,6 +46,7 @@ object ProjectBuild extends Build { object Configuration { val commonVersion = "0.2.9-SNAPSHOT" + val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" @@ -55,8 +56,8 @@ object Configuration { "org.slf4j" % "slf4j-api" % "1.7.5", "joda-time" % "joda-time" % "2.2", "org.joda" % "joda-convert" % "1.3.1", - "org.scala-lang" % "scala-library" % "2.10.3", - "io.netty" % "netty-all" % "4.0.11.Final", + "org.scala-lang" % "scala-library" % projectScalaVersion, + "io.netty" % "netty-all" % "4.0.12.Final", "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, logbackDependency @@ -75,7 +76,7 @@ object Configuration { :+ "-feature" , scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), - scalaVersion := "2.10.3", + scalaVersion := projectScalaVersion, javacOptions := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8"), organization := "com.github.mauricio", version := commonVersion, From ffa2c262b715ad4ad8eea7f4047ef316cf177e7e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 29 Nov 2013 03:12:01 -0300 Subject: [PATCH 194/357] Fixing MySQL bug where insert statements without placeholders sent as prepared statements would never complete --- .../db/exceptions/DatabaseException.scala | 6 +++- .../async/db/mysql/MySQLConnection.scala | 27 ++++++++++---- .../mysql/codec/MySQLConnectionHandler.scala | 12 ++----- .../db/mysql/codec/MySQLFrameDecoder.scala | 23 +++++++----- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 5 +++ ...paredStatementPrepareResponseDecoder.scala | 5 +-- .../db/mysql/PreparedStatementsSpec.scala | 17 +++++++++ .../async/db/mysql/TransactionSpec.scala | 36 +++++++++++++++++++ .../async/db/postgresql/TransactionSpec.scala | 16 +++++++++ 9 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/TransactionSpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DatabaseException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DatabaseException.scala index 0dfaa9b8..1975edcb 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DatabaseException.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/DatabaseException.scala @@ -16,4 +16,8 @@ package com.github.mauricio.async.db.exceptions -class DatabaseException(message: String) extends RuntimeException(message) \ No newline at end of file +class DatabaseException(message: String, cause : Throwable) extends RuntimeException(message) { + + def this( message : String ) = this(message, null) + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 8a8bf773..1517a095 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -43,7 +43,6 @@ import scala.util.Success import com.github.mauricio.async.db.mysql.message.server.EOFMessage object MySQLConnection { - final val log = Log.get[MySQLConnection] final val Counter = new AtomicLong() final val MicrosecondsVersion = Version(5,6,0) } @@ -58,13 +57,13 @@ class MySQLConnection( with Connection { - import MySQLConnection.log - // validate that this charset is supported charsetMapper.toInt(configuration.charset) + private final val connectionCount = MySQLConnection.Counter.incrementAndGet() private final val connectionId = s"[mysql-connection-$connectionCount]" + private final val log = Log.getByName(connectionId) private implicit val internalPool = executionContext private final val connectionHandler = new MySQLConnectionHandler( @@ -97,8 +96,14 @@ class MySQLConnection( def close: Future[Connection] = { + log.debug("Closing connection") + if ( this.isConnected ) { if (!this.disconnectionPromise.isCompleted) { + val exception = new DatabaseException("Connection is being closed") + exception.fillInStackTrace() + this.failQueryPromise(exception) + this.connectionHandler.clearQueryState this.connectionHandler.write(QuitMessage.Instance).onComplete { case Success(channelFuture) => { this.connectionHandler.disconnect.onComplete { @@ -115,17 +120,17 @@ class MySQLConnection( } override def connected(ctx: ChannelHandlerContext) { - log.debug(s"$connectionId Connected to {}", ctx.channel.remoteAddress) + log.debug("Connected to {}", ctx.channel.remoteAddress) this.connected = true } override def exceptionCaught(throwable: Throwable) { - log.error(s"$connectionId Transport failure ", throwable) + log.error("Transport failure", throwable) setException(throwable) } override def onError(message: ErrorMessage) { - log.error(s"$connectionId Received an error message -> {}", message) + log.error("Received an error message -> {}", message) val exception = new MySQLException(message) exception.fillInStackTrace() this.setException(exception) @@ -139,9 +144,11 @@ class MySQLConnection( override def onOk(message: OkMessage) { if ( !this.connectionPromise.isCompleted ) { + log.debug("Connected to database") this.connectionPromise.success(this) } else { if (this.isQuerying) { + log.debug("Succeeding query promise") this.succeedQueryPromise( new MySQLQueryResult( message.affectedRows, @@ -151,6 +158,8 @@ class MySQLConnection( message.warnings ) ) + } else { + log.warn("Received OK when not querying or connecting, not sure what this is") } } } @@ -239,6 +248,11 @@ class MySQLConnection( promise.future } + + override def toString: String = { + "%s(%s,%d)".format(this.getClass.getName, this.connectionId, this.connectionCount) + } + private def validateIsReadyForQuery() { if ( isQuerying ) { throw new ConnectionStillRunningQueryException(this.connectionCount, false) @@ -255,4 +269,5 @@ class MySQLConnection( private def clearQueryPromise : Option[Promise[QueryResult]] = { this.queryPromiseReference.getAndSet(None) } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index a5c0357d..1d3ff7f1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -35,10 +35,6 @@ import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent._ -object MySQLConnectionHandler { - val log = Log.get[MySQLConnectionHandler] -} - class MySQLConnectionHandler( configuration: Configuration, charsetMapper: CharsetMapper, @@ -49,10 +45,8 @@ class MySQLConnectionHandler( ) extends SimpleChannelInboundHandler[Object] { - //import MySQLConnectionHandler.log - private implicit val internalPool = executionContext - + private final val log = Log.getByName(s"[connection-handler]${connectionId}") private final val bootstrap = new Bootstrap().group(this.group) private final val connectionPromise = Promise[MySQLConnectionHandler] private final val decoder = new MySQLFrameDecoder(configuration.charset, connectionId) @@ -92,7 +86,7 @@ class MySQLConnectionHandler( override def channelRead0(ctx: ChannelHandlerContext, message: Object) { - //log.debug("Message received {}", message) + log.debug("Message received {}", message) message match { case m: ServerMessage => { @@ -224,7 +218,7 @@ class MySQLConnectionHandler( def disconnect: ChannelFuture = this.currentContext.close() - private def clearQueryState { + def clearQueryState { this.currentColumns.clear() this.currentParameters.clear() this.currentQuery = null diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 5c26c3a2..06d740d7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -28,15 +28,12 @@ import io.netty.handler.codec.ByteToMessageDecoder import java.nio.ByteOrder import java.nio.charset.Charset import java.util.concurrent.atomic.AtomicInteger +import com.github.mauricio.async.db.mysql.MySQLHelper -object MySQLFrameDecoder { - val log = Log.get[MySQLFrameDecoder] -} - class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToMessageDecoder { - + private final val log = Log.getByName(s"[frame-decoder]${connectionId}") private final val messagesCount = new AtomicInteger() private final val handshakeDecoder = new HandshakeV10Decoder(charset) private final val errorDecoder = new ErrorDecoder(charset) @@ -80,8 +77,8 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM // TODO: Remove once https://github.com/netty/netty/issues/1704 is fixed val slice = buffer.readSlice(size).order(ByteOrder.LITTLE_ENDIAN) - //val dump = MySQLHelper.dumpAsHex(slice) - //log.debug(s"$connectionId [${messagesCount.get()}] Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) + val dump = MySQLHelper.dumpAsHex(slice) + log.debug(s"[${messagesCount.get()}] Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) slice.readByte() @@ -168,8 +165,18 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM if (slice.readableBytes() != 0) { throw new BufferNotFullyConsumedException(slice) } + if (result != null) { - out.add(result) + result match { + case m : PreparedStatementPrepareResponse => { + out.add(result) + if ( m.columnsCount == 0 && m.paramsCount == 0 ) { + this.clear + out.add(new ParamAndColumnProcessingFinishedMessage(new EOFMessage(0, 0)) ) + } + } + case _ => out.add(result) + } } } } else { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 819f5604..cbb91eb4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -26,6 +26,7 @@ import java.nio.charset.Charset import scala.annotation.switch import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.MessageToMessageEncoder +import io.netty.buffer.ByteBuf object MySQLOneToOneEncoder { val log = Log.get[MySQLOneToOneEncoder] @@ -33,6 +34,8 @@ object MySQLOneToOneEncoder { class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends MessageToMessageEncoder[Any] { + import MySQLOneToOneEncoder.log + private final val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private final val queryEncoder = new QueryMessageEncoder(charset) private final val rowEncoder = new BinaryRowEncoder(charset) @@ -66,6 +69,8 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten case _ => throw new EncoderNotAvailableException(message) } + log.debug("Writing message {}", message) + val result = encoder.encode(message) ByteBufferUtils.writePacketLength(result, sequence) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala index bb3f1174..af131267 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.mysql.decoder import com.github.mauricio.async.db.mysql.message.server.{PreparedStatementPrepareResponse, ServerMessage} import com.github.mauricio.async.db.util.Log import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.mysql.MySQLHelper class PreparedStatementPrepareResponseDecoder extends MessageDecoder { @@ -26,8 +27,8 @@ class PreparedStatementPrepareResponseDecoder extends MessageDecoder { def decode(buffer: ByteBuf): ServerMessage = { - //val dump = MySQLHelper.dumpAsHex(buffer) - //log.debug("prepared statement response dump is \n{}", dump) + val dump = MySQLHelper.dumpAsHex(buffer) + log.debug("prepared statement response dump is \n{}", dump) val statementId = Array[Byte]( buffer.readByte(), buffer.readByte(), buffer.readByte(), buffer.readByte() ) val columnsCount = buffer.readUnsignedShort() diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index d8a3178e..5154a64d 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -21,6 +21,8 @@ import java.util.concurrent.TimeUnit import org.joda.time._ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration +import com.github.mauricio.async.db.util.FutureUtils._ +import scala.Some class PreparedStatementsSpec extends Specification with ConnectionHelper { @@ -376,6 +378,21 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { } } + "insert with prepared statements and without columns" in { + withConnection { + connection => + executeQuery(connection, this.createTable) + + executePreparedStatement(connection, this.insert) + + val result = executePreparedStatement(connection, this.select).rows.get + result.size === 1 + + result(0)("name") === "Maurício Aragão" + } + + } + } } \ 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 new file mode 100644 index 00000000..04d3eaa5 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/TransactionSpec.scala @@ -0,0 +1,36 @@ +package com.github.mauricio.async.db.mysql + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.util.ExecutorServiceUtils._ +import com.github.mauricio.async.db.util.FutureUtils.await + +class TransactionSpec extends Specification with ConnectionHelper { + + val insertUser = """INSERT INTO users (name) VALUES (?)""" + + "connection in transaction" should { + + "correctly store the values of the transaction" in { + withConnection { + connection => + executeQuery(connection, this.createTable) + + val future = connection.inTransaction { + c => + c.sendPreparedStatement(this.insert) + .flatMap( r => connection.sendPreparedStatement(this.insert)) + } + + await(future) + + val result = executePreparedStatement(connection, this.select).rows.get + result.size === 2 + + result(0)("name") === "Maurício Aragão" + result(1)("name") === "Maurício Aragão" + } + } + + } + +} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala index 29db58aa..a8bbc501 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala @@ -32,6 +32,22 @@ class TransactionSpec extends Specification with DatabaseTestHelper { } } + "commit simple inserts with prepared statements" in { + withHandler { handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { conn => + conn.sendPreparedStatement(tableInsert(1)).flatMap { _ => + conn.sendPreparedStatement(tableInsert(2)) + } + }) + + val rows = executePreparedStatement(handler, tableSelect).rows.get + rows.length === 2 + rows(0)(0) === 1 + rows(1)(0) === 2 + } + } + "rollback on error" in { withHandler { handler => executeDdl(handler, tableCreate) From 3477a01757b7e281ffea70f37ab808325415c58f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 29 Nov 2013 11:25:40 -0300 Subject: [PATCH 195/357] Going forward with the specs2 upgrade --- .../mauricio/async/db/util/FutureUtils.scala | 2 +- .../mysql/codec/MySQLConnectionHandler.scala | 2 +- .../db/mysql/codec/MySQLFrameDecoder.scala | 4 +- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 2 +- ...paredStatementPrepareResponseDecoder.scala | 4 +- .../async/db/mysql/ConnectionHelper.scala | 12 +++--- .../async/db/mysql/MySQLConnectionSpec.scala | 12 +++--- .../db/mysql/PreparedStatementsSpec.scala | 4 +- .../async/db/mysql/TransactionSpec.scala | 4 +- .../pool/MySQLConnectionFactorySpec.scala | 41 +++++++++---------- .../postgresql/PostgreSQLConnectionSpec.scala | 4 +- project/Build.scala | 2 +- 12 files changed, 46 insertions(+), 47 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala index 763159b9..ccff6609 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/FutureUtils.scala @@ -22,7 +22,7 @@ import scala.language.postfixOps object FutureUtils { - def await[T]( future : Future[T] ) : T = { + def awaitFuture[T]( future : Future[T] ) : T = { Await.result(future, 5 seconds ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 1d3ff7f1..6186c3f1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -86,7 +86,7 @@ class MySQLConnectionHandler( override def channelRead0(ctx: ChannelHandlerContext, message: Object) { - log.debug("Message received {}", message) + //log.debug("Message received {}", message) message match { case m: ServerMessage => { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 06d740d7..baeefa08 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -77,8 +77,8 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM // TODO: Remove once https://github.com/netty/netty/issues/1704 is fixed val slice = buffer.readSlice(size).order(ByteOrder.LITTLE_ENDIAN) - val dump = MySQLHelper.dumpAsHex(slice) - log.debug(s"[${messagesCount.get()}] Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) + //val dump = MySQLHelper.dumpAsHex(slice) + //log.debug(s"[${messagesCount.get()}] Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) slice.readByte() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index cbb91eb4..459f0e7e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -69,7 +69,7 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten case _ => throw new EncoderNotAvailableException(message) } - log.debug("Writing message {}", message) + //log.debug("Writing message {}", message) val result = encoder.encode(message) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala index af131267..9e44a64a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala @@ -27,8 +27,8 @@ class PreparedStatementPrepareResponseDecoder extends MessageDecoder { def decode(buffer: ByteBuf): ServerMessage = { - val dump = MySQLHelper.dumpAsHex(buffer) - log.debug("prepared statement response dump is \n{}", dump) + //val dump = MySQLHelper.dumpAsHex(buffer) + //log.debug("prepared statement response dump is \n{}", dump) val statementId = Array[Byte]( buffer.readByte(), buffer.readByte(), buffer.readByte(), buffer.readByte() ) val columnsCount = buffer.readUnsignedShort() diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index 0c9e2cbd..ce323ce5 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.mysql -import com.github.mauricio.async.db.util.FutureUtils.await +import com.github.mauricio.async.db.util.FutureUtils.awaitFuture import com.github.mauricio.async.db._ import com.github.mauricio.async.db.pool.{PoolConfiguration, ConnectionPool} import com.github.mauricio.async.db.mysql.pool.MySQLConnectionFactory @@ -110,7 +110,7 @@ trait ConnectionHelper { try { fn(pool) } finally { - await( pool.close ) + awaitFuture( pool.close ) } } @@ -120,20 +120,20 @@ trait ConnectionHelper { val connection = new MySQLConnection(this.defaultConfiguration) try { - await( connection.connect ) + awaitFuture( connection.connect ) fn(connection) } finally { - await( connection.close ) + awaitFuture( connection.close ) } } def executeQuery( connection : Connection, query : String ) : QueryResult = { - await( connection.sendQuery(query) ) + awaitFuture( connection.sendQuery(query) ) } def executePreparedStatement( connection : Connection, query : String, values : Any * ) : QueryResult = { - await( connection.sendPreparedStatement(query, values) ) + awaitFuture( connection.sendPreparedStatement(query, values) ) } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala index 9dc02d56..aebf18dd 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.util.FutureUtils.await +import com.github.mauricio.async.db.util.FutureUtils.awaitFuture import org.specs2.mutable.Specification class MySQLConnectionSpec extends Specification { @@ -60,7 +60,7 @@ class MySQLConnectionSpec extends Specification { withNonConnectedConnection { connection => - await(connection.connect) === connection + awaitFuture(connection.connect) === connection }(configuration) } @@ -68,21 +68,21 @@ class MySQLConnectionSpec extends Specification { "connect to a MySQL instance without password" in { withNonConnectedConnection({ connection => - await(connection.connect) === connection + awaitFuture(connection.connect) === connection }) (rootConfiguration) } "connect to a MySQL instance without a database" in { withNonConnectedConnection({ connection => - await(connection.connect) === connection + awaitFuture(connection.connect) === connection }) (configurationWithoutDatabase) } "connect to a MySQL instance without database with password" in { withNonConnectedConnection({ connection => - await(connection.connect) === connection + awaitFuture(connection.connect) === connection }) (configurationWithPasswordWithoutDatabase) } @@ -94,7 +94,7 @@ class MySQLConnectionSpec extends Specification { try { fn(connection) } finally { - await(connection.close) + awaitFuture(connection.close) } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 5154a64d..56fa2c17 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -21,8 +21,8 @@ import java.util.concurrent.TimeUnit import org.joda.time._ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration -import com.github.mauricio.async.db.util.FutureUtils._ import scala.Some +import org.specs2.execute.Skipped class PreparedStatementsSpec extends Specification with ConnectionHelper { @@ -271,7 +271,7 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { connection => if ( connection.version < MySQLConnection.MicrosecondsVersion ) { - pending(s"this version of MySQL (${connection.version}) does not support microseconds") + skipped(s"this version of MySQL (${connection.version}) does not support microseconds") } else { executeQuery(connection, create) executeQuery(connection, insert) 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 04d3eaa5..f4ef811a 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 @@ -2,7 +2,7 @@ package com.github.mauricio.async.db.mysql import org.specs2.mutable.Specification import com.github.mauricio.async.db.util.ExecutorServiceUtils._ -import com.github.mauricio.async.db.util.FutureUtils.await +import com.github.mauricio.async.db.util.FutureUtils.awaitFuture class TransactionSpec extends Specification with ConnectionHelper { @@ -21,7 +21,7 @@ class TransactionSpec extends Specification with ConnectionHelper { .flatMap( r => connection.sendPreparedStatement(this.insert)) } - await(future) + awaitFuture(future) val result = executePreparedStatement(connection, this.select).rows.get result.size === 2 diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala index 8703dd98..f0270112 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql.pool import com.github.mauricio.async.db.mysql.{MySQLConnection, ConnectionHelper} -import com.github.mauricio.async.db.util.FutureUtils.await +import com.github.mauricio.async.db.util.FutureUtils.awaitFuture import org.specs2.mutable.Specification import scala.util._ import com.github.mauricio.async.db.exceptions.ConnectionNotConnectedException @@ -39,28 +39,27 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { } try { - factory.validate(connection) match { - case Failure(e) => ok("connection sucessfully rejected") - case Success(e) => failure("should not have come here") + if (factory.validate(connection).isSuccess) { + throw new IllegalStateException("should not have come here") } } finally { - await(connection.close) + awaitFuture(connection.close) } - + ok("connection successfully rejected") } "it should take a connection from the pool and the pool should not accept it back if it is broken" in { withPool { pool => - val connection = await(pool.take) + val connection = awaitFuture(pool.take) pool.inUse.size === 1 - await(connection.disconnect) + awaitFuture(connection.disconnect) try { - await(pool.giveBack(connection)) + awaitFuture(pool.giveBack(connection)) } catch { case e: ConnectionNotConnectedException => { // all good @@ -79,18 +78,18 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { } } - "fail validation if a connection is disconnected" in { +"fail validation if a connection is disconnected" in { - val connection = factory.create + val connection = factory.create - await(connection.disconnect) + awaitFuture(connection.disconnect) - factory.validate(connection) match { - case Failure(e) => ok("Connection successfully rejected") - case Success(c) => failure("should not have come here") - } + factory.validate(connection) match { + case Failure(e) => ok("Connection successfully rejected") + case Success(c) => failure("should not have come here") + } - } +} "fail validation if a connection is still waiting for a query" in { val connection = factory.create @@ -103,7 +102,7 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { case Success(c) => failure("should not have come here") } - await(connection.close) === connection + awaitFuture(connection.close) === connection } "accept a good connection" in { @@ -114,7 +113,7 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { case Failure(e) => failure("should not have come here") } - await(connection.close) === connection + awaitFuture(connection.close) === connection } "test a valid connection and say it is ok" in { @@ -126,7 +125,7 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { case Failure(e) => failure("should not have come here") } - await(connection.close) === connection + awaitFuture(connection.close) === connection } @@ -134,7 +133,7 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { val connection = factory.create - await(connection.disconnect) + awaitFuture(connection.disconnect) factory.test(connection) match { case Failure(e) => ok("Connection successfully rejected") 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 47724a24..a56789a7 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 @@ -282,14 +282,14 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { withHandler(configuration, { handler => executeQuery(handler, "SELECT 0") - failure("should not have come here") + throw new IllegalStateException("should not have come here") }) } catch { case e: GenericDatabaseException => { e.errorMessage.fields(InformationMessage.Routine) === "auth_failed" } case e: Exception => { - failure("should not have come here") + throw new IllegalStateException("should not have come here") } } diff --git a/project/Build.scala b/project/Build.scala index ffc4bf3c..4fd2c01a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -48,7 +48,7 @@ object Configuration { val commonVersion = "0.2.9-SNAPSHOT" val projectScalaVersion = "2.10.3" - val specs2Dependency = "org.specs2" %% "specs2" % "2.0" % "test" + val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" val commonDependencies = Seq( From de673d3dcaa1a620574e8cac652ca84a2f5af6df Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 29 Nov 2013 11:28:15 -0300 Subject: [PATCH 196/357] Fixing identation --- .../mysql/pool/MySQLConnectionFactorySpec.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala index f0270112..cb7cd11c 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala @@ -78,18 +78,18 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { } } -"fail validation if a connection is disconnected" in { + "fail validation if a connection is disconnected" in { - val connection = factory.create + val connection = factory.create - awaitFuture(connection.disconnect) + awaitFuture(connection.disconnect) - factory.validate(connection) match { - case Failure(e) => ok("Connection successfully rejected") - case Success(c) => failure("should not have come here") - } + factory.validate(connection) match { + case Failure(e) => ok("Connection successfully rejected") + case Success(c) => failure("should not have come here") + } -} + } "fail validation if a connection is still waiting for a query" in { val connection = factory.create From 0f6d6375cb1b4b2ff455fc5c3a69fa474aad7860 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 29 Nov 2013 12:01:19 -0300 Subject: [PATCH 197/357] Fixing spec that failed after specs2 upgrade --- .../async/db/mysql/PreparedStatementsSpec.scala | 2 +- .../db/mysql/pool/MySQLConnectionFactorySpec.scala | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 56fa2c17..4592bf01 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -271,7 +271,7 @@ class PreparedStatementsSpec extends Specification with ConnectionHelper { connection => if ( connection.version < MySQLConnection.MicrosecondsVersion ) { - skipped(s"this version of MySQL (${connection.version}) does not support microseconds") + true === true // no op } else { executeQuery(connection, create) executeQuery(connection, insert) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala index cb7cd11c..72a4e4e1 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactorySpec.scala @@ -79,16 +79,11 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { } "fail validation if a connection is disconnected" in { - val connection = factory.create awaitFuture(connection.disconnect) - factory.validate(connection) match { - case Failure(e) => ok("Connection successfully rejected") - case Success(c) => failure("should not have come here") - } - + factory.validate(connection).isFailure must beTrue } "fail validation if a connection is still waiting for a query" in { @@ -130,16 +125,11 @@ class MySQLConnectionFactorySpec extends Specification with ConnectionHelper { } "fail test if a connection is disconnected" in { - val connection = factory.create awaitFuture(connection.disconnect) - factory.test(connection) match { - case Failure(e) => ok("Connection successfully rejected") - case Success(c) => failure("should not have come here") - } - + factory.test(connection).isFailure must beTrue } } From 8fce70a8e72ef1c2b710619968c71c003859ddc9 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 1 Dec 2013 17:57:23 -0300 Subject: [PATCH 198/357] Including test for binary columns --- .../async/db/mysql/BinaryColumnsSpec.scala | 48 +++++++++++++++++++ .../db/mysql/PreparedStatementsSpec.scala | 1 - 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala new file mode 100644 index 00000000..6630fb22 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala @@ -0,0 +1,48 @@ +package com.github.mauricio.async.db.mysql + +import org.specs2.mutable.Specification +import java.util.UUID +import io.netty.util.CharsetUtil +import com.github.mauricio.async.db.RowData + +class BinaryColumnsSpec extends Specification with ConnectionHelper { + + "connection" should { + + "correctly load fields as byte arrays" in { + + val create = """CREATE TEMPORARY TABLE t ( + | id BIGINT NOT NULL AUTO_INCREMENT, + | uuid BINARY(36) NOT NULL, + | address VARBINARY(16) NOT NULL, + | PRIMARY KEY (id), + | INDEX idx_t_uuid (uuid), + | INDEX idx_t_address (address) + |);""".stripMargin + + val uuid = UUID.randomUUID().toString + val host = "127.0.0.1" + + val preparedInsert = "INSERT INTO t (uuid, address) VALUES (?, ?)" + val insert = s"INSERT INTO t (uuid, address) VALUES ('${uuid}', '${host}')" + val select = "SELECT * FROM t" + + withConnection { + connection => + executeQuery(connection, create) + executeQuery(connection, insert) + + val result = executeQuery(connection, select).rows.get + + compareBytes(result(0), "uuid", uuid ) + compareBytes(result(0), "address", host ) + } + + } + + } + + def compareBytes( row : RowData, column : String, expected : String ) = + row(column) === expected.getBytes(CharsetUtil.UTF_8) + +} \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala index 4592bf01..2b6bfe8a 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/PreparedStatementsSpec.scala @@ -22,7 +22,6 @@ import org.joda.time._ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration import scala.Some -import org.specs2.execute.Skipped class PreparedStatementsSpec extends Specification with ConnectionHelper { From a8925fe517ad07af34217ecfa3a40e30cd916083 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 1 Dec 2013 18:03:22 -0300 Subject: [PATCH 199/357] Removing a bit of logging and moving specs --- .../async/db/mysql/MySQLConnection.scala | 1 - .../async/db/mysql/BinaryColumnsSpec.scala | 56 +++++++++++++++++++ .../mauricio/async/db/mysql/QuerySpec.scala | 49 ---------------- 3 files changed, 56 insertions(+), 50 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 1517a095..9621dde8 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -148,7 +148,6 @@ class MySQLConnection( this.connectionPromise.success(this) } else { if (this.isQuerying) { - log.debug("Succeeding query promise") this.succeedQueryPromise( new MySQLQueryResult( message.affectedRows, diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala index 6630fb22..22912620 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala @@ -36,6 +36,62 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { compareBytes(result(0), "uuid", uuid ) compareBytes(result(0), "address", host ) + + executePreparedStatement( connection, preparedInsert, uuid, host) + + val otherResult = executePreparedStatement(connection, select).rows.get + + compareBytes(otherResult(1), "uuid", uuid ) + compareBytes(otherResult(1), "address", host ) + } + + } + + "support BINARY type" in { + + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL AUTO_INCREMENT, + | binary_column BINARY(20), + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (binary_column) VALUES (?)" + val select = "SELECT * FROM POSTS" + val bytes = (1 to 10).map(_.toByte).toArray + val padding = Array.fill[Byte](10)(0) + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, bytes) + val row = executeQuery(connection, select).rows.get(0) + row("id") === 1 + row("binary_column") === bytes ++ padding + } + + } + + "support VARBINARY type" in { + + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL AUTO_INCREMENT, + | varbinary_column VARBINARY(20), + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (varbinary_column) VALUES (?)" + val select = "SELECT * FROM POSTS" + val bytes = (1 to 10).map(_.toByte).toArray + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, bytes) + val row = executeQuery(connection, select).rows.get(0) + row("id") === 1 + row("varbinary_column") === bytes } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index cc3a57de..86ade7ae 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -211,55 +211,6 @@ class QuerySpec extends Specification with ConnectionHelper { } - "support BINARY type" in { - - val create = - """CREATE TEMPORARY TABLE POSTS ( - | id INT NOT NULL AUTO_INCREMENT, - | binary_column BINARY(20), - | primary key (id)) - """.stripMargin - - val insert = "INSERT INTO POSTS (binary_column) VALUES (?)" - val select = "SELECT * FROM POSTS" - val bytes = (1 to 10).map(_.toByte).toArray - val padding = Array.fill[Byte](10)(0) - - withConnection { - connection => - executeQuery(connection, create) - executePreparedStatement(connection, insert, bytes) - val row = executeQuery(connection, select).rows.get(0) - row("id") === 1 - row("binary_column") === bytes ++ padding - } - - } - - "support VARBINARY type" in { - - val create = - """CREATE TEMPORARY TABLE POSTS ( - | id INT NOT NULL AUTO_INCREMENT, - | varbinary_column VARBINARY(20), - | primary key (id)) - """.stripMargin - - val insert = "INSERT INTO POSTS (varbinary_column) VALUES (?)" - val select = "SELECT * FROM POSTS" - val bytes = (1 to 10).map(_.toByte).toArray - - withConnection { - connection => - executeQuery(connection, create) - executePreparedStatement(connection, insert, bytes) - val row = executeQuery(connection, select).rows.get(0) - row("id") === 1 - row("varbinary_column") === bytes - } - - } - "fail if number of args required is different than the number of provided parameters" in { withConnection { From 2d5c7bcf4c6f67ab36f33c30e0b20b10c6862498 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 1 Dec 2013 18:33:33 -0300 Subject: [PATCH 200/357] A couple more transaction tests and docs on transaction mechanism --- CHANGELOG.md | 13 +++++++ README.markdown | 35 ++++++++++++++++--- .../async/db/mysql/TransactionSpec.scala | 30 ++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 561ec0d0..16d086c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.2.9 - 2013-12-01 + +* PostgreSQL driver cannot parse value set by current_timestamp with timezone - #51 +* Add AsyncObjectPool.use to combine take and giveBack - #53 +* Add support for postgres interval type as Period - #56 +* Connection mutex improvements for issue - #59 +* Improve URL parser to allow missing hostname/dbname - #64 +* Decode OIDs as Long - #62 +* Improve placeholders and prepared statement handling - #65 +* ResultSet.columnNames order does not match ResultSet order - #61 +* Add Connection.inTransaction to wrap queries in a transaction block - #54 +* Add support for MySQL BINARY/VARBINARY types - #55 + ## 0.2.8 - 2013-09-24 * Validate the number of parameters for prepared statements - fixes #47 diff --git a/README.markdown b/README.markdown index 602a54f7..2a602f66 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.8" +"com.github.mauricio" %% "postgresql-async" % "0.2.9" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.8 + 0.2.9 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.8" +"com.github.mauricio" %% "mysql-async" % "0.2.9" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.8 + 0.2.9 ``` @@ -154,6 +154,30 @@ as prepared statement parameters and they will be encoded to their respective Po Remember that parameters are positional the order they show up at query should be the same as the one in the array or sequence given to the method call. +## Transactions + +Both drivers support transactions at the database level, the isolation level is the default for your database/connection, +to change the isolation level just call your database's command to set the isolation level for what you want. + +Here's an example of how transactions work: + +```scala + val future = connection.inTransaction { + c => + c.sendPreparedStatement(this.insert) + .flatMap( r => connection.sendPreparedStatement(this.insert)) + } +``` + +The `inTransaction` method allows you to execute a collection of statements in a single transactions, just use the +connection object you will receive in your block and send your statements to it. Given each statement causes a new +future to be returned, you need to `flatMap` the calls to be able to get a `Future[T]` instead of `Future[Future[...]]` + back. + +If all futures succeed, the transaction is committed normally, if any of them fail, a `rollback` is issued to the +database. You should not reuse a database connection that has rolled back a transaction, just close it and create a +new connection to continue using it. + ## Example usage (for PostgreSQL, but it looks almost the same on MySQL) You can find a small Play 2 app using it [here](https://github.com/mauricio/postgresql-async-app) and a blog post about @@ -209,9 +233,12 @@ Check the blog post above for more details and the project's ScalaDocs. ## Contributors * [devsprint](https://github.com/devsprint) +* [dylex](https://github.com/dylex) * [fwbrasil](https://github.com/fwbrasil) +* [kxbmap](https://github.com/kxbmap) * [magro](https://github.com/magro) * [normanmaurer](https://github.com/normanmaurer) +* [seratch](https://github.com/seratch) * [theon](https://github.com/theon) ## Contributing 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 f4ef811a..92921468 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 @@ -31,6 +31,36 @@ class TransactionSpec extends Specification with ConnectionHelper { } } + "correctly rollback changes if the transaction raises an exception" in { + + withConnection { + connection => + executeQuery(connection, this.createTable) + + val brokenInsert = """INSERT INTO users (id, name) VALUES (1, 'Maurício Aragão')""" + + executeQuery(connection, this.insert) + + val future = connection.inTransaction { + c => + c.sendQuery(this.insert).flatMap(r => c.sendQuery(brokenInsert)) + } + + try { + awaitFuture(future) + ko("Should not have arrived here") + } catch { + case e : Exception => { + val result = executePreparedStatement(connection, this.select).rows.get + result.size === 1 + result(0)("name") === "Maurício Aragão" + ok("success") + } + } + } + + } + } } From 56076f67009a71bfc27d0edb217e1a8072f5b93d Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 2 Dec 2013 00:13:24 -0300 Subject: [PATCH 201/357] Removing concurrent tests to make SFL4J happy --- build.sbt | 4 +- .../async/db/mysql/TransactionSpec.scala | 45 +++++- .../async/db/postgresql/TransactionSpec.scala | 144 +++++++++++------- 3 files changed, 131 insertions(+), 62 deletions(-) diff --git a/build.sbt b/build.sbt index 74ea37d6..4ba2d92a 100644 --- a/build.sbt +++ b/build.sbt @@ -1 +1,3 @@ -scalaVersion := "2.10.3" \ No newline at end of file +scalaVersion := "2.10.3" + +parallelExecution in ThisBuild := false \ 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 92921468..9e209fff 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 @@ -3,9 +3,12 @@ package com.github.mauricio.async.db.mysql import org.specs2.mutable.Specification import com.github.mauricio.async.db.util.ExecutorServiceUtils._ import com.github.mauricio.async.db.util.FutureUtils.awaitFuture +import com.github.mauricio.async.db.mysql.exceptions.MySQLException +import com.github.mauricio.async.db.Connection class TransactionSpec extends Specification with ConnectionHelper { + val brokenInsert = """INSERT INTO users (id, name) VALUES (1, 'Maurício Aragão')""" val insertUser = """INSERT INTO users (name) VALUES (?)""" "connection in transaction" should { @@ -36,9 +39,6 @@ class TransactionSpec extends Specification with ConnectionHelper { withConnection { connection => executeQuery(connection, this.createTable) - - val brokenInsert = """INSERT INTO users (id, name) VALUES (1, 'Maurício Aragão')""" - executeQuery(connection, this.insert) val future = connection.inTransaction { @@ -50,7 +50,11 @@ class TransactionSpec extends Specification with ConnectionHelper { awaitFuture(future) ko("Should not have arrived here") } catch { - case e : Exception => { + case e : MySQLException => { + + e.errorMessage.errorCode === 1062 + e.errorMessage.errorMessage === "Duplicate entry '1' for key 'PRIMARY'" + val result = executePreparedStatement(connection, this.select).rows.get result.size === 1 result(0)("name") === "Maurício Aragão" @@ -61,6 +65,39 @@ class TransactionSpec extends Specification with ConnectionHelper { } + "should make a connection invalid and not return it to the pool if it raises an exception" in { + + withPool { + pool => + + executeQuery(pool, this.createTable) + executeQuery(pool, this.insert) + + var connection : Connection = null + + val future = pool.inTransaction { + c => + connection = c + c.sendQuery(this.brokenInsert) + } + + try { + awaitFuture(future) + ko("this should not be reached") + } catch { + case e : MySQLException => { + + pool.availables must have size(0) + pool.availables must not contain(connection.asInstanceOf[MySQLConnection]) + + ok("success") + } + } + + } + + } + } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala index a8bbc501..92c49e2c 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala @@ -2,100 +2,130 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.exceptions.DatabaseException import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.control.Exception.catching +import com.github.mauricio.async.db.postgresql.exceptions.GenericDatabaseException class TransactionSpec extends Specification with DatabaseTestHelper { val log = Log.get[TransactionSpec] val tableCreate = "CREATE TEMP TABLE transaction_test (x integer PRIMARY KEY)" - def tableInsert(x : Int) = "INSERT INTO transaction_test VALUES (" + x.toString + ")" + + def tableInsert(x: Int) = "INSERT INTO transaction_test VALUES (" + x.toString + ")" + val tableSelect = "SELECT x FROM transaction_test ORDER BY x" "transactions" should { "commit simple inserts" in { - withHandler { handler => - executeDdl(handler, tableCreate) - await(handler.inTransaction { conn => - conn.sendQuery(tableInsert(1)).flatMap { _ => - conn.sendQuery(tableInsert(2)) - } - }) + withHandler { + handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { + conn => + conn.sendQuery(tableInsert(1)).flatMap { + _ => + conn.sendQuery(tableInsert(2)) + } + }) - val rows = executeQuery(handler, tableSelect).rows.get - rows.length === 2 - rows(0)(0) === 1 - rows(1)(0) === 2 + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 2 + rows(0)(0) === 1 + rows(1)(0) === 2 } } "commit simple inserts with prepared statements" in { - withHandler { handler => - executeDdl(handler, tableCreate) - await(handler.inTransaction { conn => - conn.sendPreparedStatement(tableInsert(1)).flatMap { _ => - conn.sendPreparedStatement(tableInsert(2)) - } - }) + withHandler { + handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { + conn => + conn.sendPreparedStatement(tableInsert(1)).flatMap { + _ => + conn.sendPreparedStatement(tableInsert(2)) + } + }) - val rows = executePreparedStatement(handler, tableSelect).rows.get - rows.length === 2 - rows(0)(0) === 1 - rows(1)(0) === 2 + val rows = executePreparedStatement(handler, tableSelect).rows.get + rows.length === 2 + rows(0)(0) === 1 + rows(1)(0) === 2 } } "rollback on error" in { - withHandler { handler => - executeDdl(handler, tableCreate) - catching(classOf[DatabaseException]).opt( - await(handler.inTransaction { conn => - conn.sendQuery(tableInsert(1)).flatMap { _ => - conn.sendQuery(tableInsert(1)) + withHandler { + handler => + executeDdl(handler, tableCreate) + + try { + await(handler.inTransaction { + conn => + conn.sendQuery(tableInsert(1)).flatMap { + _ => + conn.sendQuery(tableInsert(1)) + } + }) + failure("Should not have come here") + } catch { + case e: GenericDatabaseException => { + e.errorMessage.message === "duplicate key value violates unique constraint \"transaction_test_pkey\"" } - }) - ) === None + } - val rows = executeQuery(handler, tableSelect).rows.get - rows.length === 0 + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 0 } + } + + "do not reuse connection in pool if the transaction failed" in { + + + } "rollback explicitly" in { - withHandler { handler => - executeDdl(handler, tableCreate) - await(handler.inTransaction { conn => - conn.sendQuery(tableInsert(1)).flatMap { _ => - conn.sendQuery("ROLLBACK") - } - }) + withHandler { + handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { + conn => + conn.sendQuery(tableInsert(1)).flatMap { + _ => + conn.sendQuery("ROLLBACK") + } + }) - val rows = executeQuery(handler, tableSelect).rows.get - rows.length === 0 + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 0 } } "rollback to savepoint" in { - withHandler { handler => - executeDdl(handler, tableCreate) - await(handler.inTransaction { conn => - conn.sendQuery(tableInsert(1)).flatMap { _ => - conn.sendQuery("SAVEPOINT one").flatMap { _ => - conn.sendQuery(tableInsert(2)).flatMap { _ => - conn.sendQuery("ROLLBACK TO SAVEPOINT one") + withHandler { + handler => + executeDdl(handler, tableCreate) + await(handler.inTransaction { + conn => + conn.sendQuery(tableInsert(1)).flatMap { + _ => + conn.sendQuery("SAVEPOINT one").flatMap { + _ => + conn.sendQuery(tableInsert(2)).flatMap { + _ => + conn.sendQuery("ROLLBACK TO SAVEPOINT one") + } + } } - } - } - }) + }) - val rows = executeQuery(handler, tableSelect).rows.get - rows.length === 1 - rows(0)(0) === 1 + val rows = executeQuery(handler, tableSelect).rows.get + rows.length === 1 + rows(0)(0) === 1 } } From 2e8addb65dff9007601eb874d3dbe3900ca32b2c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 2 Dec 2013 00:17:10 -0300 Subject: [PATCH 202/357] Removing bad test :( --- .../mauricio/async/db/postgresql/TransactionSpec.scala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala index 92c49e2c..aad280d7 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TransactionSpec.scala @@ -79,12 +79,6 @@ class TransactionSpec extends Specification with DatabaseTestHelper { rows.length === 0 } - } - - "do not reuse connection in pool if the transaction failed" in { - - - } "rollback explicitly" in { From 962d7cf8f8d074a21259e624d09d3c315be526e1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 2 Dec 2013 10:35:13 -0300 Subject: [PATCH 203/357] Closing 0.2.9 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 4fd2c01a..8318641a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.9-SNAPSHOT" + val commonVersion = "0.2.9" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From af60e474a1cb054069c94fd306f4f681da10ed5f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 2 Dec 2013 10:46:06 -0300 Subject: [PATCH 204/357] Preparing next development process --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 8318641a..0b51fab9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.9" + val commonVersion = "0.2.10-SNAPSHOT" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From 1896e46d0463c9d3da6a86f4928298bc0a34600e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 8 Dec 2013 20:34:14 -0300 Subject: [PATCH 205/357] Moving netty log setting to out of the class --- .../scala/com/github/mauricio/async/db/util/NettyUtils.scala | 2 ++ .../mauricio/async/db/postgresql/PostgreSQLConnection.scala | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) 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 d43b14f9..1e294cf1 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 @@ -1,6 +1,7 @@ package com.github.mauricio.async.db.util import io.netty.channel.nio.NioEventLoopGroup +import io.netty.util.internal.logging.{InternalLoggerFactory, Slf4JLoggerFactory} /* * Copyright 2013 Maurício Linhares @@ -19,6 +20,7 @@ import io.netty.channel.nio.NioEventLoopGroup object NettyUtils { + InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) lazy val DefaultEventLoopGroup = new NioEventLoopGroup(0, DaemonThreadsFactory) } \ No newline at end of file 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 e90cee74..5f13636b 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 @@ -30,12 +30,10 @@ import messages.backend._ import messages.frontend._ import scala.Some import scala.concurrent._ -import io.netty.util.internal.logging.{Slf4JLoggerFactory, InternalLoggerFactory} import io.netty.channel.EventLoopGroup object PostgreSQLConnection { val log = Log.get[PostgreSQLConnection] - InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) val Counter = new AtomicLong() val ServerVersionKey = "server_version" } From 86571442d93bb74821b5a4f2145d1f3055615806 Mon Sep 17 00:00:00 2001 From: Flavio Wionn Brasil Date: Wed, 11 Dec 2013 00:49:44 +0100 Subject: [PATCH 206/357] use netty PooledByteBufAllocator by default --- .../scala/com/github/mauricio/async/db/Configuration.scala | 5 ++++- .../db/postgresql/codec/PostgreSQLConnectionHandler.scala | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) 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 30cf27bd..70497e08 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 @@ -20,6 +20,8 @@ import java.nio.charset.Charset import scala.Predef._ import scala.{None, Option, Int} import io.netty.util.CharsetUtil +import io.netty.buffer.AbstractByteBufAllocator +import io.netty.buffer.PooledByteBufAllocator object Configuration { val DefaultCharset = CharsetUtil.UTF_8 @@ -49,5 +51,6 @@ case class Configuration(username: String, password: Option[String] = None, database: Option[String] = None, charset: Charset = Configuration.DefaultCharset, - maximumMessageSize: Int = 16777216 + maximumMessageSize: Int = 16777216, + allocator: AbstractByteBufAllocator = PooledByteBufAllocator.DEFAULT ) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index 3157b7a4..4a3a7bc1 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -90,6 +90,7 @@ class PostgreSQLConnectionHandler }) this.bootstrap.option[java.lang.Boolean](ChannelOption.SO_KEEPALIVE, true) + this.bootstrap.option(ChannelOption.ALLOCATOR, configuration.allocator) this.bootstrap.connect(new InetSocketAddress(configuration.host, configuration.port)).onFailure { case e => connectionFuture.tryFailure(e) From 8756134acc4c7abaa7f5510a6278567ffc4dc0eb Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Dec 2013 20:31:09 -0300 Subject: [PATCH 207/357] Removing application name from PostgreSQL connection - fixes #70 --- .../mauricio/async/db/postgresql/PostgreSQLConnection.scala | 2 +- .../db/postgresql/codec/PostgreSQLConnectionHandler.scala | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) 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 5f13636b..fbba6635 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 @@ -33,7 +33,6 @@ import scala.concurrent._ import io.netty.channel.EventLoopGroup object PostgreSQLConnection { - val log = Log.get[PostgreSQLConnection] val Counter = new AtomicLong() val ServerVersionKey = "server_version" } @@ -60,6 +59,7 @@ class PostgreSQLConnection executionContext ) private final val currentCount = Counter.incrementAndGet() + private final val log = Log.getByName(s"${this.getClass.getName}:${currentCount}") private final val preparedStatementsCounter = new AtomicInteger() private final implicit val internalExecutionContext = executionContext diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index 4a3a7bc1..ff2403cc 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -26,8 +26,6 @@ import com.github.mauricio.async.db.util._ import java.net.InetSocketAddress import scala.annotation.switch import scala.concurrent._ -import scala.util.Failure -import scala.util.Success import io.netty.channel._ import io.netty.bootstrap.Bootstrap import io.netty.channel @@ -62,7 +60,6 @@ class PostgreSQLConnectionHandler private val properties = List( "user" -> configuration.username, "database" -> configuration.database, - "application_name" -> "Netty-PostgreSQL-driver-0.1.2-SNAPSHOT", "client_encoding" -> configuration.charset.name(), "DateStyle" -> "ISO", "extra_float_digits" -> "2") From c625339efb2895354478b873f4a3fa8cb1d2551e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Dec 2013 20:43:17 -0300 Subject: [PATCH 208/357] Accepting a bigger difference --- .../github/mauricio/async/db/postgresql/TimeAndDateSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index 9c40e5e7..0d6d98c9 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -182,7 +182,7 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { val dateTime = rows(0)("moment").asInstanceOf[DateTime] - dateTime.getMillis must beCloseTo(millis, 100) + dateTime.getMillis must beCloseTo(millis, 500) } } From 64bc6faedd5d4eac5e95f3a367d8d94e9e189fe7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Dec 2013 20:44:35 -0300 Subject: [PATCH 209/357] Updating changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16d086c1..000dc0a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.2.10 - none + +* Removed application_name from PostgreSQL default connection values - #70 + ## 0.2.9 - 2013-12-01 * PostgreSQL driver cannot parse value set by current_timestamp with timezone - #51 From 627b000d24494f8ab9a2e4c562c2339404e43adf Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Dec 2013 21:10:54 -0300 Subject: [PATCH 210/357] Closing 0.2.10 --- CHANGELOG.md | 2 +- README.markdown | 8 ++++---- project/Build.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 000dc0a3..dafe7483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.2.10 - none +## 0.2.10 - 2013-12-18 * Removed application_name from PostgreSQL default connection values - #70 diff --git a/README.markdown b/README.markdown index 2a602f66..f5406b21 100644 --- a/README.markdown +++ b/README.markdown @@ -16,7 +16,7 @@ If you want information specific to the drivers, check the [PostgreSQL README](p 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.9" +"com.github.mauricio" %% "postgresql-async" % "0.2.10" ``` Or Maven: @@ -25,14 +25,14 @@ Or Maven: com.github.mauricio postgresql-async_2.10 - 0.2.9 + 0.2.10 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.9" +"com.github.mauricio" %% "mysql-async" % "0.2.10" ``` Or Maven: @@ -41,7 +41,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.9 + 0.2.10 ``` diff --git a/project/Build.scala b/project/Build.scala index 0b51fab9..4e58bf4f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.10-SNAPSHOT" + val commonVersion = "0.2.10" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From bd18edc887808343bd8d7899648c2d80a82d4f65 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 18 Dec 2013 21:52:43 -0300 Subject: [PATCH 211/357] Next development cycle --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 4e58bf4f..4b4a8b61 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.10" + val commonVersion = "0.2.11-SNAPSHOT" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From 80e054fccc2759da8473ec22331c78bf57f23d59 Mon Sep 17 00:00:00 2001 From: Flavio Wionn Brasil Date: Fri, 20 Dec 2013 15:28:16 +0100 Subject: [PATCH 212/357] named executors --- .../async/db/util/DaemonThreadsFactory.scala | 14 +++++++++----- .../async/db/util/ExecutorServiceUtils.scala | 6 +++--- .../github/mauricio/async/db/util/NettyUtils.scala | 2 +- .../com/github/mauricio/async/db/util/Worker.scala | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala index 39cc7569..2839564a 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/DaemonThreadsFactory.scala @@ -16,14 +16,18 @@ package com.github.mauricio.async.db.util -import java.util.concurrent.{Executors, ThreadFactory} +import java.util.concurrent.{ Executors, ThreadFactory } +import java.util.concurrent.atomic.AtomicInteger -object DaemonThreadsFactory extends ThreadFactory { - def newThread(r: Runnable): Thread = { +case class DaemonThreadsFactory(name: String) extends ThreadFactory { + + private val threadNumber = new AtomicInteger(1) + def newThread(r: Runnable): Thread = { val thread = Executors.defaultThreadFactory().newThread(r) thread.setDaemon(true) - - return thread + val threadName = name + "-thread-" + threadNumber.getAndIncrement + thread.setName(threadName) + thread } } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala index 44ba2c83..6246dae7 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ExecutorServiceUtils.scala @@ -20,11 +20,11 @@ import java.util.concurrent.{ExecutorService, Executors} import scala.concurrent.ExecutionContext object ExecutorServiceUtils { - implicit val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory) + implicit val CachedThreadPool = Executors.newCachedThreadPool(DaemonThreadsFactory("db-async-default")) implicit val CachedExecutionContext = ExecutionContext.fromExecutor( CachedThreadPool ) - def newFixedPool( count : Int ) : ExecutorService = { - Executors.newFixedThreadPool( count, DaemonThreadsFactory ) + def newFixedPool( count : Int, name: String ) : ExecutorService = { + Executors.newFixedThreadPool( count, DaemonThreadsFactory(name) ) } } 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 1e294cf1..32f736e3 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 @@ -21,6 +21,6 @@ import io.netty.util.internal.logging.{InternalLoggerFactory, Slf4JLoggerFactory object NettyUtils { InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) - lazy val DefaultEventLoopGroup = new NioEventLoopGroup(0, DaemonThreadsFactory) + lazy val DefaultEventLoopGroup = new NioEventLoopGroup(0, DaemonThreadsFactory("db-async-netty")) } \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Worker.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Worker.scala index 70837b08..0988b052 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Worker.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Worker.scala @@ -22,7 +22,7 @@ import scala.concurrent.{ExecutionContextExecutorService, ExecutionContext} object Worker { val log = Log.get[Worker] - def apply() : Worker = apply(ExecutorServiceUtils.newFixedPool(1)) + def apply() : Worker = apply(ExecutorServiceUtils.newFixedPool(1, "db-async-worker")) def apply( executorService : ExecutorService ) : Worker = { new Worker(ExecutionContext.fromExecutorService( executorService )) From a75e93958697187b0bc59c82d07eeb23ae25fceb Mon Sep 17 00:00:00 2001 From: Flavio Wionn Brasil Date: Fri, 20 Dec 2013 16:15:37 +0100 Subject: [PATCH 213/357] PostgreSQLConnectionFactory - allow to configure the execution context --- .../postgresql/pool/PostgreSQLConnectionFactory.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala index 5b326e20..de06a671 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala @@ -25,6 +25,10 @@ import scala.concurrent.Await import scala.concurrent.duration._ import scala.language.postfixOps import scala.util.{Success, Failure, Try} +import scala.concurrent.ExecutionContext +import com.github.mauricio.async.db.util.ExecutorServiceUtils +import com.github.mauricio.async.db.util.NettyUtils +import io.netty.channel.EventLoopGroup object PostgreSQLConnectionFactory { val log = Log.get[PostgreSQLConnectionFactory] @@ -37,12 +41,15 @@ object PostgreSQLConnectionFactory { * @param configuration */ -class PostgreSQLConnectionFactory( val configuration : Configuration ) extends ObjectFactory[PostgreSQLConnection] { +class PostgreSQLConnectionFactory( + val configuration : Configuration, + group : EventLoopGroup = NettyUtils.DefaultEventLoopGroup, + executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends ObjectFactory[PostgreSQLConnection] { import PostgreSQLConnectionFactory.log def create: PostgreSQLConnection = { - val connection = new PostgreSQLConnection(configuration) + val connection = new PostgreSQLConnection(configuration, group = group, executionContext = executionContext) Await.result(connection.connect, 5.seconds) connection From 69e29d8da7e5e7bc20499575cbe61ce3a7e3fce4 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 27 Dec 2013 02:25:50 -0300 Subject: [PATCH 214/357] Fixes issue with LocalDateTime not being printed - fixes #73 --- .../mauricio/async/db/util/BufferDumper.java | 4 +- .../db/column/TimestampEncoderDecoder.scala | 29 ++++++--- .../column/TimestampEncoderDecoderSpec.scala | 63 +++++++++++++++++++ .../db/mysql/binary/BinaryRowDecoder.scala | 6 -- .../mysql/codec/MySQLConnectionHandler.scala | 20 ++++-- .../db/mysql/codec/MySQLFrameDecoder.scala | 3 +- ...paredStatementPrepareResponseDecoder.scala | 3 +- .../db/postgresql/codec/MessageEncoder.scala | 6 +- .../codec/PostgreSQLConnectionHandler.scala | 4 +- .../PostgreSQLColumnEncoderRegistry.scala | 3 +- .../PreparedStatementOpeningEncoder.scala | 4 +- .../PreparedStatementOpeningMessage.scala | 7 ++- .../postgresql/PostgreSQLConnectionSpec.scala | 15 +++++ project/Build.scala | 6 +- 14 files changed, 137 insertions(+), 36 deletions(-) rename mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java => db-async-common/src/main/java/com/github/mauricio/async/db/util/BufferDumper.java (97%) create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoderSpec.scala diff --git a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java b/db-async-common/src/main/java/com/github/mauricio/async/db/util/BufferDumper.java similarity index 97% rename from mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java rename to db-async-common/src/main/java/com/github/mauricio/async/db/util/BufferDumper.java index 36eeaac6..cd9ef72f 100644 --- a/mysql-async/src/main/java/com/github/mauricio/async/db/mysql/MySQLHelper.java +++ b/db-async-common/src/main/java/com/github/mauricio/async/db/util/BufferDumper.java @@ -1,8 +1,8 @@ -package com.github.mauricio.async.db.mysql; +package com.github.mauricio.async.db.util; import io.netty.buffer.ByteBuf; -public class MySQLHelper { +public class BufferDumper { public static final String dumpAsHex(ByteBuf buffer) { int length = buffer.readableBytes(); diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala index 78719082..c3e32ac2 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoder.scala @@ -23,21 +23,32 @@ import org.joda.time._ import org.joda.time.format.DateTimeFormatterBuilder object TimestampEncoderDecoder { + val BaseFormat = "yyyy-MM-dd HH:mm:ss" + val MillisFormat = ".SSSSSS" val Instance = new TimestampEncoderDecoder() } class TimestampEncoderDecoder extends ColumnEncoderDecoder { + import TimestampEncoderDecoder._ + private val optional = new DateTimeFormatterBuilder() - .appendPattern(".SSSSSS").toParser + .appendPattern(MillisFormat).toParser private val optionalTimeZone = new DateTimeFormatterBuilder() .appendPattern("Z").toParser - private val format = new DateTimeFormatterBuilder() - .appendPattern("yyyy-MM-dd HH:mm:ss") + private val builder = new DateTimeFormatterBuilder() + .appendPattern(BaseFormat) .appendOptional(optional) .appendOptional(optionalTimeZone) - .toFormatter + + private val timezonedPrinter = new DateTimeFormatterBuilder() + .appendPattern(s"${BaseFormat}${MillisFormat}Z").toFormatter + + private val nonTimezonedPrinter = new DateTimeFormatterBuilder() + .appendPattern(s"${BaseFormat}${MillisFormat}").toFormatter + + private val format = builder.toFormatter def formatter = format @@ -47,11 +58,11 @@ class TimestampEncoderDecoder extends ColumnEncoderDecoder { override def encode(value: Any): String = { value match { - case t: Timestamp => this.formatter.print(new DateTime(t)) - case t: Date => this.formatter.print(new DateTime(t)) - case t: Calendar => this.formatter.print(new DateTime(t)) - case t: LocalDateTime => this.formatter.print(t) - case t: ReadableDateTime => this.formatter.print(t) + case t: Timestamp => this.timezonedPrinter.print(new DateTime(t)) + case t: Date => this.timezonedPrinter.print(new DateTime(t)) + case t: Calendar => this.timezonedPrinter.print(new DateTime(t)) + case t: LocalDateTime => this.nonTimezonedPrinter.print(t) + case t: ReadableDateTime => this.timezonedPrinter.print(t) case _ => throw new DateEncoderNotAvailableException(value) } } diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoderSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoderSpec.scala new file mode 100644 index 00000000..4bf33453 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/column/TimestampEncoderDecoderSpec.scala @@ -0,0 +1,63 @@ +/* + * 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 org.specs2.mutable.Specification +import org.joda.time.DateTime +import java.sql.Timestamp +import org.joda.time.format.DateTimeFormatterBuilder +import java.util.Calendar + +class TimestampEncoderDecoderSpec extends Specification { + + val encoder = TimestampEncoderDecoder.Instance + val dateTime = new DateTime() + .withDate(2013, 12, 27) + .withTime(8, 40, 50, 800) + + val result = "2013-12-27 08:40:50.800000" + val formatter = new DateTimeFormatterBuilder().appendPattern("Z").toFormatter + val resultWithTimezone = s"2013-12-27 08:40:50.800000${formatter.print(dateTime)}" + + "decoder" should { + + "should print a timestamp" in { + val timestamp = new Timestamp(dateTime.toDate.getTime) + encoder.encode(timestamp) === resultWithTimezone + } + + "should print a LocalDateTime" in { + encoder.encode(dateTime.toLocalDateTime) === result + } + + "should print a date" in { + encoder.encode(dateTime.toDate) === resultWithTimezone + } + + "should print a calendar" in { + val calendar = Calendar.getInstance() + calendar.setTime(dateTime.toDate) + encoder.encode(calendar) === resultWithTimezone + } + + "should print a datetime" in { + encoder.encode(dateTime) === resultWithTimezone + } + + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index 4e7757f6..0f59ca5e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -18,15 +18,9 @@ package com.github.mauricio.async.db.mysql.binary import _root_.io.netty.buffer.ByteBuf import com.github.mauricio.async.db.exceptions.BufferNotFullyConsumedException -import com.github.mauricio.async.db.mysql.binary.decoder._ -import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage import com.github.mauricio.async.db.util._ -import java.nio.charset.Charset import scala.collection.mutable.ArrayBuffer -import com.github.mauricio.async.db.mysql.MySQLHelper -import scala.annotation.switch -import com.github.mauricio.async.db.mysql.util.CharsetMapper object BinaryRowDecoder { final val log = Log.get[BinaryRowDecoder] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 6186c3f1..ad703c1d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -187,7 +187,7 @@ class MySQLConnectionHandler( def write( message : QueryMessage ) : ChannelFuture = { this.decoder.queryProcessStarted() - this.currentContext.writeAndFlush(message) + writeAndHandleError(message) } def write( message : PreparedStatementMessage ) { @@ -203,17 +203,17 @@ class MySQLConnectionHandler( } case None => { decoder.preparedStatementPrepareStarted() - this.currentContext.writeAndFlush( new PreparedStatementPrepareMessage(message.statement) ) + writeAndHandleError( new PreparedStatementPrepareMessage(message.statement) ) } } } def write( message : HandshakeResponseMessage ) : ChannelFuture = { - this.currentContext.writeAndFlush(message) + writeAndHandleError(message) } def write( message : QuitMessage ) : ChannelFuture = { - this.currentContext.writeAndFlush(message) + writeAndHandleError(message) } def disconnect: ChannelFuture = this.currentContext.close() @@ -236,7 +236,7 @@ class MySQLConnectionHandler( decoder.preparedStatementExecuteStarted(columnsCount, parameters.size) this.currentColumns.clear() this.currentParameters.clear() - this.currentContext.writeAndFlush(new PreparedStatementExecuteMessage( statementId, values, parameters )) + writeAndHandleError(new PreparedStatementExecuteMessage( statementId, values, parameters )) } private def onPreparedStatementPrepareResponse( message : PreparedStatementPrepareResponse ) { @@ -266,4 +266,14 @@ class MySQLConnectionHandler( } } + private def writeAndHandleError( message : Any ) : ChannelFuture = { + val future = this.currentContext.writeAndFlush(message) + + future.onFailure { + case e : Throwable => handleException(e) + } + + future + } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index baeefa08..e5ea7b3b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -21,14 +21,13 @@ import com.github.mauricio.async.db.mysql.decoder._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.util.ByteBufferUtils.read3BytesInt import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util.{BufferDumper, Log} import io.netty.buffer.ByteBuf import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.ByteToMessageDecoder import java.nio.ByteOrder import java.nio.charset.Charset import java.util.concurrent.atomic.AtomicInteger -import com.github.mauricio.async.db.mysql.MySQLHelper class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToMessageDecoder { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala index 9e44a64a..dfb3a73d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/PreparedStatementPrepareResponseDecoder.scala @@ -17,9 +17,8 @@ package com.github.mauricio.async.db.mysql.decoder import com.github.mauricio.async.db.mysql.message.server.{PreparedStatementPrepareResponse, ServerMessage} -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util.{BufferDumper, Log} import io.netty.buffer.ByteBuf -import com.github.mauricio.async.db.mysql.MySQLHelper class PreparedStatementPrepareResponseDecoder extends MessageDecoder { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala index b512b1a8..f371d0e5 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.exceptions.EncoderNotAvailableException import com.github.mauricio.async.db.postgresql.encoders._ import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend._ -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util.{BufferDumper, Log} import java.nio.charset.Charset import scala.annotation.switch import io.netty.handler.codec.MessageToMessageEncoder @@ -43,7 +43,7 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e val buffer = msg match { case message: ClientMessage => { - val encoder = (message.kind : @switch) match { + val encoder = (message.kind: @switch) match { case ServerMessage.Close => CloseMessageEncoder case ServerMessage.Execute => this.executeEncoder case ServerMessage.Parse => this.openEncoder @@ -52,7 +52,9 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e case ServerMessage.PasswordMessage => this.credentialEncoder case _ => throw new EncoderNotAvailableException(message) } + encoder.encode(message) + } case _ => { throw new IllegalArgumentException("Can not encode message %s".format(msg)) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index ff2403cc..9e34d4f4 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -206,7 +206,9 @@ class PostgreSQLConnectionHandler } def write( message : ClientMessage ) { - this.currentContext.writeAndFlush(message) + this.currentContext.writeAndFlush(message).onFailure { + case e : Throwable => connectionDelegate.onError(e) + } } } 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 2ec2c1a4..358903aa 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 @@ -20,7 +20,6 @@ import com.github.mauricio.async.db.column._ import org.joda.time._ import scala.Some import scala.collection.JavaConversions._ -import java.nio.charset.Charset object PostgreSQLColumnEncoderRegistry { val Instance = new PostgreSQLColumnEncoderRegistry() @@ -51,7 +50,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[java.math.BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), classOf[LocalDate] -> ( DateEncoderDecoder -> ColumnTypes.Date ), - classOf[LocalDateTime] -> (TimestampEncoderDecoder.Instance -> ColumnTypes.TimestampWithTimezone), + classOf[LocalDateTime] -> (TimestampEncoderDecoder.Instance -> ColumnTypes.Timestamp), classOf[DateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[ReadableDateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[ReadableInstant] -> (DateEncoderDecoder -> ColumnTypes.Date), diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index f78a2321..442f5ae0 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementOpeningMessage} -import com.github.mauricio.async.db.util.ByteBufferUtils +import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} @@ -28,6 +28,8 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR with PreparedStatementEncoderHelper { + private val log = Log.get[PreparedStatementOpeningEncoder] + override def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementOpeningMessage] diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala index 7863033e..78ed8b4b 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/PreparedStatementOpeningMessage.scala @@ -20,4 +20,9 @@ import com.github.mauricio.async.db.column.ColumnEncoderRegistry import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage class PreparedStatementOpeningMessage(statementId: Int, query: String, values: Seq[Any], encoderRegistry : ColumnEncoderRegistry) - extends PreparedStatementMessage(statementId: Int, ServerMessage.Parse, query, values, encoderRegistry) \ No newline at end of file + extends PreparedStatementMessage(statementId: Int, ServerMessage.Parse, query, values, encoderRegistry) { + + override def toString() : String = + s"${this.getClass.getSimpleName}(id=${statementId},query=${query},values=${values}})" + +} \ No newline at end of file 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 a56789a7..93244111 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 @@ -27,6 +27,7 @@ import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +import org.joda.time.LocalDateTime object PostgreSQLConnectionSpec { val log = Log.get[PostgreSQLConnectionSpec] @@ -413,6 +414,20 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { } + "insert a LocalDateTime" in { + + withHandler { + handler => + executePreparedStatement(handler, "CREATE TEMP TABLE test(t TIMESTAMP)") + val date1 = new LocalDateTime + executePreparedStatement(handler, "INSERT INTO test(t) VALUES(?)", Array(date1)) + val result = executePreparedStatement(handler, "SELECT t FROM test") + val date2 = result.rows.get.head(0) + date1 === date2 + } + + } + } } diff --git a/project/Build.scala b/project/Build.scala index 4b4a8b61..0a5bc97b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -54,10 +54,10 @@ object Configuration { val commonDependencies = Seq( "commons-pool" % "commons-pool" % "1.6", "org.slf4j" % "slf4j-api" % "1.7.5", - "joda-time" % "joda-time" % "2.2", - "org.joda" % "joda-convert" % "1.3.1", + "joda-time" % "joda-time" % "2.3", + "org.joda" % "joda-convert" % "1.5", "org.scala-lang" % "scala-library" % projectScalaVersion, - "io.netty" % "netty-all" % "4.0.12.Final", + "io.netty" % "netty-all" % "4.0.14.Final", "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, logbackDependency From 4f518eec04644c6917e2feafd3374e620d6560e9 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 31 Dec 2013 04:56:29 -0300 Subject: [PATCH 215/357] Defaulting string to untyped on parameter binding to allow for automatic casts, fixes #75 and #74 --- .../db/postgresql/column/ColumnTypes.scala | 2 +- .../PostgreSQLColumnEncoderRegistry.scala | 3 +- .../db/postgresql/PreparedStatementSpec.scala | 57 ++++++++++++++++++- script/prepare_build.sh | 1 + 4 files changed, 59 insertions(+), 4 deletions(-) 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 e9a5e042..7f15b0f6 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 @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.column object ColumnTypes { - + final val Untyped = 0 final val Bigserial = 20 final val BigserialArray = 1016 final val Char = 18 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 358903aa..b5f32735 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 @@ -167,10 +167,11 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { } else { value match { case Some(v) => kindOf(v) + case v : String => ColumnTypes.Untyped case _ => { this.classes.get(value.getClass) match { case Some( entry ) => entry._2 - case None => 0 + case None => ColumnTypes.Untyped } } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 7775e0b9..44e43e7d 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql import org.specs2.mutable.Specification -import org.joda.time.{DateTime, LocalDate} +import org.joda.time.LocalDate import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.exceptions.InsufficientParametersException @@ -129,7 +129,7 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { val otherResult = executePreparedStatement(handler, select).rows.get otherResult.size === x - otherResult.columnNames must contain(allOf( "id", "other_moment", "other_content")).inOrder + otherResult.columnNames must contain(allOf("id", "other_moment", "other_content")).inOrder otherResult(x - 1)("other_moment") === otherMoment otherResult(x - 1)("other_content") === otherMessage @@ -189,6 +189,59 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } } + "support handling of enum types" in { + + withHandler { + handler => + val create = """CREATE TEMP TABLE messages + |( + |id bigserial NOT NULL, + |feeling example_mood, + |CONSTRAINT bigserial_column_pkey PRIMARY KEY (id ) + |);""".stripMargin + val insert = "INSERT INTO messages (feeling) VALUES (?) RETURNING id" + val select = "SELECT * FROM messages" + + executeDdl(handler, create) + + executePreparedStatement(handler, insert, Array("sad")) + + val result = executePreparedStatement(handler, select).rows.get + + result.size === 1 + result(0)("id") === 1L + result(0)("feeling") === "sad" + } + + } + + "support handling JSON type" in { + + withHandler { + handler => + val create = """create temp table people + |( + |id bigserial primary key, + |addresses json, + |phones json + |);""".stripMargin + + val insert = "INSERT INTO people (addresses, phones) VALUES (?,?) RETURNING id" + val select = "SELECT * FROM people" + val addresses = """[ {"Home" : {"city" : "Tahoe", "state" : "CA"}} ]""" + val phones = """[ "925-575-0415", "916-321-2233" ]""" + + executeDdl(handler, create) + executePreparedStatement(handler, insert, Array(addresses, phones) ) + val result = executePreparedStatement(handler, select).rows.get + + result(0)("addresses") === addresses + result(0)("phones") === phones + + } + + } + } } diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 7e724fa6..93687868 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -12,6 +12,7 @@ psql -c "alter database netty_driver_time_test set timezone to 'GMT'" -U postgre psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres +psql -d "netty_driver_test" -c "CREATE TYPE example_mood AS ENUM ('sad', 'ok', 'happy');" sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf From 8c0fe28a9cd28d371d82a3f3f30b36d212519c26 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 31 Dec 2013 05:07:54 -0300 Subject: [PATCH 216/357] Marking the JSON spec as pending --- .../mauricio/async/db/postgresql/PreparedStatementSpec.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 44e43e7d..06b80967 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -217,6 +217,8 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { "support handling JSON type" in { + pending("travis-ci PG doesn't have the JSON type") + withHandler { handler => val create = """create temp table people From 7cc7aaf2823eb39a4d8f1d59c2105a0e1abf87bf Mon Sep 17 00:00:00 2001 From: Jakub Arnold Date: Tue, 31 Dec 2013 17:18:37 +0100 Subject: [PATCH 217/357] DAT GRAMMAR --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index f5406b21..d1be38da 100644 --- a/README.markdown +++ b/README.markdown @@ -70,7 +70,7 @@ If you have used JDBC before, you might have heard that prepared statements are to databases. This isn't exactly true all the time (as you can see on [this presentation](http://www.youtube.com/watch?v=kWOAHIpmLAI) by [@tenderlove](http://github.com/tenderlove)) and there is a memory cost in keeping prepared statements. -Prepared statements are tied to a connection, they are not database-wide, so, if you generate your queries dinamically +Prepared statements are tied to a connection, they are not database-wide, so, if you generate your queries dynamically all the time you might eventually blow up your connection memory and your database memory. Why? From 6fb7e712c4e380805c8ecd90b8eaac737c92a39e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 1 Jan 2014 16:04:29 -0300 Subject: [PATCH 218/357] Fixing a couple typos --- README.markdown | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/README.markdown b/README.markdown index d1be38da..05bc4c6f 100644 --- a/README.markdown +++ b/README.markdown @@ -3,7 +3,7 @@ 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 process of _send a statement, get a response_ that you usually see in applications out there. So it's unlikely -there will be support for updating result sets live or things like that. +there will be support for updating result sets live or stuff like that. This project always returns [JodaTime](http://joda-time.sourceforge.net/) when dealing with date types and not the `java.util.Date` class. @@ -49,7 +49,7 @@ Or Maven: **READ THIS NOW** -Both clients will let you set the database encoding for something else. Unless you are 1000% sure of what you are doing, +Both clients will let you set the database encoding for something else. Unless you are **1000% sure** of what you are doing, **DO NOT** change the default encoding (currently, UTF-8). Some people assume the connection encoding is the **database** or **columns** encoding but **IT IS NOT**, this is just the connection encoding that is used between client and servers doing communication. @@ -62,7 +62,7 @@ safely stored at your database/column encoding. This is as long as you are using the correct string types, BLOB columns will not be translated since they're supposed to hold a stream of bytes. -So, just don't touch it and be happy. +So, just don't touch it, create your tables and columns with the correct encoding and be happy. ## Prepared statements gotcha @@ -113,7 +113,7 @@ So, prepared statements are awesome, but are not free. Use them judiciously. Represents a connection to the database. This is the **root** object you will be using in your application. You will find three classes that implement this trait, `PostgreSQLConnection`, `MySQLConnection` and `ConnectionPool`. -The different between them is that `ConnectionPool` is, as the name implies, a pool of connections and you +The difference between them is that `ConnectionPool` is, as the name implies, a pool of connections and you need to give it an connection factory so it can create connections and manage them. To create both you will need a `Configuration` object with your database details. You can create one manually or @@ -149,7 +149,9 @@ query without having to escape stuff yourself. The driver itself will make sure database in a safe way so you don't have to worry about SQL injection attacks. The basic numbers, Joda Time date, time, timestamp objects, strings and arrays of these objects are all valid values -as prepared statement parameters and they will be encoded to their respective PostgreSQL types. +as prepared statement parameters and they will be encoded to their respective database types. Remember that not all databases +are created equal, so not every type will work or might work in unexpected ways. For instance, MySQL doesn't have array +types, so, if you send an array or collection to MySQL it won't work. Remember that parameters are positional the order they show up at query should be the same as the one in the array or sequence given to the method call. @@ -230,17 +232,6 @@ disconnect and the connection is closed. You can also use the `ConnectionPool` provided by the driver to simplify working with database connections in your app. Check the blog post above for more details and the project's ScalaDocs. -## Contributors - -* [devsprint](https://github.com/devsprint) -* [dylex](https://github.com/dylex) -* [fwbrasil](https://github.com/fwbrasil) -* [kxbmap](https://github.com/kxbmap) -* [magro](https://github.com/magro) -* [normanmaurer](https://github.com/normanmaurer) -* [seratch](https://github.com/seratch) -* [theon](https://github.com/theon) - ## Contributing Contributing to the project is simple, fork it on Github, hack on what you're insterested in seeing done or at the @@ -251,6 +242,8 @@ You should be easily able to build this project in your favorite IDE since it's using a plugin that generates your IDE's project files. You can use [sbt-idea](https://github.com/mpeltonen/sbt-idea) for IntelliJ Idea and [sbteclipse](https://github.com/typesafehub/sbteclipse) for Eclipse integration. +[Check our list of contributors!](https://github.com/mauricio/postgresql-async/graphs/contributors) + ## Licence This project is freely available under the Apache 2 licence, fork, fix and send back :) From 40b06b50431c70e2926d2cd072233dee81563c8c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 15:22:07 -0300 Subject: [PATCH 219/357] Improving logging, trace to see network bytes, debug to see prepared statements opening and execute outputs - fixes #77 --- .../db/postgresql/codec/MessageDecoder.scala | 19 ++++++++++----- .../db/postgresql/codec/MessageEncoder.scala | 7 +++++- .../ExecutePreparedStatementEncoder.scala | 3 +-- .../PreparedStatementEncoderHelper.scala | 24 ++++++++++++++++++- .../PreparedStatementOpeningEncoder.scala | 12 ++++++++-- 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala index 68f8abdc..8a3d9fa5 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.codec import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException} import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParsersRegistry} -import com.github.mauricio.async.db.util.Log +import com.github.mauricio.async.db.util.{BufferDumper, Log} import java.nio.charset.Charset import com.github.mauricio.async.db.exceptions.NegativeMessageSizeException import io.netty.handler.codec.ByteToMessageDecoder @@ -33,6 +33,8 @@ object MessageDecoder { class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder.DefaultMaximumSize) extends ByteToMessageDecoder { + import MessageDecoder.log + private val parser = new MessageParsersRegistry(charset) override def decode(ctx: ChannelHandlerContext, b: ByteBuf, out: java.util.List[Object]): Unit = { @@ -54,17 +56,22 @@ class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder } if (b.readableBytes() >= length) { - code match { + + if ( log.isTraceEnabled ) { + log.trace(s"Received buffer ${code}\n${BufferDumper.dumpAsHex(b)}") + } + + val result = code match { case ServerMessage.Authentication => { - val msg = AuthenticationStartupParser.parseMessage(b) - out.add(msg) + AuthenticationStartupParser.parseMessage(b) } case _ => { - val msg = parser.parse(code, b.readSlice(length)) - out.add(msg) + parser.parse(code, b.readSlice(length)) } } + out.add(result) + } else { b.resetReaderIndex() return diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala index f371d0e5..0d1fd4e6 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala @@ -33,6 +33,8 @@ object MessageEncoder { class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) extends MessageToMessageEncoder[Object] { + import MessageEncoder.log + private val executeEncoder = new ExecutePreparedStatementEncoder(charset, encoderRegistry) private val openEncoder = new PreparedStatementOpeningEncoder(charset, encoderRegistry) private val startupEncoder = new StartupMessageEncoder(charset) @@ -54,13 +56,16 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e } encoder.encode(message) - } case _ => { throw new IllegalArgumentException("Can not encode message %s".format(msg)) } } + if (log.isTraceEnabled) { + log.trace(s"Message being sent ${msg.getClass.getName}\n${BufferDumper.dumpAsHex(buffer)}") + } + out.add(buffer) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 1b49138a..375b5043 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -33,10 +33,9 @@ class ExecutePreparedStatementEncoder( def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[PreparedStatementExecuteMessage] - val statementIdBytes = m.statementId.toString.getBytes(charset) - writeExecutePortal( statementIdBytes, m.values, encoder, charset ) + writeExecutePortal( statementIdBytes, m.query, m.values, encoder, charset ) } } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala index 1ad9242a..2ab3df15 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -21,6 +21,7 @@ import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import com.github.mauricio.async.db.column.ColumnEncoderRegistry import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} +import scala.collection.mutable.ArrayBuffer object PreparedStatementEncoderHelper { final val log = Log.get[PreparedStatementEncoderHelper] @@ -32,6 +33,7 @@ trait PreparedStatementEncoderHelper { def writeExecutePortal( statementIdBytes: Array[Byte], + query : String, values: Seq[Any], encoder: ColumnEncoderRegistry, charset: Charset, @@ -52,16 +54,36 @@ trait PreparedStatementEncoderHelper { bindBuffer.writeShort(values.length) + val decodedValues = if ( log.isDebugEnabled ) { + new ArrayBuffer[String](values.size) + } else { + null + } + for (value <- values) { if (value == null || value == None) { bindBuffer.writeInt(-1) + + if (log.isDebugEnabled) { + decodedValues += null + } } else { - val content = encoder.encode(value).getBytes(charset) + val encodedValue = encoder.encode(value) + + if ( log.isDebugEnabled ) { + decodedValues += encodedValue + } + + val content = encodedValue.getBytes(charset) bindBuffer.writeInt(content.length) bindBuffer.writeBytes( content ) } } + if (log.isDebugEnabled) { + log.debug(s"Executing query - statement id (${statementIdBytes.mkString("-")}) - statement ($query) - encoded values (${decodedValues.mkString(", ")}) - original values (${values.mkString(", ")})") + } + bindBuffer.writeShort(0) ByteBufferUtils.writeLength(bindBuffer) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala index 442f5ae0..41263bb1 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementOpeningEncoder.scala @@ -23,12 +23,16 @@ import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} +object PreparedStatementOpeningEncoder { + val log = Log.get[PreparedStatementOpeningEncoder] +} + class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderRegistry) extends Encoder with PreparedStatementEncoderHelper { - private val log = Log.get[PreparedStatementOpeningEncoder] + import PreparedStatementOpeningEncoder.log override def encode(message: ClientMessage): ByteBuf = { @@ -49,13 +53,17 @@ class PreparedStatementOpeningEncoder(charset: Charset, encoder : ColumnEncoderR parseBuffer.writeShort(columnCount) + if ( log.isDebugEnabled ) { + log.debug(s"Opening query (${m.query}) - statement id (${statementIdBytes.mkString("-")}) - selected types (${m.valueTypes.mkString(", ")}) - values (${m.values.mkString(", ")})") + } + for (kind <- m.valueTypes) { parseBuffer.writeInt(kind) } ByteBufferUtils.writeLength(parseBuffer) - val executeBuffer = writeExecutePortal(statementIdBytes, m.values, encoder, charset, true) + val executeBuffer = writeExecutePortal(statementIdBytes, m.query, m.values, encoder, charset, true) Unpooled.wrappedBuffer(parseBuffer, executeBuffer) } From b4e319cb7debfd77e89454f9ec51cbded27abb82 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 15:57:38 -0300 Subject: [PATCH 220/357] Including message tracing for MySQL as well --- .../mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index e5ea7b3b..86527387 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -79,6 +79,12 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM //val dump = MySQLHelper.dumpAsHex(slice) //log.debug(s"[${messagesCount.get()}] Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) + if ( log.isTraceEnabled ) { + log.trace(s"Message received is $messageType - " + + s"(count=$messagesCount,size=$size,isInQuery=$isInQuery,processingColumns=$processingColumns,processingParams=$processingParams,processedColumns=$processedColumns,processedParams=$processedParams)" + + s"\n${BufferDumper.dumpAsHex(buffer)}}") + } + slice.readByte() val decoder = messageType match { From 53ec23c10491090c8cd227f4644c2ac689658290 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 16:08:51 -0300 Subject: [PATCH 221/357] Taking out the trash --- .../mauricio/async/db/util/BitMap.scala | 119 ------------------ .../github/mauricio/async/db/util/Flag.scala | 52 -------- .../mauricio/async/db/util/BitMapSpec.scala | 61 --------- .../mauricio/async/db/util/FlagSpec.scala | 54 -------- .../db/mysql/codec/MySQLFrameDecoder.scala | 5 +- project/Build.scala | 1 - 6 files changed, 1 insertion(+), 291 deletions(-) delete mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala delete mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala delete mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala delete mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala deleted file mode 100644 index 4bab73ba..00000000 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/BitMap.scala +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.util - -import io.netty.buffer.ByteBuf - -object BitMap { - final val Bytes = Array(128, 64, 32, 16, 8, 4, 2, 1) - - def apply(bytes: Byte*): BitMap = new BitMap(bytes.toArray) - - def forSize( totalBits : Int ) : BitMap = { - val quotient = totalBits / 8 - val remainder = totalBits % 8 - val finalSize = if ( remainder == 0 ) quotient else quotient + 1 - new BitMap( new Array[Byte](finalSize) ) - } - - def fromBuffer( totalBits : Int, buffer : ByteBuf ) : BitMap = { - val quotient = totalBits / 8 - val remainder = totalBits % 8 - - val bitMapSource = new Array[Byte](if (remainder == 0) quotient else quotient + 1) - buffer.readBytes(bitMapSource) - - new BitMap(bitMapSource) - } - -} - -/** - * - * Implements a bit map where you can check which bits are set and which are not. - * - * @param bytes - */ - -class BitMap(bytes: Array[Byte]) extends IndexedSeq[(Int, Boolean)] { - - val length = bytes.length * 8 - - /** - * - * Returns true if the bit at the given index is set, false if it is not. - * - * @param index the bit position, starts at 0 - * @return - */ - - def isSet(index: Int): Boolean = { - val quotient = index / 8 - val remainder = index % 8 - - (bytes(quotient) & BitMap.Bytes(remainder)) != 0 - } - - def set(index: Int) { - val quotient = index / 8 - val remainder = index % 8 - - bytes(quotient) = (bytes(quotient) | BitMap.Bytes(remainder)).asInstanceOf[Byte] - } - - override def foreach[U](f: ((Int, Boolean)) => U) { - var currentIndex = 0 - for (byte <- bytes) { - var x = 0 - while (x < BitMap.Bytes.length) { - f(currentIndex, (byte & BitMap.Bytes(x)) != 0) - x += 1 - currentIndex += 1 - } - } - } - - def foreachWithLimit[U](startIndex: Int, length: Int, f: ((Int, Boolean)) => U) { - var currentIndex = startIndex - var start = startIndex / 8 - var x = startIndex % 8 - val limit = length + startIndex - while (start < bytes.length) { - val byte = this.bytes(start) - while (x < BitMap.Bytes.length) { - f(currentIndex, (byte & BitMap.Bytes(x)) != 0) - x += 1 - currentIndex += 1 - - if (currentIndex >= limit) { - return - } - } - x = 0 - start += 1 - } - } - - def apply(idx: Int): (Int, Boolean) = (idx, this.isSet(idx)) - - override def toString: String = this.map(entry => if (entry._2) '1' else '0').mkString("") - - def write( buffer : ByteBuf ) { - buffer.writeBytes(bytes) - } - -} diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala deleted file mode 100644 index dc7fdd98..00000000 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/Flag.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.util - -/** - * - * Simplifies the represenation of a collection of flags. You should subclass this - * class and give it a default collection of flags. - * - * @param mask - * @param possibleValues - */ - -abstract class Flag[T](mask: Int, possibleValues: Map[String, Int]) { - - def +(flag: Int) : T = create(mask | flag) - - def -(flag: Int) : T = create(mask & ~flag) - - def has(flag: Int) : Boolean = hasAll(flag) - - protected def create( value : Int ) : T - - def hasAll(flags: Int*) : Boolean = flags.map { - f => (f & mask) > 0 - }.reduceLeft { - _ && _ - } - - override def toString() : String = { - val cs = possibleValues.filter { - case (key, value) => has(value) - }.map { - case (key,value) => key - }.mkString (", ") - s"${this.getClass.getSimpleName}(" + mask + ": " + cs + ")" - } -} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala deleted file mode 100644 index 4dce05e1..00000000 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/BitMapSpec.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.util - -import org.specs2.mutable.Specification -import scala.collection.mutable.ArrayBuffer - -class BitMapSpec extends Specification { - - "bitmap" should { - - "correctly set and unset bits" in { - - val bitMap = BitMap(0x32, 121) - - bitMap.isSet(0) must beFalse - bitMap.isSet(1) must beFalse - bitMap.isSet(2) must beTrue - bitMap.isSet(3) must beTrue - bitMap.isSet(4) must beFalse - bitMap.isSet(5) must beFalse - bitMap.isSet(6) must beTrue - bitMap.isSet(7) must beFalse - - bitMap.toString === Array("0011", "0010", "0111", "1001").mkString("") - - } - - "correctly foreach over a piece of the bitmap" in { - - val bitMap = BitMap(0, 16) - - val buffer = new ArrayBuffer[Character]() - - bitMap.foreachWithLimit(9, 3, { - case ( index, isNull ) => { - buffer += (if( isNull ) '1' else '0') - } - }) - - buffer.mkString("") === "001" - - } - - } - -} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala deleted file mode 100644 index 6438c465..00000000 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/util/FlagSpec.scala +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.util - -import org.specs2.mutable.Specification - -class FlagSpec extends Specification { - - import SampleFlag._ - - "flag" should { - - "compose on flags correctly" in { - - val flag = new SampleFlag(0) + Flag_1 - flag.has(Flag_1) must beTrue - - } - - } - -} - -object SampleFlag { - - val Flag_1 = 0x0080 - val Flag_2 = 0x0100 - val Flag_3 = 0x0200 - - val FlagMap = Map( - "Flag_1" -> Flag_1, - "Flag_2" -> Flag_2, - "Flag_3" -> Flag_3 - ) - -} - -class SampleFlag( flag : Int ) extends Flag[SampleFlag]( flag, SampleFlag.FlagMap ) { - protected def create(value: Int): SampleFlag = new SampleFlag(value) -} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 86527387..c138ec43 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -74,10 +74,7 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM throw new NegativeMessageSizeException(messageType, size) } - // TODO: Remove once https://github.com/netty/netty/issues/1704 is fixed - val slice = buffer.readSlice(size).order(ByteOrder.LITTLE_ENDIAN) - //val dump = MySQLHelper.dumpAsHex(slice) - //log.debug(s"[${messagesCount.get()}] Dump of message is - $messageType - $size isInQuery $isInQuery processingColumns $processingColumns processedColumns $processedColumns processingParams $processingParams processedParams $processedParams \n{}", dump) + val slice = buffer.readSlice(size) if ( log.isTraceEnabled ) { log.trace(s"Message received is $messageType - " + diff --git a/project/Build.scala b/project/Build.scala index 0a5bc97b..25dbdda9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -52,7 +52,6 @@ object Configuration { val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" val commonDependencies = Seq( - "commons-pool" % "commons-pool" % "1.6", "org.slf4j" % "slf4j-api" % "1.7.5", "joda-time" % "joda-time" % "2.3", "org.joda" % "joda-convert" % "1.5", From 3bf97042341bdc4124666b8124b96aac6d10b34a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 16:20:05 -0300 Subject: [PATCH 222/357] Also print outbound MySQL messages --- CHANGELOG.md | 10 ++++++++++ .../async/db/mysql/codec/MySQLFrameDecoder.scala | 2 +- .../async/db/mysql/codec/MySQLOneToOneEncoder.scala | 8 +++++--- .../async/db/postgresql/codec/MessageEncoder.scala | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dafe7483..b60a8e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.2.11 - 2014-01-11 + +* Driver logs prepared statement data for PostgreSQL calls when logging is set to debug - #77; +* MySQL and PostgreSQL drivers log network bytes read/written when logging is set to trace; +* PostgreSQL now correctly sends JSON to JSON fields without requiring a cast - #75; +* LocalDateTime is not printed (driver fails silently) - #74; +* Support for ENUM types on PostgreSQL - #75; +* Allow configuring the execution context used by PostgreSQL connections - #72 +* Naming executors so we can see the threads created - #71 + ## 0.2.10 - 2013-12-18 * Removed application_name from PostgreSQL default connection values - #70 diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index c138ec43..61fa263c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -77,7 +77,7 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM val slice = buffer.readSlice(size) if ( log.isTraceEnabled ) { - log.trace(s"Message received is $messageType - " + + log.trace(s"Reading message is $messageType - " + s"(count=$messagesCount,size=$size,isInQuery=$isInQuery,processingColumns=$processingColumns,processingParams=$processingParams,processedColumns=$processedColumns,processedParams=$processedParams)" + s"\n${BufferDumper.dumpAsHex(buffer)}}") } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 459f0e7e..175a783c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -21,7 +21,7 @@ import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import com.github.mauricio.async.db.mysql.encoder._ import com.github.mauricio.async.db.mysql.message.client.ClientMessage import com.github.mauricio.async.db.mysql.util.CharsetMapper -import com.github.mauricio.async.db.util.{ByteBufferUtils, Log} +import com.github.mauricio.async.db.util.{BufferDumper, ByteBufferUtils, Log} import java.nio.charset.Charset import scala.annotation.switch import io.netty.channel.ChannelHandlerContext @@ -69,14 +69,16 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten case _ => throw new EncoderNotAvailableException(message) } - //log.debug("Writing message {}", message) - val result = encoder.encode(message) ByteBufferUtils.writePacketLength(result, sequence) sequence += 1 + if ( log.isTraceEnabled ) { + log.trace(s"Writing message ${message.getClass.getName} - \n${BufferDumper.dumpAsHex(result)}") + } + out.add(result) } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala index 0d1fd4e6..5cf5d480 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala @@ -63,7 +63,7 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e } if (log.isTraceEnabled) { - log.trace(s"Message being sent ${msg.getClass.getName}\n${BufferDumper.dumpAsHex(buffer)}") + log.trace(s"Sending message ${msg.getClass.getName}\n${BufferDumper.dumpAsHex(buffer)}") } out.add(buffer) From aed6c45ef29e0dc3d1ff230b600333d563061a17 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 16:38:06 -0300 Subject: [PATCH 223/357] Default test log level is now info --- mysql-async/src/test/resources/logback.xml | 2 +- postgresql-async/src/test/resources/logback.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-async/src/test/resources/logback.xml b/mysql-async/src/test/resources/logback.xml index 3d3f5dad..99797b1d 100644 --- a/mysql-async/src/test/resources/logback.xml +++ b/mysql-async/src/test/resources/logback.xml @@ -13,7 +13,7 @@ - + diff --git a/postgresql-async/src/test/resources/logback.xml b/postgresql-async/src/test/resources/logback.xml index 30bf6dbe..a565a966 100644 --- a/postgresql-async/src/test/resources/logback.xml +++ b/postgresql-async/src/test/resources/logback.xml @@ -13,7 +13,7 @@ - + From 7f5e53494f5296d7b31ec79707248bb41c56457c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 17:47:43 -0300 Subject: [PATCH 224/357] Implement LISTEN/NOTIFY support for PostgreSQL --- .../db/postgresql/PostgreSQLConnection.scala | 25 ++- .../codec/PostgreSQLConnectionDelegate.scala | 1 + .../codec/PostgreSQLConnectionHandler.scala | 4 + .../encoders/QueryMessageEncoder.scala | 12 +- .../backend/NotificationResponse.scala | 20 ++ .../messages/backend/ServerMessage.scala | 2 +- .../parsers/MessageParsersRegistry.scala | 2 + .../parsers/NotificationResponseParser.scala | 31 +++ .../src/test/resources/logback.xml | 2 +- .../db/postgresql/ListenNotifySpec.scala | 197 ++++++++++++++++++ 10 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NotificationResponse.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NotificationResponseParser.scala create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ListenNotifySpec.scala 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 fbba6635..82b5a5ce 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 @@ -31,6 +31,7 @@ import messages.frontend._ import scala.Some import scala.concurrent._ import io.netty.channel.EventLoopGroup +import java.util.concurrent.CopyOnWriteArrayList object PostgreSQLConnection { val Counter = new AtomicLong() @@ -74,6 +75,7 @@ class PostgreSQLConnection private var currentQuery: Option[MutableResultSet[PostgreSQLColumnData]] = None private var currentPreparedStatement: Option[PreparedStatementHolder] = None private var version = Version(0,0,0) + private var notifyListeners = new CopyOnWriteArrayList[NotificationResponse => Unit]() private var queryResult: Option[QueryResult] = None @@ -224,6 +226,25 @@ class PostgreSQLConnection } + override def onNotificationResponse( message : NotificationResponse ) { + val iterator = this.notifyListeners.iterator() + while ( iterator.hasNext ) { + iterator.next().apply(message) + } + } + + def registerNotifyListener( listener : NotificationResponse => Unit ) { + this.notifyListeners.add(listener) + } + + def unregisterNotifyListener( listener : NotificationResponse => Unit ) { + this.notifyListeners.remove(listener) + } + + def clearNotifyListeners() { + this.notifyListeners.clear() + } + private def credential(authenticationMessage: AuthenticationChallengeMessage): CredentialMessage = { if (configuration.username != null && configuration.password.isDefined) { new CredentialMessage( @@ -248,7 +269,7 @@ class PostgreSQLConnection ) } - def validateIfItIsReadyForQuery(errorMessage: String) = + def validateIfItIsReadyForQuery(errorMessage: String) = if (this.queryPromise.isDefined) notReadyForQueryError(errorMessage, false) @@ -285,7 +306,7 @@ class PostgreSQLConnection } } - def write( message : ClientMessage ) { + private def write( message : ClientMessage ) { this.connectionHandler.write(message) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala index 939646ee..bde1f230 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionDelegate.scala @@ -28,5 +28,6 @@ trait PostgreSQLConnectionDelegate { def onParameterStatus( message : ParameterStatusMessage ) def onReadyForQuery() def onRowDescription(message : RowDescriptionMessage) + def onNotificationResponse(message : NotificationResponse ) } \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index 9e34d4f4..b53821ee 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -158,6 +158,10 @@ class PostgreSQLConnectionHandler case ServerMessage.NoData => { } case ServerMessage.Notice => { + log.info("Received notice {}", m) + } + case ServerMessage.NotificationResponse => { + connectionDelegate.onNotificationResponse(m.asInstanceOf[NotificationResponse]) } case ServerMessage.ParameterStatus => { connectionDelegate.onParameterStatus(m.asInstanceOf[ParameterStatusMessage]) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala index 579e86b9..b368670a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/QueryMessageEncoder.scala @@ -18,16 +18,26 @@ package com.github.mauricio.async.db.postgresql.encoders import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{QueryMessage, ClientMessage} -import com.github.mauricio.async.db.util.ByteBufferUtils +import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} +object QueryMessageEncoder { + val log = Log.get[QueryMessageEncoder] +} + class QueryMessageEncoder(charset: Charset) extends Encoder { + import QueryMessageEncoder.log + override def encode(message: ClientMessage): ByteBuf = { val m = message.asInstanceOf[QueryMessage] + if ( log.isDebugEnabled ) { + log.debug("Executing direct query ({})", m.query) + } + val buffer = Unpooled.buffer() buffer.writeByte(ServerMessage.Query) buffer.writeInt(0) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NotificationResponse.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NotificationResponse.scala new file mode 100644 index 00000000..ab911160 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/NotificationResponse.scala @@ -0,0 +1,20 @@ +/* + * Copyright 2013-2014 db-async-common + * + * The db-async-common project 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.messages.backend + +class NotificationResponse( val backendPid : Int, val channel : String, val payload : String ) + extends ServerMessage(ServerMessage.NotificationResponse) \ No newline at end of file diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala index 0ef530d0..c413ef4e 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala @@ -34,7 +34,7 @@ object ServerMessage { final val EmptyQueryString = 'I' final val NoData = 'n' final val Notice = 'N' - final val Notification = 'A' + final val NotificationResponse = 'A' final val ParameterStatus = 'S' final val Parse = 'P' final val ParseComplete = '1' diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala index 6770d6dc..9346a1e9 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/MessageParsersRegistry.scala @@ -28,6 +28,7 @@ class MessageParsersRegistry(charset: Charset) { private val noticeParser = new NoticeParser(charset) private val parameterStatusParser = new ParameterStatusParser(charset) private val rowDescriptionParser = new RowDescriptionParser(charset) + private val notificationResponseParser = new NotificationResponseParser(charset) private def parserFor(t: Byte): MessageParser = { t match { @@ -41,6 +42,7 @@ class MessageParsersRegistry(charset: Charset) { case ServerMessage.EmptyQueryString => ReturningMessageParser.EmptyQueryStringMessageParser case ServerMessage.NoData => ReturningMessageParser.NoDataMessageParser case ServerMessage.Notice => this.noticeParser + case ServerMessage.NotificationResponse => this.notificationResponseParser case ServerMessage.ParameterStatus => this.parameterStatusParser case ServerMessage.ParseComplete => ReturningMessageParser.ParseCompleteMessageParser case ServerMessage.RowDescription => this.rowDescriptionParser diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NotificationResponseParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NotificationResponseParser.scala new file mode 100644 index 00000000..e01dec7f --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/parsers/NotificationResponseParser.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2014 db-async-common + * + * The db-async-common project 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.parsers + +import language.implicitConversions +import java.nio.charset.Charset +import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.postgresql.messages.backend.{NotificationResponse, ServerMessage} +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper + +class NotificationResponseParser( charset : Charset ) extends MessageParser { + + def parseMessage(buffer: ByteBuf): ServerMessage = { + new NotificationResponse( buffer.readInt(), buffer.readCString(charset), buffer.readCString(charset) ) + } + +} \ No newline at end of file diff --git a/postgresql-async/src/test/resources/logback.xml b/postgresql-async/src/test/resources/logback.xml index a565a966..30bf6dbe 100644 --- a/postgresql-async/src/test/resources/logback.xml +++ b/postgresql-async/src/test/resources/logback.xml @@ -13,7 +13,7 @@ - + diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ListenNotifySpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ListenNotifySpec.scala new file mode 100644 index 00000000..b1d72097 --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ListenNotifySpec.scala @@ -0,0 +1,197 @@ +/* + * Copyright 2013-2014 db-async-common + * + * The db-async-common project 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 + +import org.specs2.mutable.Specification +import java.util.UUID +import com.github.mauricio.async.db.postgresql.messages.backend.NotificationResponse + +class ListenNotifySpec extends Specification with DatabaseTestHelper { + + def generateQueueName() = "scala_pg_async_test_" + UUID.randomUUID().toString.replaceAll("-", "") + + "connection" should { + + "should be able to receive a notification if listening" in { + + withHandler { + connection => + + val queue = generateQueueName() + + executeQuery(connection, s"LISTEN $queue") + + var payload = "" + var channel = "" + + connection.registerNotifyListener((message) => { + payload = message.payload + channel = message.channel + }) + + executeQuery(connection, s"NOTIFY $queue, 'this-is-some-data'") + + Thread.sleep(1000) + + payload === "this-is-some-data" + channel === queue + + connection.hasRecentError must beFalse + } + + } + + "should be able to receive a notification from a pg_notify call" in { + + withHandler { + connection => + val queue = generateQueueName() + + executeQuery(connection, s"LISTEN $queue") + + var payload = "" + var channel = "" + + connection.registerNotifyListener((message) => { + payload = message.payload + channel = message.channel + }) + + executePreparedStatement(connection, "SELECT pg_notify(?, ?)", Array(queue, "notifying-again")) + + Thread.sleep(1000) + + payload === "notifying-again" + channel === queue + } + + } + + "should not receive any notification if not registered to the correct channel" in { + + withHandler { + connection => + + var queue = generateQueueName() + var otherQueue = generateQueueName() + + executeQuery(connection, s"LISTEN $queue") + + var payload = "" + var channel = "" + + connection.registerNotifyListener((message) => { + payload = message.payload + channel = message.channel + }) + + executePreparedStatement(connection, "SELECT pg_notify(?, ?)", Array(otherQueue, "notifying-again")) + + Thread.sleep(1000) + + payload === "" + channel === "" + } + + } + + "should not receive notifications if cleared the collection" in { + + withHandler { + connection => + val queue = generateQueueName() + + executeQuery(connection, s"LISTEN $queue") + + var payload = "" + var channel = "" + + connection.registerNotifyListener((message) => { + payload = message.payload + channel = message.channel + }) + + connection.clearNotifyListeners() + + executeQuery(connection, s"NOTIFY $queue, 'this-is-some-data'") + + Thread.sleep(1000) + + payload === "" + channel === "" + } + + } + + "should not receive notification if listener was removed" in { + + withHandler { + connection => + val queue = generateQueueName() + + executeQuery(connection, s"LISTEN $queue") + + var payload = "" + var channel = "" + + val listener : NotificationResponse => Unit = (message) => { + payload = message.payload + channel = message.channel + } + + connection.registerNotifyListener(listener) + connection.unregisterNotifyListener(listener) + + executeQuery(connection, s"NOTIFY $queue, 'this-is-some-data'") + + Thread.sleep(1000) + + payload === "" + channel === "" + } + + } + + "should be able to receive notify without payload" in { + withHandler { + connection => + val queue = generateQueueName() + + executeQuery(connection, s"LISTEN $queue") + + var payload = "this is some fake payload" + var channel = "" + + val listener : NotificationResponse => Unit = (message) => { + payload = message.payload + channel = message.channel + } + + connection.registerNotifyListener(listener) + + executeQuery(connection, s"NOTIFY $queue") + + Thread.sleep(1000) + + payload === "" + channel === queue + } + } + + } + +} From 1052490cffce1f3893788ee9b3d457a1a85e9f3c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 17:49:35 -0300 Subject: [PATCH 225/357] Updating changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b60a8e40..2efd8238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.2.11 - 2014-01-11 +* LISTEN/NOTIFY support for PostgreSQL * Driver logs prepared statement data for PostgreSQL calls when logging is set to debug - #77; * MySQL and PostgreSQL drivers log network bytes read/written when logging is set to trace; * PostgreSQL now correctly sends JSON to JSON fields without requiring a cast - #75; From 7ae9cbd335e751860ead0964b099ecb99cd4e647 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 17:50:11 -0300 Subject: [PATCH 226/357] Closing 0.2.11 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 25dbdda9..39197907 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.11-SNAPSHOT" + val commonVersion = "0.2.11" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From a02fab4d70e952aa491041ebb7c430f87aabde99 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 17:56:58 -0300 Subject: [PATCH 227/357] Start 0.2.12 development process --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 39197907..bfebec2f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.11" + val commonVersion = "0.2.12-SNAPSHOT" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From 92432809c421b5f4a4befe61a7c8dca0fba6337d Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 11 Jan 2014 18:03:55 -0300 Subject: [PATCH 228/357] Updating readmes --- README.markdown | 10 ++++++---- postgresql-async/README.md | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.markdown b/README.markdown index 05bc4c6f..0caaf0d3 100644 --- a/README.markdown +++ b/README.markdown @@ -11,12 +11,14 @@ This project always returns [JodaTime](http://joda-time.sourceforge.net/) when d If you want information specific to the drivers, check the [PostgreSQL README](postgresql-async/README.md) and the [MySQL README](mysql-async/README.md). +You can view the project's [CHANGELOG here](CHANGELOG.md). + ## Include them as dependencies 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.10" +"com.github.mauricio" %% "postgresql-async" % "0.2.11" ``` Or Maven: @@ -24,7 +26,7 @@ Or Maven: ```xml com.github.mauricio - postgresql-async_2.10 + postgresql-async_2.11 0.2.10 ``` @@ -32,7 +34,7 @@ Or Maven: And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.10" +"com.github.mauricio" %% "mysql-async" % "0.2.11" ``` Or Maven: @@ -41,7 +43,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.10 + 0.2.11 ``` diff --git a/postgresql-async/README.md b/postgresql-async/README.md index c15425f6..dc4fde05 100644 --- a/postgresql-async/README.md +++ b/postgresql-async/README.md @@ -20,6 +20,7 @@ This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql - date, time and timestamp types are handled as JodaTime objects and **not** as **java.util.Date** objects - all work is done using the new `scala.concurrent.Future` and `scala.concurrent.Promise` objects - support for Byte arrays if using PostgreSQL >= 9.0 +- support for LISTEN/NOTIFY operations (check [ListenNotifySpec](https://github.com/mauricio/postgresql-async/blob/master/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ListenNotifySpec.scala) for an example on how to use it ); ## What is missing? From 18dcb59d0b986fed7ff761da507f7eff089ef9cc Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 Jan 2014 00:58:20 -0300 Subject: [PATCH 229/357] Include reference to ScalikeJBDC-Async --- README.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.markdown b/README.markdown index 0caaf0d3..31e9b2d6 100644 --- a/README.markdown +++ b/README.markdown @@ -13,6 +13,10 @@ If you want information specific to the drivers, check the [PostgreSQL README](p You can view the project's [CHANGELOG here](CHANGELOG.md). +And, just in case, this is a database driver. You will have to write SQL to use it. If you're looking for something + higher level, you should check [ScalikeJDBC-Async](https://github.com/scalikejdbc/scalikejdbc-async), that provides + a higher level API on top of these drivers. + ## Include them as dependencies And if you're in a hurry, you can include them in your build like this, if you're using PostgreSQL: From 4a1fa9d59a012141f14b33267a1ec18c0d02ef1c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 Jan 2014 09:10:57 -0300 Subject: [PATCH 230/357] Fixing transaction example - fixes #78 --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 31e9b2d6..690e2c18 100644 --- a/README.markdown +++ b/README.markdown @@ -173,7 +173,7 @@ Here's an example of how transactions work: val future = connection.inTransaction { c => c.sendPreparedStatement(this.insert) - .flatMap( r => connection.sendPreparedStatement(this.insert)) + .flatMap( r => c.sendPreparedStatement(this.insert)) } ``` From 262c4afc82d4818db9fb9f9de892696d67c8c7fc Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Mon, 13 Jan 2014 19:45:14 -0500 Subject: [PATCH 231/357] Fix issues link in README It was linking to an issues file in the repository. --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 690e2c18..95d66e90 100644 --- a/README.markdown +++ b/README.markdown @@ -110,7 +110,7 @@ So, prepared statements are awesome, but are not free. Use them judiciously. - checkout the source code - find bugs, find places where performance can be improved - check the **What is missing** piece -- check the [issues page](issues) for bugs or new features +- check the [issues page](https://github.com/mauricio/postgresql-async/issues) for bugs or new features - send a pull request with specs ## Main public interface From ae04a22030afdb5ff98c20551ca290f0fae2e7fb Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 15 Jan 2014 01:44:36 -0300 Subject: [PATCH 232/357] Including reference to Activate at the readme --- README.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 95d66e90..ac8c8dd8 100644 --- a/README.markdown +++ b/README.markdown @@ -15,7 +15,8 @@ You can view the project's [CHANGELOG here](CHANGELOG.md). And, just in case, this is a database driver. You will have to write SQL to use it. If you're looking for something higher level, you should check [ScalikeJDBC-Async](https://github.com/scalikejdbc/scalikejdbc-async), that provides - a higher level API on top of these drivers. + a higher level API on top of these drivers and the [Activate Framework](http://activate-framework.org/) that providesa + a distributed ORM on top of them. ## Include them as dependencies From 79c27291d020a92675af11e97a5ea0922c93b203 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 15 Jan 2014 10:13:06 -0300 Subject: [PATCH 233/357] Including the integrations as a list --- README.markdown | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.markdown b/README.markdown index ac8c8dd8..633bfbbf 100644 --- a/README.markdown +++ b/README.markdown @@ -13,10 +13,14 @@ If you want information specific to the drivers, check the [PostgreSQL README](p You can view the project's [CHANGELOG here](CHANGELOG.md). -And, just in case, this is a database driver. You will have to write SQL to use it. If you're looking for something - higher level, you should check [ScalikeJDBC-Async](https://github.com/scalikejdbc/scalikejdbc-async), that provides - a higher level API on top of these drivers and the [Activate Framework](http://activate-framework.org/) that providesa - a distributed ORM on top of them. +## Abstractions and integrations + +* [Activate Framework](http://activate-framework.org/) - full ORM solution for persisting objects using a software + transactional memory (STM) layer; +* [ScalikeJDBC-Async](https://github.com/scalikejdbc/scalikejdbc-async) - provides an abstraction layer on top of the + driver allowing you to write less SQL and make use of a nice high level database access API; +* [mod-mysql-postgresql](https://github.com/vert-x/mod-mysql-postgresql) - [vert.x](http://vertx.io/) module that integrates + the driver into a vert.x application; ## Include them as dependencies From 37c85b6e7178d02cfdec8e7226d2a1b8f00294c5 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 18 Jan 2014 15:14:48 -0300 Subject: [PATCH 234/357] Do not check for handshake if we are already running queries - fixes #80 --- .../db/mysql/codec/MySQLFrameDecoder.scala | 6 +-- .../mauricio/async/db/mysql/QuerySpec.scala | 40 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 61fa263c..ad70ffc7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -77,15 +77,15 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM val slice = buffer.readSlice(size) if ( log.isTraceEnabled ) { - log.trace(s"Reading message is $messageType - " + + log.trace(s"Reading message type $messageType - " + s"(count=$messagesCount,size=$size,isInQuery=$isInQuery,processingColumns=$processingColumns,processingParams=$processingParams,processedColumns=$processedColumns,processedParams=$processedParams)" + - s"\n${BufferDumper.dumpAsHex(buffer)}}") + s"\n${BufferDumper.dumpAsHex(slice)}}") } slice.readByte() val decoder = messageType match { - case ServerMessage.ServerProtocolVersion => this.handshakeDecoder + case ServerMessage.ServerProtocolVersion if !isInQuery => this.handshakeDecoder case ServerMessage.Error => { this.clear this.errorDecoder diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 86ade7ae..2f505169 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -223,6 +223,46 @@ class QuerySpec extends Specification with ConnectionHelper { } + "select from another empty table with many columns" in { + withConnection { + connection => + val create = """create temporary table test_11 ( + | id int primary key not null, + | c2 text not null, c3 text not null, c4 text not null, + | c5 text not null, c6 text not null, c7 text not null, + | c8 text not null, c9 text not null, c10 text not null, + | c11 text not null + |) ENGINE=InnoDB DEFAULT CHARSET=utf8;""".stripMargin + + executeQuery(connection, create) + + val result = executeQuery(connection, "select * from test_11") + + result.rows.get.size === 0 + } + } + + "select from an empty table with many columns" in { + + withConnection { + connection => + + val create = """create temporary table test_10 ( + | id int primary key not null, + | c2 text not null, c3 text not null, c4 text not null, + | c5 text not null, c6 text not null, c7 text not null, + | c8 text not null, c9 text not null, c10 text not null + |) ENGINE=InnoDB DEFAULT CHARSET=utf8;""".stripMargin + + executeQuery(connection, create) + + val result = executeQuery(connection, "select * from test_10") + + result.rows.get.size === 0 + } + + } + } } From de01a778ce38f5a81db4332c6082cfc3351b3e12 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 15 Feb 2014 11:49:20 -0300 Subject: [PATCH 235/357] Workaround for string issue --- .../db/postgresql/PreparedStatementSpec.scala | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 06b80967..66f7a57b 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -217,31 +217,41 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { "support handling JSON type" in { - pending("travis-ci PG doesn't have the JSON type") + if ( System.getenv("TRAVIS") == null ) { + withHandler { + handler => + val create = """create temp table people + |( + |id bigserial primary key, + |addresses json, + |phones json + |);""".stripMargin + + val insert = "INSERT INTO people (addresses, phones) VALUES (?,?) RETURNING id" + val select = "SELECT * FROM people" + val addresses = """[ {"Home" : {"city" : "Tahoe", "state" : "CA"}} ]""" + val phones = """[ "925-575-0415", "916-321-2233" ]""" + + executeDdl(handler, create) + executePreparedStatement(handler, insert, Array(addresses, phones) ) + val result = executePreparedStatement(handler, select).rows.get + + result(0)("addresses") === addresses + result(0)("phones") === phones + } + success + } else { + pending + } + } + "support select bind value" in { withHandler { handler => - val create = """create temp table people - |( - |id bigserial primary key, - |addresses json, - |phones json - |);""".stripMargin - - val insert = "INSERT INTO people (addresses, phones) VALUES (?,?) RETURNING id" - val select = "SELECT * FROM people" - val addresses = """[ {"Home" : {"city" : "Tahoe", "state" : "CA"}} ]""" - val phones = """[ "925-575-0415", "916-321-2233" ]""" - - executeDdl(handler, create) - executePreparedStatement(handler, insert, Array(addresses, phones) ) - val result = executePreparedStatement(handler, select).rows.get - - result(0)("addresses") === addresses - result(0)("phones") === phones - + val string = "someString" + val result = executePreparedStatement(handler, "SELECT CAST(? AS VARCHAR)", Array(string)).rows.get + result(0)(0) == string } - } } From 23318990130d7197614f7199eb7e67b847bfc75d Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 15 Feb 2014 11:50:28 -0300 Subject: [PATCH 236/357] Closing 0.2.12 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index bfebec2f..a0d477e4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.12-SNAPSHOT" + val commonVersion = "0.2.12" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From 5b04723694bdf0ea11aceb67e161578ef9cad44a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 15 Feb 2014 12:00:14 -0300 Subject: [PATCH 237/357] Starting next development process --- CHANGELOG.md | 4 ++++ README.markdown | 10 +++++----- project/Build.scala | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2efd8238..97dab08e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.2.12 - 2014-01-11 + +* Do not check for handshake requests after a real handshake has happened already - MySQL - #80; + ## 0.2.11 - 2014-01-11 * LISTEN/NOTIFY support for PostgreSQL diff --git a/README.markdown b/README.markdown index 633bfbbf..b82dd377 100644 --- a/README.markdown +++ b/README.markdown @@ -27,7 +27,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.11" +"com.github.mauricio" %% "postgresql-async" % "0.2.12" ``` Or Maven: @@ -35,15 +35,15 @@ Or Maven: ```xml com.github.mauricio - postgresql-async_2.11 - 0.2.10 + postgresql-async_2.10 + 0.2.12 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.11" +"com.github.mauricio" %% "mysql-async" % "0.2.12" ``` Or Maven: @@ -52,7 +52,7 @@ Or Maven: com.github.mauricio mysql-async_2.10 - 0.2.11 + 0.2.12 ``` diff --git a/project/Build.scala b/project/Build.scala index a0d477e4..40c6465d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.12" + val commonVersion = "0.2.13-SNAPSHOT" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From 701c9dcf4be605c4fdaff8107bf3e76271b44a0f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 28 Feb 2014 10:55:21 -0300 Subject: [PATCH 238/357] Accepting unsafe mysql_old_password auth method - fixes #37 --- .../async/db/mysql/MySQLConnection.scala | 19 ++--- .../mysql/codec/MySQLConnectionHandler.scala | 56 +++++++++----- .../db/mysql/codec/MySQLFrameDecoder.scala | 11 ++- .../db/mysql/codec/MySQLHandlerDelegate.scala | 3 +- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 5 ++ .../AuthenticationSwitchRequestDecoder.scala | 15 ++++ .../AuthenticationSwitchResponseEncoder.scala | 27 +++++++ .../encoder/HandshakeResponseEncoder.scala | 6 +- .../encoder/auth/AuthenticationMethod.scala | 11 ++- .../MySQLNativePasswordAuthentication.scala | 14 ++-- .../auth/OldPasswordAuthentication.scala | 76 +++++++++++++++++++ .../client/AuthenticationSwitchResponse.scala | 6 ++ .../mysql/message/client/ClientMessage.scala | 1 + .../server/AuthenticationSwitchRequest.scala | 6 ++ mysql-async/src/test/resources/logback.xml | 2 +- .../async/db/mysql/OldPasswordSpec.scala | 35 +++++++++ project/Build.scala | 2 +- script/prepare_build.sh | 2 + 18 files changed, 247 insertions(+), 50 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/AuthenticationSwitchRequestDecoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/AuthenticationSwitchResponseEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/OldPasswordAuthentication.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/AuthenticationSwitchResponse.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/AuthenticationSwitchRequest.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 9621dde8..0bae9cc1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -26,21 +26,11 @@ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import java.util.concurrent.atomic.{AtomicLong,AtomicReference} -import scala.Some import scala.concurrent.{ExecutionContext, Promise, Future} -import scala.util.Failure -import scala.util.Success import io.netty.channel.{EventLoopGroup, ChannelHandlerContext} -import com.github.mauricio.async.db.mysql.message.server.HandshakeMessage -import com.github.mauricio.async.db.mysql.message.client.HandshakeResponseMessage -import com.github.mauricio.async.db.mysql.message.server.ErrorMessage -import com.github.mauricio.async.db.mysql.message.client.QueryMessage import scala.util.Failure import scala.Some -import com.github.mauricio.async.db.mysql.message.server.OkMessage -import com.github.mauricio.async.db.mysql.message.client.PreparedStatementMessage import scala.util.Success -import com.github.mauricio.async.db.mysql.message.server.EOFMessage object MySQLConnection { final val Counter = new AtomicLong() @@ -95,9 +85,6 @@ class MySQLConnection( } def close: Future[Connection] = { - - log.debug("Closing connection") - if ( this.isConnected ) { if (!this.disconnectionPromise.isCompleted) { val exception = new DatabaseException("Connection is being closed") @@ -125,7 +112,7 @@ class MySQLConnection( } override def exceptionCaught(throwable: Throwable) { - log.error("Transport failure", throwable) + log.error("Transport failure ", throwable) setException(throwable) } @@ -190,6 +177,10 @@ class MySQLConnection( )) } + override def switchAuthentication( message : AuthenticationSwitchRequest ) { + this.connectionHandler.write(new AuthenticationSwitchResponse( configuration.password, message )) + } + def sendQuery(query: String): Future[QueryResult] = { this.validateIsReadyForQuery() val promise = Promise[QueryResult] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index ad703c1d..d70b3623 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -34,6 +34,7 @@ import scala.Some import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent._ +import com.github.mauricio.async.db.exceptions.DatabaseException class MySQLConnectionHandler( configuration: Configuration, @@ -85,9 +86,6 @@ class MySQLConnectionHandler( } override def channelRead0(ctx: ChannelHandlerContext, message: Object) { - - //log.debug("Message received {}", message) - message match { case m: ServerMessage => { (m.kind: @switch) match { @@ -103,16 +101,7 @@ class MySQLConnectionHandler( handlerDelegate.onError(m.asInstanceOf[ErrorMessage]) } case ServerMessage.EOF => { - - val resultSet = this.currentQuery - this.clearQueryState - - if ( resultSet != null ) { - handlerDelegate.onResultSet( resultSet, m.asInstanceOf[EOFMessage] ) - } else { - handlerDelegate.onEOF(m.asInstanceOf[EOFMessage]) - } - + this.handleEOF(m) } case ServerMessage.ColumnDefinition => { val message = m.asInstanceOf[ColumnDefinitionMessage] @@ -162,9 +151,15 @@ class MySQLConnectionHandler( } override def channelActive(ctx: ChannelHandlerContext): Unit = { + log.debug("Channel became active") handlerDelegate.connected(ctx) } + + override def channelInactive(ctx: ChannelHandlerContext) { + log.debug("Channel became inactive") + } + override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { // unwrap CodecException if needed cause match { @@ -212,6 +207,8 @@ class MySQLConnectionHandler( writeAndHandleError(message) } + def write( message : AuthenticationSwitchResponse ) : ChannelFuture = writeAndHandleError(message) + def write( message : QuitMessage ) : ChannelFuture = { writeAndHandleError(message) } @@ -225,7 +222,7 @@ class MySQLConnectionHandler( } def isConnected : Boolean = { - if ( this.currentContext != null ) { + if ( this.currentContext != null && this.currentContext.channel() != null ) { this.currentContext.channel.isActive } else { false @@ -267,13 +264,36 @@ class MySQLConnectionHandler( } private def writeAndHandleError( message : Any ) : ChannelFuture = { - val future = this.currentContext.writeAndFlush(message) - future.onFailure { - case e : Throwable => handleException(e) + if ( this.currentContext.channel().isActive ) { + val future = this.currentContext.writeAndFlush(message) + + future.onFailure { + case e : Throwable => handleException(e) + } + + future + } else { + throw new DatabaseException("This channel is not active and can't take messages") } + } - future + private def handleEOF( m : ServerMessage ) { + m match { + case eof : EOFMessage => { + val resultSet = this.currentQuery + this.clearQueryState + + if ( resultSet != null ) { + handlerDelegate.onResultSet( resultSet, eof ) + } else { + handlerDelegate.onEOF(eof) + } + } + case authenticationSwitch : AuthenticationSwitchRequest => { + handlerDelegate.switchAuthentication(authenticationSwitch) + } + } } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index ad70ffc7..3d92929f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -40,6 +40,7 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM private final val columnDecoder = new ColumnDefinitionDecoder(charset, new DecoderRegistry(charset)) private final val rowDecoder = new ResultSetRowDecoder(charset) private final val preparedStatementPrepareDecoder = new PreparedStatementPrepareResponseDecoder() + private final val authenticationSwitchDecoder = new AuthenticationSwitchRequestDecoder(charset) private[codec] var processingColumns = false private[codec] var processingParams = false @@ -104,8 +105,14 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM this.processingColumns = false ColumnProcessingFinishedDecoder } else { - this.clear - EOFMessageDecoder + + if ( this.isInQuery ) { + this.clear + EOFMessageDecoder + } else { + this.authenticationSwitchDecoder + } + } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala index 6663dc96..10c25df7 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLHandlerDelegate.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.mysql.codec import com.github.mauricio.async.db.ResultSet -import com.github.mauricio.async.db.mysql.message.server.{EOFMessage, OkMessage, ErrorMessage, HandshakeMessage} +import com.github.mauricio.async.db.mysql.message.server._ import io.netty.channel.ChannelHandlerContext trait MySQLHandlerDelegate { @@ -29,5 +29,6 @@ trait MySQLHandlerDelegate { def exceptionCaught( exception : Throwable ) def connected( ctx : ChannelHandlerContext ) def onResultSet( resultSet : ResultSet, message : EOFMessage ) + def switchAuthentication( message : AuthenticationSwitchRequest ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 175a783c..074a8b6a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -41,6 +41,7 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten private final val rowEncoder = new BinaryRowEncoder(charset) private final val prepareEncoder = new PreparedStatementPrepareEncoder(charset) private final val executeEncoder = new PreparedStatementExecuteEncoder(rowEncoder) + private final val authenticationSwitchEncoder = new AuthenticationSwitchResponseEncoder(charset) private var sequence = 1 @@ -66,6 +67,10 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten sequence = 0 this.prepareEncoder } + case ClientMessage.AuthSwitchResponse => { + sequence += 1 + this.authenticationSwitchEncoder + } case _ => throw new EncoderNotAvailableException(message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/AuthenticationSwitchRequestDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/AuthenticationSwitchRequestDecoder.scala new file mode 100644 index 00000000..d96abfd0 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/AuthenticationSwitchRequestDecoder.scala @@ -0,0 +1,15 @@ +package com.github.mauricio.async.db.mysql.decoder + +import java.nio.charset.Charset +import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.mysql.message.server.{AuthenticationSwitchRequest, ServerMessage} +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper + +class AuthenticationSwitchRequestDecoder( charset : Charset ) extends MessageDecoder { + def decode(buffer: ByteBuf): ServerMessage = { + new AuthenticationSwitchRequest( + buffer.readCString(charset), + buffer.readUntilEOF(charset) + ) + } +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/AuthenticationSwitchResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/AuthenticationSwitchResponseEncoder.scala new file mode 100644 index 00000000..6d7a0e7c --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/AuthenticationSwitchResponseEncoder.scala @@ -0,0 +1,27 @@ +package com.github.mauricio.async.db.mysql.encoder + +import com.github.mauricio.async.db.mysql.message.client.{AuthenticationSwitchResponse, ClientMessage} +import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException +import com.github.mauricio.async.db.mysql.encoder.auth.AuthenticationMethod +import java.nio.charset.Charset +import com.github.mauricio.async.db.util.ByteBufferUtils + +class AuthenticationSwitchResponseEncoder( charset : Charset ) extends MessageEncoder { + + def encode(message: ClientMessage): ByteBuf = { + val switch = message.asInstanceOf[AuthenticationSwitchResponse] + + val method = switch.request.method + val authenticator = AuthenticationMethod.Availables.getOrElse( + method, { throw new UnsupportedAuthenticationMethodException(method) }) + + val buffer = ByteBufferUtils.packetBuffer() + + val bytes = authenticator.generateAuthentication(charset, switch.password, switch.request.seed.getBytes(charset) ) + buffer.writeBytes(bytes) + + buffer + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala index e74b050e..b60bf33d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.mysql.encoder import io.netty.buffer.ByteBuf import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException -import com.github.mauricio.async.db.mysql.encoder.auth.MySQLNativePasswordAuthentication +import com.github.mauricio.async.db.mysql.encoder.auth.{AuthenticationMethod, MySQLNativePasswordAuthentication} import com.github.mauricio.async.db.mysql.message.client.{HandshakeResponseMessage, ClientMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} @@ -47,7 +47,7 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e import HandshakeResponseEncoder._ - private val authenticationMethods = Map("mysql_native_password" -> new MySQLNativePasswordAuthentication(charset)) + private val authenticationMethods = AuthenticationMethod.Availables def encode(message: ClientMessage): ByteBuf = { @@ -78,7 +78,7 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e val method = m.authenticationMethod.get val authenticator = this.authenticationMethods.getOrElse( method, { throw new UnsupportedAuthenticationMethodException(method) }) - val bytes = authenticator.generateAuthentication( m.username, m.password, m.seed ) + val bytes = authenticator.generateAuthentication(charset, m.password, m.seed ) buffer.writeByte(bytes.length) buffer.writeBytes(bytes) } else { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala index b3b6f059..7ca9829e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala @@ -16,8 +16,17 @@ package com.github.mauricio.async.db.mysql.encoder.auth +import java.nio.charset.Charset + +object AuthenticationMethod { + final val Availables = Map( + "mysql_native_password" -> MySQLNativePasswordAuthentication, + "mysql_old_password" -> OldPasswordAuthentication + ) +} + trait AuthenticationMethod { - def generateAuthentication( username : String, password : Option[String], seed : Array[Byte] ) : Array[Byte] + def generateAuthentication( charset : Charset, password : Option[String], seed : Array[Byte] ) : Array[Byte] } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala index d40adbc5..21991684 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/MySQLNativePasswordAuthentication.scala @@ -19,25 +19,21 @@ package com.github.mauricio.async.db.mysql.encoder.auth import java.nio.charset.Charset import java.security.MessageDigest -object MySQLNativePasswordAuthentication { - final val EmptyArray = Array.empty[Byte] -} +object MySQLNativePasswordAuthentication extends AuthenticationMethod { -class MySQLNativePasswordAuthentication( charset : Charset ) extends AuthenticationMethod { - - import MySQLNativePasswordAuthentication.EmptyArray + final val EmptyArray = Array.empty[Byte] - def generateAuthentication(username: String, password: Option[String], seed : Array[Byte]): Array[Byte] = { + def generateAuthentication(charset : Charset, password: Option[String], seed : Array[Byte]): Array[Byte] = { if ( password.isDefined ) { - scramble411( password.get, seed ) + scramble411(charset, password.get, seed ) } else { EmptyArray } } - private def scramble411( password : String, seed : Array[Byte] ) : Array[Byte] = { + private def scramble411(charset : Charset, password : String, seed : Array[Byte] ) : Array[Byte] = { val messageDigest = MessageDigest.getInstance("SHA-1") val initialDigest = messageDigest.digest(password.getBytes(charset)) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/OldPasswordAuthentication.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/OldPasswordAuthentication.scala new file mode 100644 index 00000000..0b5ca289 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/OldPasswordAuthentication.scala @@ -0,0 +1,76 @@ +package com.github.mauricio.async.db.mysql.encoder.auth + +import java.nio.charset.Charset +import java.nio.CharBuffer + +object OldPasswordAuthentication extends AuthenticationMethod { + + final val EmptyArray = Array.empty[Byte] + + def generateAuthentication(charset: Charset, password: Option[String], seed: Array[Byte]): Array[Byte] = { + password match { + case Some(value) if !value.isEmpty => { + newCrypt(charset, value, new String(seed, charset) ) + } + case _ => EmptyArray + } + } + + def newCrypt( charset: Charset, password : String, seed : String) : Array[Byte] = { + var b : Byte = 0 + var d : Double = 0 + + val pw = newHash(seed) + val msg = newHash(password) + val max = 0x3fffffffL + var seed1 = (pw._1 ^ msg._1) % max + var seed2 = (pw._2 ^ msg._2) % max + val chars = new Array[Char](seed.length) + + var i = 0 + while ( i < seed.length ) { + seed1 = ((seed1 * 3) + seed2) % max + seed2 = (seed1 + seed2 + 33) % max + d = seed1.toDouble / max.toDouble + b = java.lang.Math.floor((d * 31) + 64).toByte + chars(i) = b.toChar + i += 1 + } + + seed1 = ((seed1 * 3) + seed2) % max + seed2 = (seed1 + seed2 + 33) % max + d = seed1.toDouble / max.toDouble + b = java.lang.Math.floor(d * 31).toByte + + var j = 0 + while( j < seed.length ) { + chars(j) = (chars(j) ^ b).toChar + j += 1 + } + + val bytes = new String(chars).getBytes(charset) + val result = new Array[Byte](bytes.length + 1) + System.arraycopy(bytes, 0, result, 0, bytes.length ) + result + } + + private def newHash(password : String) : (Long,Long) = { + var nr = 1345345333L + var add = 7L + var nr2 = 0x12345671L + var tmp = 0L + + password.foreach { + c => + if (c != ' ' && c != '\t') { + tmp = (0xff & c) + nr ^= ((((nr & 63) + add) * tmp) + (nr << 8)) + nr2 += ((nr2 << 8) ^ nr) + add += tmp + } + } + + (nr & 0x7fffffffL, nr2 & 0x7fffffffL) + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/AuthenticationSwitchResponse.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/AuthenticationSwitchResponse.scala new file mode 100644 index 00000000..5a0f7a4b --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/AuthenticationSwitchResponse.scala @@ -0,0 +1,6 @@ +package com.github.mauricio.async.db.mysql.message.client + +import com.github.mauricio.async.db.mysql.message.server.AuthenticationSwitchRequest + +case class AuthenticationSwitchResponse( password : Option[String], request : AuthenticationSwitchRequest ) + extends ClientMessage(ClientMessage.AuthSwitchResponse) \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala index e2d1d3c4..72d0be13 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala @@ -26,6 +26,7 @@ object ClientMessage { final val PreparedStatementPrepare = 0x16 final val PreparedStatementExecute = 0x17 final val PreparedStatement = 0x18 + final val AuthSwitchResponse = 0xfe } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/AuthenticationSwitchRequest.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/AuthenticationSwitchRequest.scala new file mode 100644 index 00000000..58500c1f --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/AuthenticationSwitchRequest.scala @@ -0,0 +1,6 @@ +package com.github.mauricio.async.db.mysql.message.server + +case class AuthenticationSwitchRequest( + method : String, + seed : String ) + extends ServerMessage(ServerMessage.EOF) diff --git a/mysql-async/src/test/resources/logback.xml b/mysql-async/src/test/resources/logback.xml index 99797b1d..e0084899 100644 --- a/mysql-async/src/test/resources/logback.xml +++ b/mysql-async/src/test/resources/logback.xml @@ -13,7 +13,7 @@ - + diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala new file mode 100644 index 00000000..fb001e06 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala @@ -0,0 +1,35 @@ +package com.github.mauricio.async.db.mysql + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.util.FutureUtils.awaitFuture +import com.github.mauricio.async.db.mysql.exceptions.MySQLException + +class OldPasswordSpec extends Specification with ConnectionHelper { + + "connection" should { + + "connect and query the database" in { + val connection = new MySQLConnection(defaultConfiguration) + try { + awaitFuture(connection.connect) + success + } catch { + case e : MySQLException => { + e.errorMessage.errorCode === 1275 + success + } + } + } + + } + + override def defaultConfiguration = new Configuration( + "mysql_async_old", + "localhost", + port = 3306, + password = Some("do_not_use_this"), + database = Some("mysql_async_tests") + ) + +} diff --git a/project/Build.scala b/project/Build.scala index 40c6465d..6ce7b6d0 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -56,7 +56,7 @@ object Configuration { "joda-time" % "joda-time" % "2.3", "org.joda" % "joda-convert" % "1.5", "org.scala-lang" % "scala-library" % projectScalaVersion, - "io.netty" % "netty-all" % "4.0.14.Final", + "io.netty" % "netty-all" % "4.0.17.Final", "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, logbackDependency diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 93687868..9992e442 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -3,6 +3,8 @@ echo "Preparing MySQL configs" mysql -u root -e 'create database mysql_async_tests;' mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async'@'localhost' IDENTIFIED BY 'root' WITH GRANT OPTION"; +mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async_old'@'localhost' WITH GRANT OPTION"; +mysql -u root -e "UPDATE mysql.user SET Password = OLD_PASSWORD('do_not_use_this'), plugin = 'mysql_old_password' where User = 'mysql_async_old'; flush privileges;"; echo "preparing postgresql configs" From 20fda4c5a91fe26a99a12a38c4e33bfd6af8760b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 28 Feb 2014 10:59:53 -0300 Subject: [PATCH 239/357] travis-ci debugging --- mysql-async/src/test/resources/logback.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-async/src/test/resources/logback.xml b/mysql-async/src/test/resources/logback.xml index e0084899..d8159fac 100644 --- a/mysql-async/src/test/resources/logback.xml +++ b/mysql-async/src/test/resources/logback.xml @@ -13,7 +13,7 @@ - + From 80876ee3293748e4bb96939e938a95d31b7b8768 Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 6 Apr 2014 21:51:16 +0100 Subject: [PATCH 240/357] Don't create a unique named logger per connection it causes a slow memory leak --- .../mauricio/async/db/postgresql/PostgreSQLConnection.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 82b5a5ce..c2cdce2e 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 @@ -60,7 +60,7 @@ class PostgreSQLConnection executionContext ) private final val currentCount = Counter.incrementAndGet() - private final val log = Log.getByName(s"${this.getClass.getName}:${currentCount}") + private final val log = Log.getByName(s"${this.getClass.getName}") private final val preparedStatementsCounter = new AtomicInteger() private final implicit val internalExecutionContext = executionContext From 8a6036fb36a25a5ca534e4ccd03e9520daf01660 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 7 Apr 2014 00:04:15 -0300 Subject: [PATCH 241/357] Moving logger back to static context until we can find a fix for the memory leak --- .../github/mauricio/async/db/mysql/MySQLConnection.scala | 4 +++- .../async/db/postgresql/PostgreSQLConnection.scala | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 0bae9cc1..a48e8739 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -35,6 +35,7 @@ import scala.util.Success object MySQLConnection { final val Counter = new AtomicLong() final val MicrosecondsVersion = Version(5,6,0) + final val log = Log.get[MySQLConnection] } class MySQLConnection( @@ -47,13 +48,14 @@ class MySQLConnection( with Connection { + import MySQLConnection.log + // validate that this charset is supported charsetMapper.toInt(configuration.charset) private final val connectionCount = MySQLConnection.Counter.incrementAndGet() private final val connectionId = s"[mysql-connection-$connectionCount]" - private final val log = Log.getByName(connectionId) private implicit val internalPool = executionContext private final val connectionHandler = new MySQLConnectionHandler( 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 c2cdce2e..70902513 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 @@ -34,8 +34,9 @@ import io.netty.channel.EventLoopGroup import java.util.concurrent.CopyOnWriteArrayList object PostgreSQLConnection { - val Counter = new AtomicLong() - val ServerVersionKey = "server_version" + final val Counter = new AtomicLong() + final val ServerVersionKey = "server_version" + final val log = Log.get[PostgreSQLConnection] } class PostgreSQLConnection @@ -59,8 +60,8 @@ class PostgreSQLConnection group, executionContext ) + private final val currentCount = Counter.incrementAndGet() - private final val log = Log.getByName(s"${this.getClass.getName}") private final val preparedStatementsCounter = new AtomicInteger() private final implicit val internalExecutionContext = executionContext From 0b1d0e028745b621a596e35d69fbc7a010b9a8c6 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 7 Apr 2014 00:19:16 -0300 Subject: [PATCH 242/357] Closing 0.2.13 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 6ce7b6d0..5fa7e83b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.13-SNAPSHOT" + val commonVersion = "0.2.13" val projectScalaVersion = "2.10.3" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" From 4ad0c9130b7e837434187e2f3b9a981fd2062249 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Sat, 10 May 2014 19:08:33 +0200 Subject: [PATCH 243/357] configs to generate the eclipse project --- .gitignore | 3 ++- project/Build.scala | 4 ++-- project/plugins.sbt | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 project/plugins.sbt diff --git a/.gitignore b/.gitignore index 44fbe0e4..db4fd9b4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ bin/* .idea* .classpath .project/* -.settings/* +**/.settings/* project/target/* project/project/* postgresql-async/target/* @@ -17,3 +17,4 @@ mysql-async/target/* .ruby-gemset *.jar *.iml +.project diff --git a/project/Build.scala b/project/Build.scala index 5fa7e83b..a7f6510b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -30,7 +30,7 @@ object ProjectBuild extends Build { name := postgresqlName, libraryDependencies ++= Configuration.implementationDependencies ) - ) aggregate (common) dependsOn (common) + ) dependsOn (common) lazy val mysql = Project( id = mysqlName, @@ -39,7 +39,7 @@ object ProjectBuild extends Build { name := mysqlName, libraryDependencies ++= Configuration.implementationDependencies ) - ) aggregate (common) dependsOn (common) + ) dependsOn (common) } diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 00000000..f0a2e04c --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0") From f8d313816548f4a26474bd4b130502328369b47a Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Sat, 10 May 2014 19:13:01 +0200 Subject: [PATCH 244/357] allow to configure the connect and test timeouts for the connection factories --- .../scala/com/github/mauricio/async/db/Configuration.scala | 5 ++++- .../async/db/mysql/pool/MySQLConnectionFactory.scala | 4 ++-- .../db/postgresql/pool/PostgreSQLConnectionFactory.scala | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) 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 70497e08..b9d3041f 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 @@ -22,6 +22,7 @@ import scala.{None, Option, Int} import io.netty.util.CharsetUtil import io.netty.buffer.AbstractByteBufAllocator import io.netty.buffer.PooledByteBufAllocator +import scala.concurrent.duration._ object Configuration { val DefaultCharset = CharsetUtil.UTF_8 @@ -52,5 +53,7 @@ case class Configuration(username: String, database: Option[String] = None, charset: Charset = Configuration.DefaultCharset, maximumMessageSize: Int = 16777216, - allocator: AbstractByteBufAllocator = PooledByteBufAllocator.DEFAULT + allocator: AbstractByteBufAllocator = PooledByteBufAllocator.DEFAULT, + connectTimeout: Duration = 5.seconds, + testTimeout: Duration = 5.seconds ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala index 94aadfd8..83791366 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala @@ -50,7 +50,7 @@ class MySQLConnectionFactory( configuration : Configuration ) extends ObjectFact */ def create: MySQLConnection = { val connection = new MySQLConnection(configuration) - Await.result(connection.connect, 5.seconds ) + Await.result(connection.connect, configuration.connectTimeout ) connection } @@ -121,7 +121,7 @@ class MySQLConnectionFactory( configuration : Configuration ) extends ObjectFact */ override def test(item: MySQLConnection): Try[MySQLConnection] = { Try { - Await.result(item.sendQuery("SELECT 0"), 5.seconds) + Await.result(item.sendQuery("SELECT 0"), configuration.testTimeout) item } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala index de06a671..62bcfd1a 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala @@ -50,7 +50,7 @@ class PostgreSQLConnectionFactory( def create: PostgreSQLConnection = { val connection = new PostgreSQLConnection(configuration, group = group, executionContext = executionContext) - Await.result(connection.connect, 5.seconds) + Await.result(connection.connect, configuration.connectTimeout) connection } @@ -87,7 +87,7 @@ class PostgreSQLConnectionFactory( override def test(item: PostgreSQLConnection): Try[PostgreSQLConnection] = { val result : Try[PostgreSQLConnection] = Try({ - Await.result( item.sendQuery("SELECT 0"), 5.seconds ) + Await.result( item.sendQuery("SELECT 0"), configuration.testTimeout ) item }) From 249f65de13cdc6d13b3a426f75b4305af690193a Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Sat, 10 May 2014 18:56:16 +0200 Subject: [PATCH 245/357] partitioned db pool --- .../db/pool/PartitionedAsyncObjectPool.scala | 65 ++++ .../db/pool/PartitionedConnectionPool.scala | 33 ++ .../pool/PartitionedAsyncObjectPoolSpec.scala | 288 ++++++++++++++++++ 3 files changed, 386 insertions(+) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPool.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedConnectionPool.scala create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPool.scala new file mode 100644 index 00000000..8561ed54 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPool.scala @@ -0,0 +1,65 @@ +package com.github.mauricio.async.db.pool + +import scala.concurrent.Future +import com.github.mauricio.async.db.util.ExecutorServiceUtils +import scala.concurrent.Promise +import java.util.concurrent.ConcurrentHashMap +import scala.util.Success +import scala.util.Failure + +class PartitionedAsyncObjectPool[T]( + factory: ObjectFactory[T], + configuration: PoolConfiguration, + numberOfPartitions: Int) + extends AsyncObjectPool[T] { + + import ExecutorServiceUtils.CachedExecutionContext + + private val pools = + (0 until numberOfPartitions) + .map(_ -> new SingleThreadedAsyncObjectPool(factory, partitionConfig)) + .toMap + + private val checkouts = new ConcurrentHashMap[T, SingleThreadedAsyncObjectPool[T]] + + def take: Future[T] = { + val pool = currentPool + pool.take.andThen { + case Success(conn) => + checkouts.put(conn, pool) + case Failure(_) => + } + } + + def giveBack(item: T) = + checkouts + .remove(item) + .giveBack(item) + .map(_ => this) + + def close = + Future.sequence(pools.values.map(_.close)).map { + _ => this + } + + def availables: Traversable[T] = pools.values.map(_.availables).flatten + + def inUse: Traversable[T] = pools.values.map(_.inUse).flatten + + def queued: Traversable[Promise[T]] = pools.values.map(_.queued).flatten + + protected def isClosed = + pools.values.forall(_.isClosed) + + private def currentPool = + pools(currentThreadAffinity) + + private def currentThreadAffinity = + (Thread.currentThread.getId % numberOfPartitions).toInt + + private def partitionConfig = + configuration.copy( + maxObjects = configuration.maxObjects / numberOfPartitions, + maxQueueSize = configuration.maxQueueSize / numberOfPartitions + ) +} \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedConnectionPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedConnectionPool.scala new file mode 100644 index 00000000..698534f5 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PartitionedConnectionPool.scala @@ -0,0 +1,33 @@ +package com.github.mauricio.async.db.pool; + +import com.github.mauricio.async.db.util.ExecutorServiceUtils +import com.github.mauricio.async.db.{ QueryResult, Connection } +import scala.concurrent.{ ExecutionContext, Future } + +class PartitionedConnectionPool[T <: Connection]( + factory: ObjectFactory[T], + configuration: PoolConfiguration, + numberOfPartitions: Int, + executionContext: ExecutionContext = ExecutorServiceUtils.CachedExecutionContext) + extends PartitionedAsyncObjectPool[T](factory, configuration, numberOfPartitions) + with Connection { + + def disconnect: Future[Connection] = if (this.isConnected) { + this.close.map(item => this)(executionContext) + } else { + Future.successful(this) + } + + def connect: Future[Connection] = Future.successful(this) + + def isConnected: Boolean = !this.isClosed + + def sendQuery(query: String): Future[QueryResult] = + this.use(_.sendQuery(query))(executionContext) + + def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = + this.use(_.sendPreparedStatement(query, values))(executionContext) + + override def inTransaction[A](f: Connection => Future[A])(implicit context: ExecutionContext = executionContext): Future[A] = + this.use(_.inTransaction[A](f)(context))(executionContext) +} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala new file mode 100644 index 00000000..3b84755d --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala @@ -0,0 +1,288 @@ +package com.github.mauricio.async.db.pool + +import org.specs2.mutable.Specification +import scala.util.Try +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.concurrent.Future +import org.specs2.mutable.SpecificationWithJUnit +import language.reflectiveCalls +import com.github.mauricio.async.db.util.ExecutorServiceUtils +import scala.concurrent.ExecutionContext +import java.util.concurrent.Executors + +class PartitionedAsyncObjectPoolSpec extends SpecificationWithJUnit { + isolated + sequential + + val config = + PoolConfiguration(100, Long.MaxValue, 100, Int.MaxValue) + + val factory = new ObjectFactory[Int] { + var reject = Set[Int]() + var failCreate = false + private var current = 0 + def create = + if (failCreate) + throw new IllegalStateException + else { + current += 1 + current + } + def destroy(item: Int) = {} + def validate(item: Int) = + Try { + if (reject.contains(item)) + throw new IllegalStateException + else item + } + } + + val pool = new PartitionedAsyncObjectPool(factory, config, 2) + def maxObjects = config.maxObjects / 2 + def maxIdle = config.maxIdle / 2 + def maxQueueSize = config.maxQueueSize / 2 + + "pool contents" >> { + + "before exceed maxObjects" >> { + + "take one element" in { + takeAndWait(1) + + pool.inUse.size mustEqual 1 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + + "take one element and return it invalid" in { + takeAndWait(1) + factory.reject += 1 + + await(pool.giveBack(1)) must throwA[IllegalStateException] + + pool.inUse.size mustEqual 0 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + + "take one failed element" in { + factory.failCreate = true + takeAndWait(1) must throwA[IllegalStateException] + + pool.inUse.size mustEqual 0 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + + "take maxObjects" in { + takeAndWait(maxObjects) + + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + + "take maxObjects - 1 and take one failed" in { + takeAndWait(maxObjects - 1) + + factory.failCreate = true + takeAndWait(1) must throwA[IllegalStateException] + + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + + "take maxObjects and receive one back" in { + takeAndWait(maxObjects) + await(pool.giveBack(1)) + + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 1 + } + + "take maxObjects and receive one invalid back" in { + takeAndWait(maxObjects) + factory.reject += 1 + await(pool.giveBack(1)) must throwA[IllegalStateException] + + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + } + + "after exceed maxObjects" >> { + + takeAndWait(maxObjects) + + "before exceed maxQueueSize" >> { + + "one take queued" in { + pool.take + + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual 1 + pool.availables.size mustEqual 0 + } + + "one take queued and receive one item back" in { + val taking = pool.take + + await(pool.giveBack(1)) + + await(taking) mustEqual 1 + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + + "one take queued and receive one invalid item back" in { + val taking = pool.take + factory.reject += 1 + await(pool.giveBack(1)) must throwA[IllegalStateException] + + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual 1 + pool.availables.size mustEqual 0 + } + + "maxQueueSize takes queued" in { + for (_ <- 0 until maxQueueSize) + pool.take + + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual maxQueueSize + pool.availables.size mustEqual 0 + } + + "maxQueueSize takes queued and receive one back" in { + val taking = pool.take + for (_ <- 0 until maxQueueSize - 1) + pool.take + + await(pool.giveBack(10)) + + await(taking) mustEqual 10 + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual maxQueueSize - 1 + pool.availables.size mustEqual 0 + } + + "maxQueueSize takes queued and receive one invalid back" in { + for (_ <- 0 until maxQueueSize) + pool.take + + factory.reject += 11 + await(pool.giveBack(11)) must throwA[IllegalStateException] + + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual maxQueueSize + pool.availables.size mustEqual 0 + } + } + + "after exceed maxQueueSize" >> { + + for (_ <- 0 until maxQueueSize) + pool.take + + "start to reject takes" in { + await(pool.take) must throwA[PoolExhaustedException] + + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual maxQueueSize + pool.availables.size mustEqual 0 + } + + "receive an object back" in { + await(pool.giveBack(1)) + + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual maxQueueSize - 1 + pool.availables.size mustEqual 0 + } + + "receive an invalid object back" in { + factory.reject += 1 + await(pool.giveBack(1)) must throwA[IllegalStateException] + + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual maxQueueSize + pool.availables.size mustEqual 0 + } + + "receive maxQueueSize objects back" in { + for (i <- 1 to maxQueueSize) + await(pool.giveBack(i)) + + pool.inUse.size mustEqual maxObjects + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + + "receive maxQueueSize invalid objects back" in { + for (i <- 1 to maxQueueSize) { + factory.reject += i + await(pool.giveBack(i)) must throwA[IllegalStateException] + } + + pool.inUse.size mustEqual maxObjects - maxQueueSize + pool.queued.size mustEqual maxQueueSize + pool.availables.size mustEqual 0 + } + + "receive maxQueueSize + 1 object back" in { + for (i <- 1 to maxQueueSize) + await(pool.giveBack(i)) + + await(pool.giveBack(1)) + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 1 + } + + "receive maxQueueSize + 1 invalid object back" in { + for (i <- 1 to maxQueueSize) + await(pool.giveBack(i)) + + factory.reject += 1 + await(pool.giveBack(1)) must throwA[IllegalStateException] + pool.inUse.size mustEqual maxObjects - 1 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 0 + } + } + } + } + + "gives back the connection to the original pool" in { + val executor = Executors.newFixedThreadPool(20) + implicit val context = ExecutionContext.fromExecutor(executor) + + val takes = + for (_ <- 0 until 30) yield { + Future().flatMap(_ => pool.take) + } + val takesAndReturns = + Future.sequence(takes).flatMap { items => + Future.sequence(items.map(pool.giveBack)) + } + + await(takesAndReturns) + + executor.shutdown + pool.inUse.size mustEqual 0 + pool.queued.size mustEqual 0 + pool.availables.size mustEqual 30 + } + + private def takeAndWait(objects: Int) = + for (_ <- 0 until objects) + await(pool.take) + + private def await[T](future: Future[T]) = + Await.result(future, Duration.Inf) +} From bed77b7f024a969727ea682b400470e661b1f3c0 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 26 Apr 2014 19:40:45 -0300 Subject: [PATCH 246/357] Trying to cross compile to Scala 2.11 and 2.10 --- build.sbt | 3 --- project/Build.scala | 11 ++++++----- project/build.properties | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 build.sbt diff --git a/build.sbt b/build.sbt deleted file mode 100644 index 4ba2d92a..00000000 --- a/build.sbt +++ /dev/null @@ -1,3 +0,0 @@ -scalaVersion := "2.10.3" - -parallelExecution in ThisBuild := false \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index a7f6510b..bc94c478 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -46,17 +46,16 @@ object ProjectBuild extends Build { object Configuration { val commonVersion = "0.2.13" - val projectScalaVersion = "2.10.3" + val projectScalaVersion = "2.11.0" - val specs2Dependency = "org.specs2" %% "specs2" % "2.3.4" % "test" + val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" val commonDependencies = Seq( "org.slf4j" % "slf4j-api" % "1.7.5", "joda-time" % "joda-time" % "2.3", "org.joda" % "joda-convert" % "1.5", - "org.scala-lang" % "scala-library" % projectScalaVersion, - "io.netty" % "netty-all" % "4.0.17.Final", + "io.netty" % "netty-all" % "4.0.18.Final", "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, logbackDependency @@ -76,9 +75,11 @@ object Configuration { , scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), scalaVersion := projectScalaVersion, - javacOptions := Seq("-source", "1.5", "-target", "1.5", "-encoding", "UTF8"), + crossScalaVersions := Seq(projectScalaVersion, "2.10.4"), + javacOptions := Seq("-source", "1.6", "-target", "1.6", "-encoding", "UTF8"), organization := "com.github.mauricio", version := commonVersion, + parallelExecution := false, publishArtifact in Test := false, publishMavenStyle := true, pomIncludeRepository := { diff --git a/project/build.properties b/project/build.properties index 0974fce4..8ac605a3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.0 +sbt.version=0.13.2 From 82541265280b0608b981d883032710787e6bf0cc Mon Sep 17 00:00:00 2001 From: Lukasz Piepiora Date: Sun, 18 May 2014 07:49:54 +0200 Subject: [PATCH 247/357] Fix cross build --- project/Build.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index bc94c478..842d67b4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -19,7 +19,7 @@ object ProjectBuild extends Build { base = file(commonName), settings = Configuration.baseSettings ++ Seq( name := commonName, - libraryDependencies := Configuration.commonDependencies + libraryDependencies ++= Configuration.commonDependencies ) ) @@ -74,7 +74,6 @@ object Configuration { :+ "-feature" , scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), - scalaVersion := projectScalaVersion, crossScalaVersions := Seq(projectScalaVersion, "2.10.4"), javacOptions := Seq("-source", "1.6", "-target", "1.6", "-encoding", "UTF8"), organization := "com.github.mauricio", From 10a7351394d89446ac43dd55a0d8efe83fb50415 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 24 May 2014 10:31:07 -0300 Subject: [PATCH 248/357] Marking the new 0.2.14 release cycle --- project/Build.scala | 2 +- project/plugins.sbt | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 842d67b4..8736fb0d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.13" + val commonVersion = "0.2.14-SNAPSHOT" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" diff --git a/project/plugins.sbt b/project/plugins.sbt index f0a2e04c..1e87e1c8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1,5 @@ -addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0") +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") From 35231441bfd684a17a49cc4218c8062b8f87bfd1 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 1 Jun 2014 18:16:44 -0300 Subject: [PATCH 249/357] Fixing issue with zeroed dates in MySQL - fixes #93 --- .../async/db/column/DateEncoderDecoder.scala | 11 +++- .../column/LocalDateTimeEncoderDecoder.scala | 9 ++- .../db/mysql/binary/decoder/DateDecoder.scala | 10 +++- .../binary/decoder/TimestampDecoder.scala | 4 +- mysql-async/src/test/resources/logback.xml | 2 +- .../mauricio/async/db/mysql/QuerySpec.scala | 1 + .../async/db/mysql/TransactionSpec.scala | 9 ++- .../async/db/mysql/ZeroDatesSpec.scala | 58 +++++++++++++++++++ .../db/postgresql/PreparedStatementSpec.scala | 3 +- .../async/db/postgresql/TimeAndDateSpec.scala | 4 +- .../db/postgresql/parsers/ParserKSpec.scala | 9 ++- 11 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ZeroDatesSpec.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala index 63c01c6e..53c2f2dd 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/DateEncoderDecoder.scala @@ -22,11 +22,16 @@ import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException object DateEncoderDecoder extends ColumnEncoderDecoder { + private val ZeroedDate = "0000-00-00" + private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd") - override def decode(value: String): LocalDate = { - this.formatter.parseLocalDate(value) - } + override def decode(value: String): LocalDate = + if ( ZeroedDate == value ) { + null + } else { + this.formatter.parseLocalDate(value) + } override def encode(value: Any): String = { value match { diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala index 27d50383..6cb67ad9 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/LocalDateTimeEncoderDecoder.scala @@ -21,6 +21,8 @@ import org.joda.time.LocalDateTime object LocalDateTimeEncoderDecoder extends ColumnEncoderDecoder { + private val ZeroedTimestamp = "0000-00-00 00:00:00" + private val optional = new DateTimeFormatterBuilder() .appendPattern(".SSSSSS").toParser @@ -33,5 +35,10 @@ object LocalDateTimeEncoderDecoder extends ColumnEncoderDecoder { format.print(value.asInstanceOf[LocalDateTime]) override def decode(value: String): LocalDateTime = - format.parseLocalDateTime(value) + if (ZeroedTimestamp == value) { + null + } else { + format.parseLocalDateTime(value) + } + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala index 681bfbe0..2d66e792 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/DateDecoder.scala @@ -20,5 +20,13 @@ import io.netty.buffer.ByteBuf import org.joda.time.LocalDate object DateDecoder extends BinaryDecoder { - override def decode(buffer: ByteBuf): LocalDate = TimestampDecoder.decode(buffer).toLocalDate + override def decode(buffer: ByteBuf): LocalDate = { + val result = TimestampDecoder.decode(buffer) + + if ( result != null ) { + result.toLocalDate + } else { + null + } + } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala index b6fc5a00..b7476a7a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/decoder/TimestampDecoder.scala @@ -24,9 +24,7 @@ object TimestampDecoder extends BinaryDecoder { val size = buffer.readUnsignedByte() size match { - case 0 => LocalDateTime.now() - .withDate(0, 0, 0) - .withTime(0, 0, 0, 0) + case 0 => null case 4 => new LocalDateTime() .withDate(buffer.readUnsignedShort(), buffer.readUnsignedByte(), buffer.readUnsignedByte()) .withTime(0, 0, 0, 0) diff --git a/mysql-async/src/test/resources/logback.xml b/mysql-async/src/test/resources/logback.xml index d8159fac..e0084899 100644 --- a/mysql-async/src/test/resources/logback.xml +++ b/mysql-async/src/test/resources/logback.xml @@ -13,7 +13,7 @@ - + diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 2f505169..039ae55d 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -181,6 +181,7 @@ class QuerySpec extends Specification with ConnectionHelper { matcher(executeQuery(connection, select)) ideasMatcher(executeQuery(connection, selectIdeas)) + success("completed") } } 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 9e209fff..0312f0d5 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 @@ -1,7 +1,6 @@ package com.github.mauricio.async.db.mysql import org.specs2.mutable.Specification -import com.github.mauricio.async.db.util.ExecutorServiceUtils._ import com.github.mauricio.async.db.util.FutureUtils.awaitFuture import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.Connection @@ -48,7 +47,7 @@ class TransactionSpec extends Specification with ConnectionHelper { try { awaitFuture(future) - ko("Should not have arrived here") + failure("should not have arrived here") } catch { case e : MySQLException => { @@ -58,7 +57,7 @@ class TransactionSpec extends Specification with ConnectionHelper { val result = executePreparedStatement(connection, this.select).rows.get result.size === 1 result(0)("name") === "Maurício Aragão" - ok("success") + success("correct result") } } } @@ -83,14 +82,14 @@ class TransactionSpec extends Specification with ConnectionHelper { try { awaitFuture(future) - ko("this should not be reached") + failure("this should not be reached") } catch { case e : MySQLException => { pool.availables must have size(0) pool.availables must not contain(connection.asInstanceOf[MySQLConnection]) - ok("success") + success("success") } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ZeroDatesSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ZeroDatesSpec.scala new file mode 100644 index 00000000..b5a06aec --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ZeroDatesSpec.scala @@ -0,0 +1,58 @@ +package com.github.mauricio.async.db.mysql + +import org.specs2.mutable.Specification +import scala.concurrent.duration.Duration +import com.github.mauricio.async.db.RowData + +class ZeroDatesSpec extends Specification with ConnectionHelper { + + val createStatement = + """CREATE TEMPORARY TABLE dates ( + |`name` varchar (255) NOT NULL, + |`timestamp_column` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + |`date_column` date NOT NULL DEFAULT '0000-00-00', + |`datetime_column` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + |`time_column` time NOT NULL DEFAULT '00:00:00', + |`year_column` year NOT NULL DEFAULT '0000' + |) + |ENGINE=MyISAM DEFAULT CHARSET=utf8;""".stripMargin + + val insertStatement = "INSERT INTO dates (name) values ('Joe')" + val selectStatement = "SELECT * FROM dates" + + def matchValues( result : RowData ) = { + result("name") === "Joe" + result("timestamp_column") must beNull + result("datetime_column") must beNull + result("date_column") must beNull + result("year_column") === 0 + result("time_column") === Duration.Zero + } + + "client" should { + + "correctly parse the MySQL zeroed dates as NULL values in text protocol" in { + + withConnection { + connection => + executeQuery(connection, createStatement) + executeQuery(connection, insertStatement) + + matchValues(executeQuery(connection, selectStatement).rows.get(0)) + } + } + + "correctly parse the MySQL zeroed dates as NULL values in binary protocol" in { + + withConnection { + connection => + executeQuery(connection, createStatement) + executeQuery(connection, insertStatement) + + matchValues(executePreparedStatement(connection, selectStatement).rows.get(0)) + } + } + + } + +} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 66f7a57b..678ee5e5 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -116,7 +116,7 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { executeDdl(handler, this.messagesCreate) executeDdl(handler, create) - 1.until(4).map { + foreach(1.until(4)) { x => executePreparedStatement(handler, this.messagesInsert, Array(message, moment)) executePreparedStatement(handler, insert, Array(otherMoment, otherMessage)) @@ -132,7 +132,6 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { otherResult.columnNames must contain(allOf("id", "other_moment", "other_content")).inOrder otherResult(x - 1)("other_moment") === otherMoment otherResult(x - 1)("other_content") === otherMessage - } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index 0d6d98c9..e671a5b4 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -128,7 +128,7 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { "support timestamp with timezone and microseconds" in { - 1.until(6).inclusive.map { + foreach(1.until(6)) { index => withHandler { handler => @@ -156,8 +156,6 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { dateTime.getMillis must be_>=(915779106000L) dateTime.getMillis must be_<(915779107000L) } - - } } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala index e6b29dd4..3f2788fc 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/parsers/ParserKSpec.scala @@ -34,11 +34,10 @@ class ParserKSpec extends Specification { val data = parser.parseMessage(buffer).asInstanceOf[ProcessData] - List( - data.kind === ServerMessage.BackendKeyData, - data.processId === 10, - data.secretKey === 20 - ) + data.kind === ServerMessage.BackendKeyData + data.processId === 10 + data.secretKey === 20 + } } From c33e617c77a0de568117220cd9aa04463a73f620 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 1 Jun 2014 18:24:37 -0300 Subject: [PATCH 250/357] Skip the old password spec since it isn't working at all --- .travis.yml | 4 ++-- .../com/github/mauricio/async/db/mysql/OldPasswordSpec.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c04d3e61..8fea92d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ language: scala scala: - - 2.10.2 + - 2.10 + - 2.11 jdk: - oraclejdk7 - openjdk7 - - openjdk6 services: - postgresql - mysql diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala index fb001e06..a1c7f602 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala @@ -16,7 +16,7 @@ class OldPasswordSpec extends Specification with ConnectionHelper { success } catch { case e : MySQLException => { - e.errorMessage.errorCode === 1275 + (e.errorMessage.errorCode === 1275).orSkip success } } From 076f4ef176f2feec44e9890e6816a1643ddab55b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 1 Jun 2014 18:29:02 -0300 Subject: [PATCH 251/357] Use full version number --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8fea92d3..53e614cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: scala scala: - - 2.10 - - 2.11 + - 2.10.4 + - 2.11.0 jdk: - oraclejdk7 - openjdk7 From 80f2c6d86e0aec5c908300d7438855e05c234af6 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 1 Jun 2014 18:36:10 -0300 Subject: [PATCH 252/357] Do not run on travis, bugger --- .../async/db/mysql/OldPasswordSpec.scala | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala index a1c7f602..09aca535 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala @@ -10,16 +10,22 @@ class OldPasswordSpec extends Specification with ConnectionHelper { "connection" should { "connect and query the database" in { - val connection = new MySQLConnection(defaultConfiguration) - try { - awaitFuture(connection.connect) - success - } catch { - case e : MySQLException => { - (e.errorMessage.errorCode === 1275).orSkip - success + + if ( System.getenv("TRAVIS") == null ) { + val connection = new MySQLConnection(defaultConfiguration) + try { + awaitFuture(connection.connect) + success("did work") + } catch { + case e : MySQLException => { + e.errorMessage.errorCode === 1275 + success("did work") + } } + } else { + skipped("not to be run on travis") } + } } From 4ba8596379aa98974b3802e1045f2c99c77d4ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Mon, 2 Jun 2014 11:00:01 -0300 Subject: [PATCH 253/357] Including details about MySQL zeroed dates --- mysql-async/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-async/README.md b/mysql-async/README.md index c6bcea16..2c3055dd 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -29,6 +29,7 @@ You can find more information about the MySQL network protocol [here](http://dev sense, that is how it was implemented at the database and as a driver we need to stay true to it, so, while you **can** send `java.sql.Time` and `LocalTime` objects to the database, when reading these values you will always receive a `scala.concurrent.Duration` object since it is the closest thing we have to what a `time` value in MySQL means. +* MySQL can store dates with values like `0000-00-00` or `0000-00-00 00:00:00` but it's not possible to represent dates like this in Java (nor there would actually be a date with a zero day or month, this is just MySQL being lenient on invalid dates) so the driver just returns `null` for any case like that. ## Supported types From 6d812b8edc36692face80a3466c427d4e5c62578 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 20 Jul 2014 17:47:20 -0300 Subject: [PATCH 254/357] Including a test case for #94, can't reproduce --- .../async/db/postgresql/PreparedStatementSpec.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 678ee5e5..4bb31dcc 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -20,6 +20,7 @@ import org.specs2.mutable.Specification import org.joda.time.LocalDate import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.exceptions.InsufficientParametersException +import java.util.Date class PreparedStatementSpec extends Specification with DatabaseTestHelper { @@ -253,6 +254,17 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } } + "fail if prepared statement has more variables than it was given" in { + withHandler { + handler => + executeDdl(handler, messagesCreate) + + handler.sendPreparedStatement( + "SELECT * FROM messages WHERE content = ? AND moment = ?", + Array("some content")) must throwAn[InsufficientParametersException] + } + } + } } From d59fedc0fbdef712f80bc05023faba3f89b0f18a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 26 Jul 2014 16:29:59 -0300 Subject: [PATCH 255/357] Connects to MySQL even if the auth protocol details are not provided, default to native password auth for now, fixes #37 --- .gitignore | 1 + Vagrantfile | 13 ++ bootstrap.sh | 5 + .../async/db/util/ChannelWrapper.scala | 8 + .../mysql/codec/MySQLConnectionHandler.scala | 1 + .../db/mysql/codec/MySQLFrameDecoder.scala | 201 ++++++++++-------- .../mysql/decoder/HandshakeV10Decoder.scala | 76 +++++-- .../encoder/HandshakeResponseEncoder.scala | 33 +-- .../encoder/auth/AuthenticationMethod.scala | 8 +- .../client/HandshakeResponseMessage.scala | 2 +- .../message/server/HandshakeMessage.scala | 8 +- .../async/db/mysql/util/MySQLIO.scala | 29 +++ .../mysql/ClientPluginAuthDisabledSpec.scala | 61 ++++++ .../async/db/mysql/ConnectionHelper.scala | 6 +- .../async/db/mysql/OldPasswordSpec.scala | 41 ---- 15 files changed, 307 insertions(+), 186 deletions(-) create mode 100644 Vagrantfile create mode 100644 bootstrap.sh create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/MySQLIO.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ClientPluginAuthDisabledSpec.scala delete mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala diff --git a/.gitignore b/.gitignore index db4fd9b4..0122f1ca 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ mysql-async/target/* *.jar *.iml .project +.vagrant/* diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..5498f80c --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,13 @@ +# -*- 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/bootstrap.sh b/bootstrap.sh new file mode 100644 index 00000000..451d77cf --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +yum -y install mysql-server +service mysqld start +mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO root;" +mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_vagrant' IDENTIFIED BY 'generic_password' WITH GRANT OPTION"; \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index 229dabff..c62e5efb 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -109,4 +109,12 @@ class ChannelWrapper( val buffer : ByteBuf ) extends AnyVal { ByteBufferUtils.writePacketLength(buffer, sequence ) } + def mysqlReadInt() : Int = { + val first = buffer.readByte() + val last = buffer.readByte() + + (first & 0xff) | ((last & 0xff) << 8) + } + + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index d70b3623..27ff04da 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -204,6 +204,7 @@ class MySQLConnectionHandler( } def write( message : HandshakeResponseMessage ) : ChannelFuture = { + decoder.hasDoneHandshake = true writeAndHandleError(message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala index 3d92929f..bd55f4fd 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoder.scala @@ -30,7 +30,7 @@ import java.nio.charset.Charset import java.util.concurrent.atomic.AtomicInteger -class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToMessageDecoder { +class MySQLFrameDecoder(charset: Charset, connectionId: String) extends ByteToMessageDecoder { private final val log = Log.getByName(s"[frame-decoder]${connectionId}") private final val messagesCount = new AtomicInteger() @@ -48,6 +48,7 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM private[codec] var isPreparedStatementPrepare = false private[codec] var isPreparedStatementExecute = false private[codec] var isPreparedStatementExecuteRows = false + private[codec] var hasDoneHandshake = false private[codec] var totalParams = 0L private[codec] var processedParams = 0L @@ -77,121 +78,133 @@ class MySQLFrameDecoder(charset: Charset, connectionId : String) extends ByteToM val slice = buffer.readSlice(size) - if ( log.isTraceEnabled ) { + if (log.isTraceEnabled) { log.trace(s"Reading message type $messageType - " + - s"(count=$messagesCount,size=$size,isInQuery=$isInQuery,processingColumns=$processingColumns,processingParams=$processingParams,processedColumns=$processedColumns,processedParams=$processedParams)" + + s"(count=$messagesCount,hasDoneHandshake=$hasDoneHandshake,size=$size,isInQuery=$isInQuery,processingColumns=$processingColumns,processingParams=$processingParams,processedColumns=$processedColumns,processedParams=$processedParams)" + s"\n${BufferDumper.dumpAsHex(slice)}}") } slice.readByte() - val decoder = messageType match { - case ServerMessage.ServerProtocolVersion if !isInQuery => this.handshakeDecoder - case ServerMessage.Error => { - this.clear - this.errorDecoder - } - case ServerMessage.EOF => { - - if (this.processingParams && this.totalParams > 0) { - this.processingParams = false - if (this.totalColumns == 0) { - ParamAndColumnProcessingFinishedDecoder - } else { - ParamProcessingFinishedDecoder - } - } else { - if (this.processingColumns) { - this.processingColumns = false - ColumnProcessingFinishedDecoder - } else { - - if ( this.isInQuery ) { - this.clear - EOFMessageDecoder - } else { - this.authenticationSwitchDecoder - } - - } - } - - } - case ServerMessage.Ok => { - if (this.isPreparedStatementPrepare) { - this.preparedStatementPrepareDecoder - } else { - if (this.isPreparedStatementExecuteRows) { - null - } else { - this.clear - this.okDecoder - } + if (this.hasDoneHandshake) { + this.handleCommonFlow(messageType, slice, out) + } else { + val decoder = messageType match { + case ServerMessage.Error => { + this.clear + this.errorDecoder } + case _ => this.handshakeDecoder } - case _ => { + this.doDecoding(decoder, slice, out) + } + } else { + buffer.resetReaderIndex() + } - if (this.isInQuery) { - null - } else { - throw new ParserNotAvailableException(messageType) - } + } + } + private def handleCommonFlow(messageType: Byte, slice: ByteBuf, out: java.util.List[Object]) { + val decoder = messageType match { + case ServerMessage.Error => { + this.clear + this.errorDecoder + } + case ServerMessage.EOF => { + + if (this.processingParams && this.totalParams > 0) { + this.processingParams = false + if (this.totalColumns == 0) { + ParamAndColumnProcessingFinishedDecoder + } else { + ParamProcessingFinishedDecoder + } + } else { + if (this.processingColumns) { + this.processingColumns = false + ColumnProcessingFinishedDecoder + } else { + this.clear + EOFMessageDecoder } } - if (decoder == null) { - slice.readerIndex(slice.readerIndex() - 1) - val result = decodeQueryResult(slice) - - if (slice.readableBytes() != 0) { - throw new BufferNotFullyConsumedException(slice) - } - if (result != null) { - out.add(result) + } + case ServerMessage.Ok => { + if (this.isPreparedStatementPrepare) { + this.preparedStatementPrepareDecoder + } else { + if (this.isPreparedStatementExecuteRows) { + null + } else { + this.clear + this.okDecoder } + } + } + case _ => { + + if (this.isInQuery) { + null } else { - val result = decoder.decode(slice) + throw new ParserNotAvailableException(messageType) + } - result match { - case m: PreparedStatementPrepareResponse => { - this.hasReadColumnsCount = true - this.totalColumns = m.columnsCount - this.totalParams = m.paramsCount - } - case m: ParamAndColumnProcessingFinishedMessage => { - this.clear - } - case m: ColumnProcessingFinishedMessage if this.isPreparedStatementPrepare => { - this.clear - } - case m: ColumnProcessingFinishedMessage if this.isPreparedStatementExecute => { - this.isPreparedStatementExecuteRows = true - } - case _ => - } + } + } - if (slice.readableBytes() != 0) { - throw new BufferNotFullyConsumedException(slice) - } + doDecoding(decoder, slice, out) + } + + private def doDecoding(decoder: MessageDecoder, slice: ByteBuf, out: java.util.List[Object]) { + if (decoder == null) { + slice.readerIndex(slice.readerIndex() - 1) + val result = decodeQueryResult(slice) + + if (slice.readableBytes() != 0) { + throw new BufferNotFullyConsumedException(slice) + } + if (result != null) { + out.add(result) + } + } else { + val result = decoder.decode(slice) + + result match { + case m: PreparedStatementPrepareResponse => { + this.hasReadColumnsCount = true + this.totalColumns = m.columnsCount + this.totalParams = m.paramsCount + } + case m: ParamAndColumnProcessingFinishedMessage => { + this.clear + } + case m: ColumnProcessingFinishedMessage if this.isPreparedStatementPrepare => { + this.clear + } + case m: ColumnProcessingFinishedMessage if this.isPreparedStatementExecute => { + this.isPreparedStatementExecuteRows = true + } + case _ => + } - if (result != null) { - result match { - case m : PreparedStatementPrepareResponse => { - out.add(result) - if ( m.columnsCount == 0 && m.paramsCount == 0 ) { - this.clear - out.add(new ParamAndColumnProcessingFinishedMessage(new EOFMessage(0, 0)) ) - } - } - case _ => out.add(result) + if (slice.readableBytes() != 0) { + throw new BufferNotFullyConsumedException(slice) + } + + if (result != null) { + result match { + case m: PreparedStatementPrepareResponse => { + out.add(result) + if (m.columnsCount == 0 && m.paramsCount == 0) { + this.clear + out.add(new ParamAndColumnProcessingFinishedMessage(new EOFMessage(0, 0))) } } + case _ => out.add(result) } - } else { - buffer.resetReaderIndex() } - } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala index 81cf2900..05feca12 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/HandshakeV10Decoder.scala @@ -16,58 +16,94 @@ package com.github.mauricio.async.db.mysql.decoder -import io.netty.buffer.ByteBuf -import com.github.mauricio.async.db.mysql.message.server.{HandshakeMessage, ServerMessage} -import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import java.nio.charset.Charset +import com.github.mauricio.async.db.mysql.encoder.auth.AuthenticationMethod +import com.github.mauricio.async.db.mysql.message.server.{HandshakeMessage, ServerMessage} +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import com.github.mauricio.async.db.util.Log +import io.netty.buffer.ByteBuf +import io.netty.util.CharsetUtil + object HandshakeV10Decoder { final val log = Log.get[HandshakeV10Decoder] final val SeedSize = 8 final val SeedComplementSize = 12 final val Padding = 10 + final val ASCII = CharsetUtil.US_ASCII } class HandshakeV10Decoder(charset: Charset) extends MessageDecoder { - import HandshakeV10Decoder._ + import com.github.mauricio.async.db.mysql.decoder.HandshakeV10Decoder._ + import com.github.mauricio.async.db.mysql.util.MySQLIO._ def decode(buffer: ByteBuf): ServerMessage = { - val serverVersion = ByteBufferUtils.readCString(buffer, charset) - val connectionId = buffer.readInt() + val serverVersion = buffer.readCString(ASCII) + val connectionId = buffer.readUnsignedInt() - var seed = new Array[Byte]( SeedSize + SeedComplementSize ) + var seed = new Array[Byte](SeedSize + SeedComplementSize) buffer.readBytes(seed, 0, SeedSize) - buffer.readByte() + buffer.readByte() // filler - var serverCapabilityFlags: Int = buffer.readShort() + // read capability flags (lower 2 bytes) + var serverCapabilityFlags = buffer.readUnsignedShort() + /* New protocol with 16 bytes to describe server characteristics */ + // read character set (1 byte) val characterSet = buffer.readByte() & 0xff - val statusFlags = buffer.readShort() + // read status flags (2 bytes) + val statusFlags = buffer.readUnsignedShort() - serverCapabilityFlags += 65536 * buffer.readShort().asInstanceOf[Int] + // read capability flags (upper 2 bytes) + serverCapabilityFlags |= buffer.readUnsignedShort() << 16 - val authPluginDataLength = buffer.readUnsignedByte() - var authenticationMethod: Option[String] = None + var authPluginDataLength = 0 + var authenticationMethod = AuthenticationMethod.Native - if (authPluginDataLength > 0) { - buffer.readerIndex(buffer.readerIndex() + Padding) - buffer.readBytes(seed, SeedSize, SeedComplementSize) + if ((serverCapabilityFlags & CLIENT_PLUGIN_AUTH) != 0) { + // read length of auth-plugin-data (1 byte) + authPluginDataLength = buffer.readByte() & 0xff + } else { + // read filler ([00]) buffer.readByte() - authenticationMethod = Some(ByteBufferUtils.readUntilEOF(buffer, charset)) } - new HandshakeMessage( + // next 10 bytes are reserved (all [00]) + buffer.readerIndex(buffer.readerIndex() + Padding) + + log.debug(s"Auth plugin data length was ${authPluginDataLength}") + + if ((serverCapabilityFlags & CLIENT_SECURE_CONNECTION) != 0) { + val complement = if ( authPluginDataLength > 0 ) { + authPluginDataLength - 1 - SeedSize + } else { + SeedComplementSize + } + + buffer.readBytes(seed, SeedSize, complement) + buffer.readByte() + } + + if ((serverCapabilityFlags & CLIENT_PLUGIN_AUTH) != 0) { + authenticationMethod = buffer.readUntilEOF(ASCII) + } + + val message = new HandshakeMessage( serverVersion, connectionId, seed, serverCapabilityFlags, - characterSet = Some(characterSet), - statusFlags = Some(statusFlags), + characterSet = characterSet, + statusFlags = statusFlags, authenticationMethod = authenticationMethod ) + + log.debug(s"handshake message was ${message}") + + message } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala index b60bf33d..9865e55e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/HandshakeResponseEncoder.scala @@ -16,24 +16,17 @@ package com.github.mauricio.async.db.mysql.encoder -import io.netty.buffer.ByteBuf +import java.nio.charset.Charset + import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException -import com.github.mauricio.async.db.mysql.encoder.auth.{AuthenticationMethod, MySQLNativePasswordAuthentication} -import com.github.mauricio.async.db.mysql.message.client.{HandshakeResponseMessage, ClientMessage} +import com.github.mauricio.async.db.mysql.encoder.auth.AuthenticationMethod +import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, HandshakeResponseMessage} import com.github.mauricio.async.db.mysql.util.CharsetMapper -import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} -import java.nio.charset.Charset +import com.github.mauricio.async.db.util.{ByteBufferUtils, Log} +import io.netty.buffer.ByteBuf object HandshakeResponseEncoder { - final val CLIENT_PROTOCOL_41 = 0x0200 - final val CLIENT_SECURE_CONNECTION = 0x8000 - final val CLIENT_CONNECT_WITH_DB = 0x0008 - final val CLIENT_TRANSACTIONS = 0x2000 - final val CLIENT_MULTI_RESULTS = 0x200000 - final val CLIENT_LONG_FLAG = 0x0001 - final val CLIENT_PLUGIN_AUTH = 524288 - final val MAX_3_BYTES = 0x00ffffff final val PADDING: Array[Byte] = List.fill(23) { 0.toByte @@ -45,7 +38,8 @@ object HandshakeResponseEncoder { class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) extends MessageEncoder { - import HandshakeResponseEncoder._ + import com.github.mauricio.async.db.mysql.encoder.HandshakeResponseEncoder._ + import com.github.mauricio.async.db.mysql.util.MySQLIO._ private val authenticationMethods = AuthenticationMethod.Availables @@ -75,10 +69,10 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e ByteBufferUtils.writeCString( m.username, buffer, charset ) if ( m.password.isDefined ) { - val method = m.authenticationMethod.get + val method = m.authenticationMethod val authenticator = this.authenticationMethods.getOrElse( method, { throw new UnsupportedAuthenticationMethodException(method) }) - val bytes = authenticator.generateAuthentication(charset, m.password, m.seed ) + val bytes = authenticator.generateAuthentication(charset, m.password, m.seed) buffer.writeByte(bytes.length) buffer.writeBytes(bytes) } else { @@ -89,14 +83,9 @@ class HandshakeResponseEncoder(charset: Charset, charsetMapper: CharsetMapper) e ByteBufferUtils.writeCString( m.database.get, buffer, charset ) } - if ( m.authenticationMethod.isDefined ) { - ByteBufferUtils.writeCString( m.authenticationMethod.get, buffer, charset ) - } else { - buffer.writeByte(0) - } + ByteBufferUtils.writeCString( m.authenticationMethod, buffer, charset ) buffer - } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala index 7ca9829e..50cf1073 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/auth/AuthenticationMethod.scala @@ -19,9 +19,13 @@ package com.github.mauricio.async.db.mysql.encoder.auth import java.nio.charset.Charset object AuthenticationMethod { + + final val Native = "mysql_native_password" + final val Old = "mysql_old_password" + final val Availables = Map( - "mysql_native_password" -> MySQLNativePasswordAuthentication, - "mysql_old_password" -> OldPasswordAuthentication + Native -> MySQLNativePasswordAuthentication, + Old -> OldPasswordAuthentication ) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala index 77dfaee4..50b7e839 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/HandshakeResponseMessage.scala @@ -22,7 +22,7 @@ case class HandshakeResponseMessage( username: String, charset: Charset, seed: Array[Byte], - authenticationMethod: Option[String] = None, + authenticationMethod: String, password: Option[String] = None, database: Option[String] = None ) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala index 1bbcf12c..dd16044a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/server/HandshakeMessage.scala @@ -18,11 +18,11 @@ package com.github.mauricio.async.db.mysql.message.server case class HandshakeMessage( serverVersion: String, - connectionId: Int, + connectionId: Long, seed: Array[Byte], serverCapabilities: Int, - characterSet: Option[Int] = None, - statusFlags: Option[Short] = None, - authenticationMethod: Option[String] = None + characterSet: Int, + statusFlags: Int, + authenticationMethod : String ) extends ServerMessage(ServerMessage.ServerProtocolVersion) \ No newline at end of file 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 new file mode 100644 index 00000000..4587eb09 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/MySQLIO.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2014 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 + +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_LONG_FLAG = 0x0001 + final val CLIENT_PLUGIN_AUTH = 0x00080000 + final val CLIENT_SECURE_CONNECTION = 0x00008000 + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ClientPluginAuthDisabledSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ClientPluginAuthDisabledSpec.scala new file mode 100644 index 00000000..c4c8b6c7 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ClientPluginAuthDisabledSpec.scala @@ -0,0 +1,61 @@ +package com.github.mauricio.async.db.mysql + +import com.github.mauricio.async.db.Configuration +import org.specs2.mutable.Specification + +/** + * + * To run this spec you have to use the Vagrant file provided with the base project + * and you have to start MySQL there. The expected MySQL version is 5.1.73. + * Make sure the bootstrap.sh script is run, if it isn't, manually run it yourself. + * + */ + +class ClientPluginAuthDisabledSpec extends Specification with ConnectionHelper { + + "connection" should { + + "connect and query the database without a password" in { + + if (System.getenv("TRAVIS") == null) { + withConnection { + connection => + executeQuery(connection, "select version()") + success("did work") + } + } else { + skipped("not to be run on travis") + } + + } + + "connect and query the database with a password" in { + + if (System.getenv("TRAVIS") == null) { + withConfigurableConnection(vagrantConfiguration) { + connection => + executeQuery(connection, "select version()") + success("did work") + } + } else { + skipped("not to be run on travis") + } + + } + + } + + override def defaultConfiguration = new Configuration( + "root", + "localhost", + port = 3307 + ) + + def vagrantConfiguration = new Configuration( + "mysql_vagrant", + "localhost", + port = 3307, + password = Some("generic_password") + ) + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index ce323ce5..771fe1e3 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -115,9 +115,11 @@ trait ConnectionHelper { } - def withConnection[T]( fn : (MySQLConnection) => T ) : T = { + def withConnection[T]( fn : (MySQLConnection) => T ) : T = + withConfigurableConnection(this.defaultConfiguration)(fn) - val connection = new MySQLConnection(this.defaultConfiguration) + def withConfigurableConnection[T]( configuration : Configuration )(fn : (MySQLConnection) => T) : T = { + val connection = new MySQLConnection(configuration) try { awaitFuture( connection.connect ) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala deleted file mode 100644 index 09aca535..00000000 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/OldPasswordSpec.scala +++ /dev/null @@ -1,41 +0,0 @@ -package com.github.mauricio.async.db.mysql - -import org.specs2.mutable.Specification -import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.util.FutureUtils.awaitFuture -import com.github.mauricio.async.db.mysql.exceptions.MySQLException - -class OldPasswordSpec extends Specification with ConnectionHelper { - - "connection" should { - - "connect and query the database" in { - - if ( System.getenv("TRAVIS") == null ) { - val connection = new MySQLConnection(defaultConfiguration) - try { - awaitFuture(connection.connect) - success("did work") - } catch { - case e : MySQLException => { - e.errorMessage.errorCode === 1275 - success("did work") - } - } - } else { - skipped("not to be run on travis") - } - - } - - } - - override def defaultConfiguration = new Configuration( - "mysql_async_old", - "localhost", - port = 3306, - password = Some("do_not_use_this"), - database = Some("mysql_async_tests") - ) - -} From 2e7ca4ece86a95203a189f1aaab38ce9375d7d15 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 26 Jul 2014 17:15:47 -0300 Subject: [PATCH 256/357] Making sure decoder is at the right status --- .../async/db/mysql/codec/MySQLFrameDecoderSpec.scala | 7 ++++++- postgresql-async/src/test/resources/logback.xml | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala index 5ff9a563..8d8790e5 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/codec/MySQLFrameDecoderSpec.scala @@ -69,6 +69,7 @@ class MySQLFrameDecoderSpec extends Specification { "on a query process it should correctly send an OK" in { val decoder = new MySQLFrameDecoder(charset, "[mysql-connection]") + decoder.hasDoneHandshake = true val embedder = new EmbeddedChannel(decoder) embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) @@ -89,6 +90,7 @@ class MySQLFrameDecoderSpec extends Specification { "on query process it should correctly send an error" in { val decoder = new MySQLFrameDecoder(charset, "[mysql-connection]") + decoder.hasDoneHandshake = true val embedder = new EmbeddedChannel(decoder) embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) @@ -112,6 +114,7 @@ class MySQLFrameDecoderSpec extends Specification { "on query process it should correctly handle a result set" in { val decoder = new MySQLFrameDecoder(charset, "[mysql-connection]") + decoder.hasDoneHandshake = true val embedder = new EmbeddedChannel(decoder) embedder.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) @@ -165,7 +168,9 @@ class MySQLFrameDecoderSpec extends Specification { } def createPipeline(): EmbeddedChannel = { - val channel = new EmbeddedChannel(new MySQLFrameDecoder(charset, "[mysql-connection]")) + val decoder = new MySQLFrameDecoder(charset, "[mysql-connection]") + decoder.hasDoneHandshake = true + val channel = new EmbeddedChannel(decoder) channel.config.setAllocator(LittleEndianByteBufAllocator.INSTANCE) channel } diff --git a/postgresql-async/src/test/resources/logback.xml b/postgresql-async/src/test/resources/logback.xml index 30bf6dbe..3ddb1518 100644 --- a/postgresql-async/src/test/resources/logback.xml +++ b/postgresql-async/src/test/resources/logback.xml @@ -13,8 +13,9 @@ - + + \ No newline at end of file From 75f0cfbde43707b97f99027d739ebfccf5fcef46 Mon Sep 17 00:00:00 2001 From: dboissin Date: Tue, 29 Jul 2014 15:51:51 +0200 Subject: [PATCH 257/357] remove error parsed statement --- .../db/postgresql/PostgreSQLConnection.scala | 1 + .../db/postgresql/PreparedStatementHolder.scala | 2 +- .../db/postgresql/PreparedStatementSpec.scala | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) 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 70902513..054bab50 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 @@ -150,6 +150,7 @@ class PostgreSQLConnection this.disconnect } + this.currentPreparedStatement.map(p => this.parsedStatements.remove(p.query)) this.currentPreparedStatement = None this.failQueryPromise(e) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala index 41f6ac40..f8b78bcf 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumnData -class PreparedStatementHolder( query : String, val statementId : Int ) { +class PreparedStatementHolder(val query : String, val statementId : Int ) { val (realQuery, paramsCount) = { val result = new StringBuilder(query.length+16) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 4bb31dcc..890518f1 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -21,6 +21,7 @@ import org.joda.time.LocalDate import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.exceptions.InsufficientParametersException import java.util.Date +import com.github.mauricio.async.db.postgresql.exceptions.GenericDatabaseException class PreparedStatementSpec extends Specification with DatabaseTestHelper { @@ -265,6 +266,22 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } } + "run prepared statement twice with bad and good values" in { + withHandler { + handler => + val content = "Some Moment" + + val query = "SELECT content FROM messages WHERE id = ?" + + executeDdl(handler, messagesCreate) + executePreparedStatement(handler, this.messagesInsert, Array(Some(content), None)) + + executePreparedStatement(handler, query, Array("undefined")) must throwA[GenericDatabaseException] + val result = executePreparedStatement(handler, query, Array(1)).rows.get + result(0)(0) === content + } + } + } } From 682e2bff8864209aa0e51a497cd71c4a6c02d812 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 3 Aug 2014 18:30:13 -0300 Subject: [PATCH 258/357] Make sure the matches are done with === --- .../mauricio/async/db/postgresql/PreparedStatementSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 890518f1..20c645cc 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -251,7 +251,7 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { handler => val string = "someString" val result = executePreparedStatement(handler, "SELECT CAST(? AS VARCHAR)", Array(string)).rows.get - result(0)(0) == string + result(0)(0) === string } } From 27c32f233db543d87dd4a601fc9604c4da91f323 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 30 Aug 2014 00:40:56 -0300 Subject: [PATCH 259/357] Closing 0.2.14 --- CHANGELOG.md | 14 ++++++++++++++ project/Build.scala | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97dab08e..21ee8997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.2.14 - 2014-08-30 + +* Remove failed prepared statement from cache - @dboissin - #95 +* Added support to zeroed dates on MySQL - #93 +* Cross compilation to Scala 2.11 is functional - @lpiepiora +* Connect to older MySQL versions where auth protocol isn't provided - #37 +* Eclipse project support - @fwbrasil - #89 +* Make timeouts configurable - @fwbrasil - #90 + +## 0.2.13 - 2014-04-07 + +* Accepts MySQL old and unsafe auth methods - #37 +* Do not name every single logger as they all leak - @njeuk #86 + ## 0.2.12 - 2014-01-11 * Do not check for handshake requests after a real handshake has happened already - MySQL - #80; diff --git a/project/Build.scala b/project/Build.scala index 8736fb0d..9fce8256 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.14-SNAPSHOT" + val commonVersion = "0.2.14" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" @@ -55,7 +55,7 @@ object Configuration { "org.slf4j" % "slf4j-api" % "1.7.5", "joda-time" % "joda-time" % "2.3", "org.joda" % "joda-convert" % "1.5", - "io.netty" % "netty-all" % "4.0.18.Final", + "io.netty" % "netty-all" % "4.0.23.Final", "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, logbackDependency From a51cbf98ceabf8e50897e36aace464be2f8c41ab Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 30 Aug 2014 00:56:59 -0300 Subject: [PATCH 260/357] Preparing for the next development cycle --- CHANGELOG.md | 22 ++++++++++++++++++++++ README.markdown | 26 +++++++++++++++++++++++++- mysql-async/README.md | 11 +++++++++++ postgresql-async/README.md | 19 +++++++++++++++---- project/Build.scala | 2 +- 5 files changed, 74 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ee8997..af445a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ + + +**Table of Contents** + +- [Changelog](#changelog) + - [0.2.14 - 2014-08-30](#0214---2014-08-30) + - [0.2.13 - 2014-04-07](#0213---2014-04-07) + - [0.2.12 - 2014-01-11](#0212---2014-01-11) + - [0.2.11 - 2014-01-11](#0211---2014-01-11) + - [0.2.10 - 2013-12-18](#0210---2013-12-18) + - [0.2.9 - 2013-12-01](#029---2013-12-01) + - [0.2.8 - 2013-09-24](#028---2013-09-24) + - [0.2.7 - 2013-09-09](#027---2013-09-09) + - [0.2.5](#025) + - [0.2.4 - 2013-07-06](#024---2013-07-06) + - [0.2.3 - 2013-05-21](#023---2013-05-21) + - [0.2.2 - 2013-05-18](#022---2013-05-18) + - [0.1.1 - 2013-04-30](#011---2013-04-30) + - [0.1.0 - 2013-04-29](#010---2013-04-29) + + + # Changelog ## 0.2.14 - 2014-08-30 diff --git a/README.markdown b/README.markdown index b82dd377..97d080ce 100644 --- a/README.markdown +++ b/README.markdown @@ -1,3 +1,27 @@ + + +**Table of Contents** + +- postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala - 2.10 - 2.11 + - [Abstractions and integrations](#abstractions-and-integrations) + - [Include them as dependencies](#include-them-as-dependencies) + - [Database connections and encodings](#database-connections-and-encodings) + - [Prepared statements gotcha](#prepared-statements-gotcha) + - [What are the design goals?](#what-are-the-design-goals) + - [What is missing?](#what-is-missing) + - [How can you help?](#how-can-you-help) + - [Main public interface](#main-public-interface) + - [Connection](#connection) + - [QueryResult](#queryresult) + - [ResultSet](#resultset) + - [Prepared statements](#prepared-statements) + - [Transactions](#transactions) + - [Example usage (for PostgreSQL, but it looks almost the same on MySQL)](#example-usage-for-postgresql-but-it-looks-almost-the-same-on-mysql) + - [Contributing](#contributing) + - [Licence](#licence) + + + # [![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 The main goal for this project is to implement simple, async, performant and reliable database drivers for @@ -123,7 +147,7 @@ So, prepared statements are awesome, but are not free. Use them judiciously. ### Connection Represents a connection to the database. This is the **root** object you will be using in your application. You will -find three classes that implement this trait, `PostgreSQLConnection`, `MySQLConnection` and `ConnectionPool`. +find three classes that implement this trait, `PostgreSQLConnection`, `MySQLConnection` and `ConnectionPool`. The difference between them is that `ConnectionPool` is, as the name implies, a pool of connections and you need to give it an connection factory so it can create connections and manage them. diff --git a/mysql-async/README.md b/mysql-async/README.md index 2c3055dd..82631754 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -1,3 +1,14 @@ + + +**Table of Contents** + +- [mysql-async - an async, Netty based, MySQL driver written in Scala 2.10](#mysql-async---an-async-netty-based-mysql-driver-written-in-scala-210) + - [What can it do now?](#what-can-it-do-now) + - [Gotchas](#gotchas) + - [Supported types](#supported-types) + + + # mysql-async - an async, Netty based, MySQL driver written in Scala 2.10 This is the MySQL part of the async driver collection. As the PostgreSQL version, it is not supposed to be a JDBC diff --git a/postgresql-async/README.md b/postgresql-async/README.md index dc4fde05..f05ebd9f 100644 --- a/postgresql-async/README.md +++ b/postgresql-async/README.md @@ -1,3 +1,14 @@ + + +**Table of Contents** + +- [postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10](#postgresql-async---an-async-netty-based-postgresql-driver-written-in-scala-210) + - [What can it do now?](#what-can-it-do-now) + - [What is missing?](#what-is-missing) + - [Supported Scala/Java types and their destination types on PostgreSQL](#supported-scalajava-types-and-their-destination-types-on-postgresql) + + + # postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10 The main goal of this project is to implement a performant and fully functional async PostgreSQL driver. This project @@ -32,7 +43,7 @@ This driver contains Java code from the [JDBC PostgreSQL](http://jdbc.postgresql ## Supported Scala/Java types and their destination types on PostgreSQL -All types also support their array versions, but they are returned as `IndexedSeq` of the type and not +All types also support their array versions, but they are returned as `IndexedSeq` of the type and not pure `Array` types. PostgreSQL type | Scala/Java type @@ -53,7 +64,7 @@ date | LocalDate time | LocalTime bytea | Array[Byte] (PostgreSQL 9.0 and above only) -All other types are returned as String. +All other types are returned as String. Now from Scala/Java types to PostgreSQL types (when using prepared statements): @@ -79,6 +90,6 @@ LocalDateTime | timestamp DateTime | timestamp_with_timezone LocalTime | time -Array types are encoded with the kind of object they hold and not the array type itself. Java `Collection` and -Scala `Traversable` objects are also assumed to be arrays of the types they hold and will be sent to PostgreSQL +Array types are encoded with the kind of object they hold and not the array type itself. Java `Collection` and +Scala `Traversable` objects are also assumed to be arrays of the types they hold and will be sent to PostgreSQL like that. diff --git a/project/Build.scala b/project/Build.scala index 9fce8256..c2138638 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.14" + val commonVersion = "0.2.15-SNAPSHOT" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" From 245359cbcf0452be302efcd30a2e0056c6e49d52 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 30 Aug 2014 01:04:04 -0300 Subject: [PATCH 261/357] Updating docs to reference Scala 2.11 as well --- README.markdown | 14 +++++++------- mysql-async/README.md | 7 +++---- postgresql-async/README.md | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.markdown b/README.markdown index 97d080ce..82f05d6a 100644 --- a/README.markdown +++ b/README.markdown @@ -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 +# [![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 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 @@ -51,7 +51,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.12" +"com.github.mauricio" %% "postgresql-async" % "0.2.14" ``` Or Maven: @@ -59,15 +59,15 @@ Or Maven: ```xml com.github.mauricio - postgresql-async_2.10 - 0.2.12 + postgresql-async_2.11 + 0.2.14 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.12" +"com.github.mauricio" %% "mysql-async" % "0.2.14" ``` Or Maven: @@ -75,8 +75,8 @@ Or Maven: ```xml com.github.mauricio - mysql-async_2.10 - 0.2.12 + mysql-async_2.11 + 0.2.14 ``` diff --git a/mysql-async/README.md b/mysql-async/README.md index 82631754..207d60d1 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -2,17 +2,16 @@ **Table of Contents** -- [mysql-async - an async, Netty based, MySQL driver written in Scala 2.10](#mysql-async---an-async-netty-based-mysql-driver-written-in-scala-210) +- [mysql-async - an async, Netty based, MySQL driver written in Scala 2.10 and 2.11](#mysql-async---an-async-netty-based-mysql-driver-written-in-scala-210) - [What can it do now?](#what-can-it-do-now) - [Gotchas](#gotchas) - [Supported types](#supported-types) -# mysql-async - an async, Netty based, MySQL driver written in Scala 2.10 +# mysql-async - an async, Netty based, MySQL driver written in Scala 2.10 and 2.11 -This is the MySQL part of the async driver collection. As the PostgreSQL version, it is not supposed to be a JDBC -replacement, but a simpler solution for those that need something that queries and then returns rows. +This is the MySQL part of the async driver collection. As the PostgreSQL version, it is not supposed to be a JDBC replacement, but a simpler solution for those that need something that queries and then returns rows. You can find more information about the MySQL network protocol [here](http://dev.mysql.com/doc/internals/en/client-server-protocol.html). diff --git a/postgresql-async/README.md b/postgresql-async/README.md index f05ebd9f..384cb647 100644 --- a/postgresql-async/README.md +++ b/postgresql-async/README.md @@ -2,14 +2,14 @@ **Table of Contents** -- [postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10](#postgresql-async---an-async-netty-based-postgresql-driver-written-in-scala-210) +- [postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10 and 2.11](#postgresql-async---an-async-netty-based-postgresql-driver-written-in-scala-210) - [What can it do now?](#what-can-it-do-now) - [What is missing?](#what-is-missing) - [Supported Scala/Java types and their destination types on PostgreSQL](#supported-scalajava-types-and-their-destination-types-on-postgresql) -# postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10 +# postgresql-async - an async Netty based PostgreSQL driver written in Scala 2.10 and 2.11 The main goal of this project is to implement a performant and fully functional async PostgreSQL driver. This project has no interest in JDBC, it's supposed to be a clean room implementation for people interested in talking directly From fbcd3029ef99c86f3b5761e54a82663888b8cc1c Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 6 Sep 2014 16:23:20 -0300 Subject: [PATCH 262/357] Handle the case where the encoder produces a null value (as it would for Some(null)) - possible fix to #99 --- .../PostgreSQLColumnEncoderRegistry.scala | 5 ++-- .../ExecutePreparedStatementEncoder.scala | 5 ++-- .../PreparedStatementEncoderHelper.scala | 29 ++++++++++++------ .../PostgreSQLColumnEncoderRegistrySpec.scala | 10 +++++++ .../ExecutePreparedStatementEncoderSpec.scala | 30 +++++++++++++++++++ 5 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoderSpec.scala 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 b5f32735..e09a2aed 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 @@ -18,7 +18,7 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.column._ import org.joda.time._ -import scala.Some + import scala.collection.JavaConversions._ object PostgreSQLColumnEncoderRegistry { @@ -127,7 +127,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { val result = collection.map { item => - if (item == null) { + if (item == null || item == None) { "NULL" } else { if (this.shouldQuote(item)) { @@ -177,4 +177,5 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { } } } + } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala index 375b5043..f1c605c2 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoder.scala @@ -16,11 +16,10 @@ package com.github.mauricio.async.db.postgresql.encoders +import java.nio.charset.Charset + import com.github.mauricio.async.db.column.ColumnEncoderRegistry -import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage import com.github.mauricio.async.db.postgresql.messages.frontend.{ClientMessage, PreparedStatementExecuteMessage} -import com.github.mauricio.async.db.util.ByteBufferUtils -import java.nio.charset.Charset import io.netty.buffer.ByteBuf class ExecutePreparedStatementEncoder( diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala index 2ab3df15..4f0716b9 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/PreparedStatementEncoderHelper.scala @@ -33,13 +33,17 @@ trait PreparedStatementEncoderHelper { def writeExecutePortal( statementIdBytes: Array[Byte], - query : String, + query: String, values: Seq[Any], encoder: ColumnEncoderRegistry, charset: Charset, writeDescribe: Boolean = false ): ByteBuf = { + if (log.isDebugEnabled) { + log.debug(s"Preparing execute portal to statement ($query) - values (${values.mkString(", ")}) - ${charset}") + } + val bindBuffer = Unpooled.buffer(1024) bindBuffer.writeByte(ServerMessage.Bind) @@ -54,14 +58,14 @@ trait PreparedStatementEncoderHelper { bindBuffer.writeShort(values.length) - val decodedValues = if ( log.isDebugEnabled ) { + val decodedValues = if (log.isDebugEnabled) { new ArrayBuffer[String](values.size) } else { null } for (value <- values) { - if (value == null || value == None) { + if (isNull(value)) { bindBuffer.writeInt(-1) if (log.isDebugEnabled) { @@ -70,25 +74,30 @@ trait PreparedStatementEncoderHelper { } else { val encodedValue = encoder.encode(value) - if ( log.isDebugEnabled ) { + if (log.isDebugEnabled) { decodedValues += encodedValue } - val content = encodedValue.getBytes(charset) - bindBuffer.writeInt(content.length) - bindBuffer.writeBytes( content ) + if (isNull(encodedValue)) { + bindBuffer.writeInt(-1) + } else { + val content = encodedValue.getBytes(charset) + bindBuffer.writeInt(content.length) + bindBuffer.writeBytes(content) + } + } } if (log.isDebugEnabled) { - log.debug(s"Executing query - statement id (${statementIdBytes.mkString("-")}) - statement ($query) - encoded values (${decodedValues.mkString(", ")}) - original values (${values.mkString(", ")})") + log.debug(s"Executing portal - statement id (${statementIdBytes.mkString("-")}) - statement ($query) - encoded values (${decodedValues.mkString(", ")}) - original values (${values.mkString(", ")})") } bindBuffer.writeShort(0) ByteBufferUtils.writeLength(bindBuffer) - if ( writeDescribe ) { + if (writeDescribe) { val describeLength = 1 + 4 + 1 + statementIdBytes.length + 1 val describeBuffer = bindBuffer describeBuffer.writeByte(ServerMessage.Describe) @@ -122,4 +131,6 @@ trait PreparedStatementEncoderHelper { } + def isNull(value: Any): Boolean = value == null || value == None + } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala index e30b7494..9e1b5e94 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLColumnEncoderRegistrySpec.scala @@ -46,6 +46,16 @@ class PostgreSQLColumnEncoderRegistrySpec extends Specification { actual mustEqual expected } + "encodes Some(null) as null" in { + val actual = encoder.encode(Some(null)) + actual mustEqual null + } + + "encodes null as null" in { + val actual = encoder.encode(null) + actual mustEqual null + } + } } \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoderSpec.scala new file mode 100644 index 00000000..9342a703 --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/encoders/ExecutePreparedStatementEncoderSpec.scala @@ -0,0 +1,30 @@ +package com.github.mauricio.async.db.postgresql.encoders + +import com.github.mauricio.async.db.postgresql.column.PostgreSQLColumnEncoderRegistry +import com.github.mauricio.async.db.postgresql.messages.frontend.PreparedStatementExecuteMessage +import io.netty.util.CharsetUtil +import org.specs2.mutable.Specification + +class ExecutePreparedStatementEncoderSpec extends Specification { + + val registry = new PostgreSQLColumnEncoderRegistry() + val encoder = new ExecutePreparedStatementEncoder(CharsetUtil.UTF_8, registry) + val sampleMessage = Array[Byte](66,0,0,0,18,49,0,49,0,0,0,0,1,-1,-1,-1,-1,0,0,69,0,0,0,10,49,0,0,0,0,0,83,0,0,0,4,67,0,0,0,7,80,49,0) + + "encoder" should { + + "correctly handle the case where an encoder returns null" in { + + val message = new PreparedStatementExecuteMessage(1, "select * from users", List(Some(null)), registry) + + val result = encoder.encode(message) + + val bytes = new Array[Byte](result.readableBytes()) + result.readBytes(bytes) + + bytes === sampleMessage + } + + } + +} From 6148f6897f6e690b66962c26e1b701f3395919db Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 6 Sep 2014 17:34:44 -0300 Subject: [PATCH 263/357] Correctly handle the length case for 253 bytes - fixes #100 --- CHANGELOG.md | 5 +++ .../async/db/util/ChannelWrapper.scala | 23 ++++------ .../mysql/decoder/ResultSetRowDecoder.scala | 15 +++---- .../mauricio/async/db/mysql/QuerySpec.scala | 44 ++++++++++++++----- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af445a82..48ef302b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ # Changelog +## 0.2.15 - still in progress + +* Fixes issue where PostgreSQL decoders fail to produce a NULL value if the null is wrapped by a `Some` instance - #99; +* Fixes issue where the 253 case of length encoded fields on MySQL produce a wrong value; + ## 0.2.14 - 2014-08-30 * Remove failed prepared statement from cache - @dboissin - #95 diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala index c62e5efb..94bca43e 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/ChannelWrapper.scala @@ -43,19 +43,6 @@ class ChannelWrapper( val buffer : ByteBuf ) extends AnyVal { def readUntilEOF( charset: Charset ) = ByteBufferUtils.readUntilEOF(buffer, charset) - def read3BytesInt : Int = { - val first = buffer.readByte() - val second = buffer.readByte() - val third = buffer.readByte() - var i = third << 16 | second << 8 | first - - if ((third & 0x80) == 0x80) { - i |= 0xff000000 - } - - i - } - def readLengthEncodedString( charset : Charset ) : String = { val length = readBinaryLength readFixedString(length.asInstanceOf[Int], charset) @@ -70,7 +57,7 @@ class ChannelWrapper( val buffer : ByteBuf ) extends AnyVal { firstByte match { case MySQL_NULL => -1 case 252 => buffer.readUnsignedShort() - case 253 => read3BytesInt + case 253 => readLongInt case 254 => buffer.readLong() case _ => throw new UnknownLengthException(firstByte) } @@ -78,6 +65,14 @@ class ChannelWrapper( val buffer : ByteBuf ) extends AnyVal { } + def readLongInt : Int = { + val first = buffer.readByte() + val second = buffer.readByte() + val third = buffer.readByte() + + ( first & 0xff ) | (( second & 0xff ) << 8) | ((third & 0xff) << 16) + } + def writeLength( length : Long ) { if (length < 251) { buffer.writeByte( length.asInstanceOf[Byte]) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala index 96288bf3..1a2a9fbd 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/decoder/ResultSetRowDecoder.scala @@ -16,11 +16,11 @@ package com.github.mauricio.async.db.mysql.decoder -import io.netty.buffer.ByteBuf +import java.nio.charset.Charset + import com.github.mauricio.async.db.mysql.message.server.{ResultSetRowMessage, ServerMessage} import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import java.nio.charset.Charset -import java.nio.ByteOrder +import io.netty.buffer.ByteBuf object ResultSetRowDecoder { @@ -28,21 +28,20 @@ object ResultSetRowDecoder { } -class ResultSetRowDecoder( charset : Charset ) extends MessageDecoder { +class ResultSetRowDecoder(charset: Charset) extends MessageDecoder { - import ResultSetRowDecoder.NULL + import com.github.mauricio.async.db.mysql.decoder.ResultSetRowDecoder.NULL def decode(buffer: ByteBuf): ServerMessage = { val row = new ResultSetRowMessage() - while (buffer.isReadable() ) { - if ( buffer.getUnsignedByte(buffer.readerIndex()) == NULL ) { + while (buffer.isReadable()) { + if (buffer.getUnsignedByte(buffer.readerIndex()) == NULL) { buffer.readByte() row += null } else { val length = buffer.readBinaryLength.asInstanceOf[Int] row += buffer.readBytes(length) - } } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala index 039ae55d..4e249387 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QuerySpec.scala @@ -93,7 +93,7 @@ class QuerySpec extends Specification with ConnectionHelper { timestamp.getSecondOfMinute === 7 - result("created_at_time") === Duration( 3, TimeUnit.HOURS ) + Duration( 14, TimeUnit.MINUTES ) + Duration( 7, TimeUnit.SECONDS ) + result("created_at_time") === Duration(3, TimeUnit.HOURS) + Duration(14, TimeUnit.MINUTES) + Duration(7, TimeUnit.SECONDS) val year = result("created_at_year").asInstanceOf[Short] @@ -150,21 +150,21 @@ class QuerySpec extends Specification with ConnectionHelper { | primary key (id) )""".stripMargin val createIdeas = """CREATE TEMPORARY TABLE ideas ( - | id INT NOT NULL AUTO_INCREMENT, - | some_idea VARCHAR(255) NOT NULL, - | primary key (id) )""".stripMargin + | id INT NOT NULL AUTO_INCREMENT, + | some_idea VARCHAR(255) NOT NULL, + | primary key (id) )""".stripMargin val select = "SELECT * FROM posts" val selectIdeas = "SELECT * FROM ideas" - val matcher : QueryResult => List[MatchResult[IndexedSeq[String]]] = { result => + val matcher: QueryResult => List[MatchResult[IndexedSeq[String]]] = { result => val columns = result.rows.get.columnNames - List(columns must contain(allOf("id", "some_bytes")).inOrder, columns must have size(2)) + List(columns must contain(allOf("id", "some_bytes")).inOrder, columns must have size (2)) } - val ideasMatcher : QueryResult => List[MatchResult[IndexedSeq[String]]] = { result => + val ideasMatcher: QueryResult => List[MatchResult[IndexedSeq[String]]] = { result => val columns = result.rows.get.columnNames - List(columns must contain(allOf("id", "some_idea")).inOrder, columns must have size(2)) + List(columns must contain(allOf("id", "some_idea")).inOrder, columns must have size (2)) } withConnection { @@ -204,10 +204,10 @@ class QuerySpec extends Specification with ConnectionHelper { executeQuery(connection, insert) val rows = executeQuery(connection, select).rows.get - rows(0)("bit_column") === Array(0,0,-128) + rows(0)("bit_column") === Array(0, 0, -128) val preparedRows = executePreparedStatement(connection, select).rows.get - preparedRows(0)("bit_column") === Array(0,0,-128) + preparedRows(0)("bit_column") === Array(0, 0, -128) } } @@ -264,6 +264,30 @@ class QuerySpec extends Specification with ConnectionHelper { } + "select from a large text column" in { + + val create = "create temporary table bombs (id char(4), bomb mediumtext character set ascii)" + + val insert = """ insert bombs values + | ('bomb', repeat(' ',65536+16384+8192+4096+2048+1024+512+256+128)), + | ('good', repeat(' ',65536+16384+8192+4096+2048+1024+512+256+128-1))""".stripMargin + + + withConnection { + connection => + executeQuery(connection, create) + executeQuery(connection, insert) + val result = executeQuery(connection, "select bomb from bombs").rows.get + + result.size === 2 + + result(0)("bomb").asInstanceOf[String].length === 98176 + result(1)("bomb").asInstanceOf[String].length === 98175 + } + + } + + } } From 59740b4cab0731c21d24068a1a047c87873ad67b Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 12 Sep 2014 16:33:29 -0300 Subject: [PATCH 264/357] Closing 0.2.15 --- CHANGELOG.md | 6 +++++- README.markdown | 8 ++++---- project/Build.scala | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ef302b..b020fd79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ **Table of Contents** - [Changelog](#changelog) + - [0.2.16 - in progress](#0216---in-progress) + - [0.2.15 - 2014-09-12](#0215---2014-09-12) - [0.2.14 - 2014-08-30](#0214---2014-08-30) - [0.2.13 - 2014-04-07](#0213---2014-04-07) - [0.2.12 - 2014-01-11](#0212---2014-01-11) @@ -22,7 +24,9 @@ # Changelog -## 0.2.15 - still in progress +## 0.2.16 - in progress + +## 0.2.15 - 2014-09-12 * Fixes issue where PostgreSQL decoders fail to produce a NULL value if the null is wrapped by a `Some` instance - #99; * Fixes issue where the 253 case of length encoded fields on MySQL produce a wrong value; diff --git a/README.markdown b/README.markdown index 82f05d6a..adc395ba 100644 --- a/README.markdown +++ b/README.markdown @@ -51,7 +51,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.14" +"com.github.mauricio" %% "postgresql-async" % "0.2.15" ``` Or Maven: @@ -60,14 +60,14 @@ Or Maven: com.github.mauricio postgresql-async_2.11 - 0.2.14 + 0.2.15 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.14" +"com.github.mauricio" %% "mysql-async" % "0.2.15" ``` Or Maven: @@ -76,7 +76,7 @@ Or Maven: com.github.mauricio mysql-async_2.11 - 0.2.14 + 0.2.15 ``` diff --git a/project/Build.scala b/project/Build.scala index c2138638..12996892 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.15-SNAPSHOT" + val commonVersion = "0.2.15" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" From de17c435ac1e8b211692c693c15d1e8c68db9895 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 12 Sep 2014 16:43:17 -0300 Subject: [PATCH 265/357] Starting 0.2.16 cycle --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 12996892..dff7c993 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.15" + val commonVersion = "0.2.16-SNAPSHOT" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" From 8de12b77b91f5bb01510f3973e7f79a17f744c6c Mon Sep 17 00:00:00 2001 From: Nick Edwards Date: Mon, 15 Sep 2014 14:29:51 +0100 Subject: [PATCH 266/357] Add dbmapper to the list of abstractions and integrations --- README.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.markdown b/README.markdown index adc395ba..342425b9 100644 --- a/README.markdown +++ b/README.markdown @@ -45,6 +45,8 @@ You can view the project's [CHANGELOG here](CHANGELOG.md). driver allowing you to write less SQL and make use of a nice high level database access API; * [mod-mysql-postgresql](https://github.com/vert-x/mod-mysql-postgresql) - [vert.x](http://vertx.io/) module that integrates the driver into a vert.x application; +* [dbmapper](https://github.com/njeuk/dbmapper) - enables SQL queries with automatic mapping from the database table to the Scala + class and a mechanism to create a Table Date Gateway model with very little boiler plate code; ## Include them as dependencies From 266cdf83a7a104e7b7719c4d99f10f5a204f29e8 Mon Sep 17 00:00:00 2001 From: africastalking Date: Mon, 29 Sep 2014 21:01:37 +0300 Subject: [PATCH 267/357] - Make lastInsertId, statusFlag and warnings accessible beyond construction --- .../mauricio/async/db/mysql/MySQLQueryResult.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala index 7b9cfe57..e7619685 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLQueryResult.scala @@ -19,9 +19,9 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.{ResultSet, QueryResult} class MySQLQueryResult( - rowsAffected: Long, - message: String, - lastInsertId: Long, - statusFlags: Int, - warnings: Int, - rows: Option[ResultSet] = None) extends QueryResult(rowsAffected, message, rows) \ No newline at end of file + rowsAffected: Long, + message: String, + val lastInsertId: Long, + val statusFlags: Int, + val warnings: Int, + rows: Option[ResultSet] = None) extends QueryResult(rowsAffected, message, rows) From 7349866475e79753ee78697f6d1e60d2571edd43 Mon Sep 17 00:00:00 2001 From: dboissin Date: Sun, 5 Oct 2014 15:28:52 +0200 Subject: [PATCH 268/357] clear old query return value --- .../async/db/postgresql/PostgreSQLConnection.scala | 1 + .../db/postgresql/PostgreSQLConnectionSpec.scala | 14 ++++++++++++++ 2 files changed, 15 insertions(+) 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 054bab50..45d53901 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 @@ -303,6 +303,7 @@ class PostgreSQLConnection private def succeedQueryPromise(result: QueryResult) { this.queryResult = None + this.currentQuery = None this.clearQueryPromise.foreach { _.success(result) } 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 93244111..632c150f 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 @@ -428,6 +428,20 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { } + "insert without return after select" in { + + withHandler { + handler => + executeDdl(handler, this.preparedStatementCreate) + executeDdl(handler, this.preparedStatementInsert, 1) + executeDdl(handler, this.preparedStatementSelect, 1) + val result = executeQuery(handler, this.preparedStatementInsert2) + + result.rows === None + } + + } + } } From e4e48809224526c8e90dab22bc015c6bd698afa8 Mon Sep 17 00:00:00 2001 From: Stephen Couchman Date: Wed, 12 Nov 2014 14:32:39 -0500 Subject: [PATCH 269/357] Implemented 'escape' decoding and associated test spec. --- .../column/ByteArrayEncoderDecoder.scala | 46 ++++++++++++- .../column/ByteArrayDecoderSpec.scala | 67 +++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayDecoderSpec.scala diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala index bfaed46e..0ec7e4f7 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala @@ -18,7 +18,8 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.column.ColumnEncoderDecoder import com.github.mauricio.async.db.postgresql.exceptions.ByteArrayFormatNotSupportedException -import com.github.mauricio.async.db.util.{Log, HexCodec} +import com.github.mauricio.async.db.util.{ Log, HexCodec } +import java.nio.ByteBuffer object ByteArrayEncoderDecoder extends ColumnEncoderDecoder { @@ -31,11 +32,52 @@ object ByteArrayEncoderDecoder extends ColumnEncoderDecoder { if (value.startsWith(HexStart)) { HexCodec.decode(value, 2) } else { - throw new ByteArrayFormatNotSupportedException() + // Default encoding is 'escape' + + // Size the buffer to the length of the string, the data can't be bigger + val buffer = ByteBuffer.allocate(value.length) + + val ci = value.iterator + + while (ci.hasNext) { + ci.next match { + case '\\' ⇒ getCharOrDie(ci) match { + case '\\' ⇒ buffer.put('\\'.toByte) + case firstDigit ⇒ + val secondDigit = getCharOrDie(ci) + val thirdDigit = getCharOrDie(ci) + // Must always be in triplets + buffer.put( + Integer.decode( + new String(Array('0', firstDigit, secondDigit, thirdDigit))).toByte) + } + case c ⇒ buffer.put(c.toByte) + } + } + + buffer.flip + val finalArray = new Array[Byte](buffer.remaining()) + buffer.get(finalArray) + + finalArray } } + /** + * This is required since {@link Iterator#next} when {@linke Iterator#hasNext} is false is undefined. + * @param ci the iterator source of the data + * @return the next character + * @throws IllegalArgumentException if there is no next character + */ + private [this] def getCharOrDie(ci: Iterator[Char]): Char = { + if (ci.hasNext) { + ci.next() + } else { + throw new IllegalArgumentException("Expected escape sequence character, found nothing") + } + } + override def encode(value: Any): String = { HexCodec.encode(value.asInstanceOf[Array[Byte]], HexStartChars) } diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayDecoderSpec.scala new file mode 100644 index 00000000..328e872f --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayDecoderSpec.scala @@ -0,0 +1,67 @@ +/* + * 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.column + +import org.specs2.mutable.Specification + +class ByteArrayDecoderSpec extends Specification { + + val escapeTestData = + """\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027""" + + """\030\031\032\033\034\035\036\037 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^""" + + """_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216""" + + """\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246""" + + """\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276""" + + """\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326""" + + """\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356""" + + """\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\377\376\375\374\373\372\371""" + + """\370\367\366\365\364\363\362\361\360\357\356\355\354\353\352\351\350\347\346\345\344\343\342\341""" + + """\340\337\336\335\334\333\332\331\330\327\326\325\324\323\322\321\320\317\316\315\314\313\312\311""" + + """\310\307\306\305\304\303\302\301\300\277\276\275\274\273\272\271\270\267\266\265\264\263\262\261""" + + """\260\257\256\255\254\253\252\251\250\247\246\245\244\243\242\241\240\237\236\235\234\233\232\231""" + + """\230\227\226\225\224\223\222\221\220\217\216\215\214\213\212\211\210\207\206\205\204\203\202\201""" + + """\200\177~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(""" + + """'&%$#"! \037\036\035\034\033\032\031\030\027\026\025\024\023\022\021\020\017\016\015\014\013\012""" + + """\011\010\007\006\005\004\003\002\001\000""" + + val hexTestData = + """\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e""" + + """2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e""" + + """5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e""" + + """8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe""" + + """bfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedee""" + + """eff0f1f2f3f4f5f6f7f8f9fafbfcfdfefffffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1""" + + """e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1""" + + """b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281""" + + """807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251""" + + """504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221""" + + """201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100""" + + val originalData = ((0 to 255) ++ ((0 to 255).reverse)).map(_.toByte).toArray + + "decoder" should { + + "parse escape data" in { + ByteArrayEncoderDecoder.decode(escapeTestData) === originalData + } + + "parse hex data" in { + ByteArrayEncoderDecoder.decode(hexTestData) === originalData + } + } + +} From 74e9495e4d7b0a8db8f18a52ee467bd4b62411cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Thu, 13 Nov 2014 20:55:18 +0100 Subject: [PATCH 270/357] Updating future changelog for 0.2.16 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b020fd79..d05f17d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ ## 0.2.16 - in progress +* Add support to byte arrays for PostgreSQL 8 and older - @SattaiLanfear - #21; + ## 0.2.15 - 2014-09-12 * Fixes issue where PostgreSQL decoders fail to produce a NULL value if the null is wrapped by a `Some` instance - #99; From 993f8f1fda8b26df2b1db147fb1203df3576bd98 Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Tue, 25 Nov 2014 14:54:12 -0500 Subject: [PATCH 271/357] Fix encoding of backslashes in arrays --- .../postgresql/column/PostgreSQLColumnEncoderRegistry.scala | 2 +- .../mauricio/async/db/postgresql/ArrayTypesSpec.scala | 6 +++--- .../column/DefaultColumnEncoderRegistrySpec.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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 e09a2aed..70d47da7 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 @@ -131,7 +131,7 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { "NULL" } else { if (this.shouldQuote(item)) { - "\"" + this.encode(item).replaceAllLiterally("\"", """\"""") + "\"" + "\"" + this.encode(item).replaceAllLiterally("\\", """\\""").replaceAllLiterally("\"", """\"""") + "\"" } else { this.encode(item) } 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 7396aeb3..e941e145 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 @@ -34,7 +34,7 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { (smallint_column, text_column, timestamp_column) values ( '{1,2,3,4}', - '{"some,\"comma,separated,text","another line of text",NULL}', + '{"some,\"comma,separated,text","another line of text","fake\,backslash","real\\,backslash\\",NULL}', '{"2013-04-06 01:15:10.528-03","2013-04-06 01:15:08.528-03"}' )""" @@ -52,7 +52,7 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { 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", null ) + 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") @@ -68,7 +68,7 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:08.528-03") ) val numbers = List(1,2,3,4) - val texts = List("some,\"comma,separated,text", "another line of text", null ) + val texts = List("some,\"comma,separated,text", "another line of text", "fake,backslash", "real\\,backslash\\", null ) withHandler { handler => diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala index 88965d49..1b41f447 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/column/DefaultColumnEncoderRegistrySpec.scala @@ -26,7 +26,7 @@ class DefaultColumnEncoderRegistrySpec extends Specification { "correctly render an array of strings with nulls" in { val items = Array( "some", """text \ hoes " here to be seen""", null, "all, right" ) - registry.encode( items ) === """{"some","text \ hoes \" here to be seen",NULL,"all, right"}""" + registry.encode( items ) === """{"some","text \\ hoes \" here to be seen",NULL,"all, right"}""" } "correctly render an array of numbers" in { From ab4ae3df99c6603e438f856ce9924aa1b285ac3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Thu, 4 Dec 2014 17:47:00 +0100 Subject: [PATCH 272/357] Support ByteBuffer and ByteBuf as parameters in prepared statements --- .../db/mysql/binary/BinaryRowEncoder.scala | 3 ++ .../mysql/binary/encoder/ByteBufEncoder.scala | 17 ++++++++++ .../binary/encoder/ByteBufferEncoder.scala | 19 +++++++++++ .../async/db/mysql/BinaryColumnsSpec.scala | 32 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index c904259e..310f80ca 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.mysql.binary import io.netty.buffer.{Unpooled, ByteBuf} +import java.nio.ByteBuffer import java.nio.charset.Charset import com.github.mauricio.async.db.mysql.binary.encoder._ import com.github.mauricio.async.db.util._ @@ -128,6 +129,8 @@ class BinaryRowEncoder( charset : Charset ) { case v : java.sql.Time => SQLTimeEncoder case v : scala.concurrent.duration.Duration => DurationEncoder case v : java.util.Date => JavaDateEncoder + case v : ByteBuffer => ByteBufferEncoder + case v : ByteBuf => ByteBufEncoder } } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala new file mode 100644 index 00000000..62b62560 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala @@ -0,0 +1,17 @@ +package com.github.mauricio.async.db.mysql.binary.encoder + +import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import io.netty.buffer.ByteBuf + +object ByteBufEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ByteBuf) { + val bytes = value.asInstanceOf[ByteBuf] + + buffer.writeLength(bytes.readableBytes()) + buffer.writeBytes(bytes) + } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_BLOB + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala new file mode 100644 index 00000000..329709ad --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala @@ -0,0 +1,19 @@ +package com.github.mauricio.async.db.mysql.binary.encoder + +import java.nio.ByteBuffer + +import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import io.netty.buffer.ByteBuf + +object ByteBufferEncoder extends BinaryEncoder { + def encode(value: Any, buffer: ByteBuf) { + val bytes = value.asInstanceOf[ByteBuffer] + + buffer.writeLength(bytes.remaining()) + buffer.writeBytes(bytes) + } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_BLOB + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala index 22912620..5ff25f99 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala @@ -2,6 +2,8 @@ package com.github.mauricio.async.db.mysql import org.specs2.mutable.Specification import java.util.UUID +import java.nio.ByteBuffer +import io.netty.buffer.Unpooled import io.netty.util.CharsetUtil import com.github.mauricio.async.db.RowData @@ -96,6 +98,36 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { } + "support BLOB type" in { + + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL AUTO_INCREMENT, + | blob_column BLOB(20), + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (blob_column) VALUES (?)" + val select = "SELECT * FROM POSTS" + val bytes = (1 to 10).map(_.toByte).toArray + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, bytes) + executePreparedStatement(connection, insert, ByteBuffer.wrap(bytes)) + executePreparedStatement(connection, insert, Unpooled.copiedBuffer(bytes)) + + val Some(rows) = executeQuery(connection, select).rows + rows foreach { + row => + row("blob_column") === bytes + } + rows.size === 3 + } + + } + } def compareBytes( row : RowData, column : String, expected : String ) = From 147fc39b8c61a625dd0ac8fe2ff0d588951ca4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Fri, 5 Dec 2014 11:31:37 +0100 Subject: [PATCH 273/357] Document supporting ByteBuffer and ByteBuf as parameters in prepared statements --- mysql-async/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mysql-async/README.md b/mysql-async/README.md index 207d60d1..adff299e 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -89,6 +89,8 @@ java.sql.Timestamp | timestamp java.sql.Time | time String | string Array[Byte] | blob +java.nio.ByteBuffer | blob +io.netty.buffer.ByteBuf | blob You don't have to match exact values when sending parameters for your prepared statements, MySQL is usually smart enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. From 97144d4b80c9cd35acc53c76021aa8b757148cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Fri, 5 Dec 2014 17:10:04 +0100 Subject: [PATCH 274/357] Support ByteBuffer and ByteBuf as parameters in prepared statements for PostgreSQL --- .../column/ByteArrayEncoderDecoder.scala | 22 ++++++++++++++++++- .../PostgreSQLColumnEncoderRegistry.scala | 7 +++++- .../postgresql/PostgreSQLConnectionSpec.scala | 7 ++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala index 0ec7e4f7..2ae1e7a4 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ByteArrayEncoderDecoder.scala @@ -21,6 +21,8 @@ import com.github.mauricio.async.db.postgresql.exceptions.ByteArrayFormatNotSupp import com.github.mauricio.async.db.util.{ Log, HexCodec } import java.nio.ByteBuffer +import io.netty.buffer.ByteBuf + object ByteArrayEncoderDecoder extends ColumnEncoderDecoder { final val log = Log.getByName(this.getClass.getName) @@ -79,7 +81,25 @@ object ByteArrayEncoderDecoder extends ColumnEncoderDecoder { } override def encode(value: Any): String = { - HexCodec.encode(value.asInstanceOf[Array[Byte]], HexStartChars) + val array = value match { + case byteArray: Array[Byte] => byteArray + + case byteBuffer: ByteBuffer if byteBuffer.hasArray => byteBuffer.array() + + case byteBuffer: ByteBuffer => + val arr = new Array[Byte](byteBuffer.remaining()) + byteBuffer.get(arr) + arr + + case byteBuf: ByteBuf if byteBuf.hasArray => byteBuf.array() + + case byteBuf: ByteBuf => + val arr = new Array[Byte](byteBuf.readableBytes()) + byteBuf.getBytes(0, arr) + arr + } + + HexCodec.encode(array, HexStartChars) } } 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 70d47da7..5afb5aa8 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 @@ -16,7 +16,10 @@ package com.github.mauricio.async.db.postgresql.column +import java.nio.ByteBuffer + import com.github.mauricio.async.db.column._ +import io.netty.buffer.ByteBuf import org.joda.time._ import scala.collection.JavaConversions._ @@ -64,7 +67,9 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[java.sql.Timestamp] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.util.Calendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), classOf[java.util.GregorianCalendar] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), - classOf[Array[Byte]] -> ( ByteArrayEncoderDecoder -> ColumnTypes.ByteA ) + classOf[Array[Byte]] -> ( ByteArrayEncoderDecoder -> ColumnTypes.ByteA ), + classOf[ByteBuffer] -> ( ByteArrayEncoderDecoder -> ColumnTypes.ByteA ), + classOf[ByteBuf] -> ( ByteArrayEncoderDecoder -> ColumnTypes.ByteA ) ) private final val classesSequence = (classOf[LocalTime] -> (TimeEncoderDecoder.Instance -> ColumnTypes.Time)) :: 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 632c150f..c9876721 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 @@ -16,6 +16,8 @@ package com.github.mauricio.postgresql +import java.nio.ByteBuffer + import com.github.mauricio.async.db.column.{TimestampEncoderDecoder, TimeEncoderDecoder, DateEncoderDecoder} import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException import com.github.mauricio.async.db.postgresql.exceptions.{QueryMustNotBeNullOrEmptyException, GenericDatabaseException} @@ -23,6 +25,7 @@ import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessa import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} +import io.netty.buffer.Unpooled import concurrent.{Future, Await} import org.specs2.mutable.Specification import scala.concurrent.ExecutionContext.Implicits.global @@ -406,10 +409,14 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { executeDdl(handler, create) log.debug("executed create") executePreparedStatement(handler, insert, Array( sampleArray )) + executePreparedStatement(handler, insert, Array( ByteBuffer.wrap(sampleArray) )) + executePreparedStatement(handler, insert, Array( Unpooled.copiedBuffer(sampleArray) )) log.debug("executed prepared statement") val rows = executeQuery(handler, select).rows.get rows(0)("content").asInstanceOf[Array[Byte]] === sampleArray + rows(1)("content").asInstanceOf[Array[Byte]] === sampleArray + rows(2)("content").asInstanceOf[Array[Byte]] === sampleArray } } From 7d873363fdd4d3fff5bdfc3a63449e109eb5135b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Fri, 5 Dec 2014 17:12:22 +0100 Subject: [PATCH 275/357] Support ByteBuffer and ByteBuf as parameters in prepared statements documentation --- postgresql-async/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/postgresql-async/README.md b/postgresql-async/README.md index 384cb647..7702e907 100644 --- a/postgresql-async/README.md +++ b/postgresql-async/README.md @@ -81,6 +81,8 @@ BigInteger | numeric BigDecimal | numeric String | varchar Array[Byte] | bytea (PostgreSQL 9.0 and above only) +java.nio.ByteBuffer | bytea (PostgreSQL 9.0 and above only) +io.netty.buffer.ByteBuf | bytea (PostgreSQL 9.0 and above only) java.util.Date | timestamp_with_timezone java.sql.Timestamp | timestamp_with_timezone java.sql.Date | date From b7dc09e7ce9bf1d0a7658123d38edc6a9539ef35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Tue, 9 Dec 2014 10:20:24 +0100 Subject: [PATCH 276/357] Use COM_STMT_SEND_LONG_DATA to optimize inserting long BLOB values for MySQL --- .../async/db/mysql/MySQLConnection.scala | 2 +- .../db/mysql/binary/BinaryRowEncoder.scala | 29 ++++++++- .../mysql/binary/encoder/BinaryEncoder.scala | 6 ++ .../binary/encoder/ByteArrayEncoder.scala | 7 ++- .../mysql/binary/encoder/ByteBufEncoder.scala | 5 ++ .../binary/encoder/ByteBufferEncoder.scala | 7 ++- .../mysql/codec/MySQLConnectionHandler.scala | 19 ++++-- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 8 ++- .../PreparedStatement.scala} | 5 +- .../mysql/encoder/SendLongDataEncoder.scala | 21 +++++++ .../mysql/message/client/ClientMessage.scala | 14 ++--- .../message/client/SendLongDataMessage.scala | 10 +++ .../async/db/mysql/BinaryColumnsSpec.scala | 62 ++++++++++++------- 13 files changed, 151 insertions(+), 44 deletions(-) rename mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/{message/client/PreparedStatementMessage.scala => codec/PreparedStatement.scala} (76%) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index a48e8739..210cebfc 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -236,7 +236,7 @@ class MySQLConnection( } val promise = Promise[QueryResult] this.setQueryPromise(promise) - this.connectionHandler.write(new PreparedStatementMessage(query, values)) + this.connectionHandler.sendPreparedStatement(query, values) promise.future } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 310f80ca..1d660dce 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -104,7 +104,34 @@ class BinaryRowEncoder( charset : Charset ) { private def encode(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any): Unit = { val encoder = encoderFor(value) parameterTypesBuffer.writeShort(encoder.encodesTo) - encoder.encode(value, parameterValuesBuffer) + if (!encoder.isLong(value)) + encoder.encode(value, parameterValuesBuffer) + } + + def isLong( maybeValue : Any ) : Boolean = { + if ( maybeValue == null || maybeValue == None ) { + false + } else { + val value = maybeValue match { + case Some(v) => v + case _ => maybeValue + } + val encoder = encoderFor(value) + encoder.isLong(value) + } + } + + def encodeLong( maybeValue: Any ) : ByteBuf = { + if ( maybeValue == null || maybeValue == None ) { + throw new UnsupportedOperationException("Cannot encode NULL as long value") + } else { + val value = maybeValue match { + case Some(v) => v + case _ => maybeValue + } + val encoder = encoderFor(value) + encoder.encodeLong(value) + } } private def encoderFor( v : Any ) : BinaryEncoder = { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala index bb504ce6..c4f87687 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala @@ -20,6 +20,12 @@ import io.netty.buffer.ByteBuf trait BinaryEncoder { + val LONG_THRESHOLD = 1023 + + def isLong( value : Any ) : Boolean = false + + def encodeLong( value : Any ) : ByteBuf = throw new UnsupportedOperationException() + def encode( value : Any, buffer : ByteBuf ) def encodesTo : Int diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala index 260f22a4..07d9d3e2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala @@ -17,11 +17,16 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import io.netty.buffer.ByteBuf +import io.netty.buffer.{Unpooled, ByteBuf} import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.mysql.column.ColumnTypes object ByteArrayEncoder extends BinaryEncoder { + + override def isLong(value: Any): Boolean = value.asInstanceOf[Array[Byte]].length > LONG_THRESHOLD + + override def encodeLong(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[Array[Byte]]) + def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[Array[Byte]] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala index 62b62560..4ba79072 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala @@ -5,6 +5,11 @@ import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import io.netty.buffer.ByteBuf object ByteBufEncoder extends BinaryEncoder { + + override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuf].readableBytes() > LONG_THRESHOLD + + override def encodeLong(value: Any): ByteBuf = value.asInstanceOf[ByteBuf] + def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[ByteBuf] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala index 329709ad..a562c84d 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala @@ -4,9 +4,14 @@ import java.nio.ByteBuffer import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import io.netty.buffer.ByteBuf +import io.netty.buffer.{Unpooled, ByteBuf} object ByteBufferEncoder extends BinaryEncoder { + + override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuffer].remaining() > LONG_THRESHOLD + + override def encodeLong(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[ByteBuffer]) + def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[ByteBuffer] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 27ff04da..4d7d3498 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -58,7 +58,7 @@ class MySQLConnectionHandler( private final val binaryRowDecoder = new BinaryRowDecoder() private var currentPreparedStatementHolder : PreparedStatementHolder = null - private var currentPreparedStatement : PreparedStatementMessage = null + private var currentPreparedStatement : PreparedStatement = null private var currentQuery : MutableResultSet[ColumnDefinitionMessage] = null private var currentContext: ChannelHandlerContext = null @@ -185,20 +185,21 @@ class MySQLConnectionHandler( writeAndHandleError(message) } - def write( message : PreparedStatementMessage ) { + def sendPreparedStatement( query: String, values: Seq[Any] ) { + val preparedStatement = new PreparedStatement(query, values) this.currentColumns.clear() this.currentParameters.clear() - this.currentPreparedStatement = message + this.currentPreparedStatement = preparedStatement - this.parsedStatements.get(message.statement) match { + this.parsedStatements.get(preparedStatement.statement) match { case Some( item ) => { - this.executePreparedStatement(item.statementId, item.columns.size, message.values, item.parameters) + this.executePreparedStatement(item.statementId, item.columns.size, preparedStatement.values, item.parameters) } case None => { decoder.preparedStatementPrepareStarted() - writeAndHandleError( new PreparedStatementPrepareMessage(message.statement) ) + writeAndHandleError( new PreparedStatementPrepareMessage(preparedStatement.statement) ) } } } @@ -234,6 +235,12 @@ class MySQLConnectionHandler( decoder.preparedStatementExecuteStarted(columnsCount, parameters.size) this.currentColumns.clear() this.currentParameters.clear() + + values.zipWithIndex.foreach { case (value, index) => + if (encoder.rowEncoder.isLong(value)) + writeAndHandleError(new SendLongDataMessage(statementId, value, index)) + } + writeAndHandleError(new PreparedStatementExecuteMessage( statementId, values, parameters )) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 074a8b6a..667b2e4e 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -36,10 +36,12 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten import MySQLOneToOneEncoder.log + final val rowEncoder = new BinaryRowEncoder(charset) + private final val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private final val queryEncoder = new QueryMessageEncoder(charset) - private final val rowEncoder = new BinaryRowEncoder(charset) private final val prepareEncoder = new PreparedStatementPrepareEncoder(charset) + private final val sendLongDataEncoder = new SendLongDataEncoder(rowEncoder) private final val executeEncoder = new PreparedStatementExecuteEncoder(rowEncoder) private final val authenticationSwitchEncoder = new AuthenticationSwitchResponseEncoder(charset) @@ -67,6 +69,10 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten sequence = 0 this.prepareEncoder } + case ClientMessage.PreparedStatementSendLongData => { + sequence = 0 + this.sendLongDataEncoder + } case ClientMessage.AuthSwitchResponse => { sequence += 1 this.authenticationSwitchEncoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/PreparedStatement.scala similarity index 76% rename from mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala rename to mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/PreparedStatement.scala index 0e52dad6..08fb0d9f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/PreparedStatement.scala @@ -14,7 +14,6 @@ * under the License. */ -package com.github.mauricio.async.db.mysql.message.client +package com.github.mauricio.async.db.mysql.codec -case class PreparedStatementMessage ( statement : String, values : Seq[Any]) - extends ClientMessage( ClientMessage.PreparedStatement ) \ No newline at end of file +case class PreparedStatement ( statement : String, values : Seq[Any]) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala new file mode 100644 index 00000000..b2bd2353 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala @@ -0,0 +1,21 @@ +package com.github.mauricio.async.db.mysql.encoder + +import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder +import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, SendLongDataMessage} +import com.github.mauricio.async.db.util.ByteBufferUtils +import io.netty.buffer.{Unpooled, ByteBuf} + +class SendLongDataEncoder( rowEncoder : BinaryRowEncoder ) extends MessageEncoder { + + def encode(message: ClientMessage): ByteBuf = { + val m = message.asInstanceOf[SendLongDataMessage] + + val buffer = ByteBufferUtils.packetBuffer() + buffer.writeByte(m.kind) + buffer.writeBytes(m.statementId) + buffer.writeShort(m.paramId) + + Unpooled.wrappedBuffer(buffer, rowEncoder.encodeLong(m.value)) + } + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala index 72d0be13..2a2a1b1f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/ClientMessage.scala @@ -20,13 +20,13 @@ import com.github.mauricio.async.db.KindedMessage object ClientMessage { - final val ClientProtocolVersion = 0x09 - final val Quit = 0x01 - final val Query = 0x03 - final val PreparedStatementPrepare = 0x16 - final val PreparedStatementExecute = 0x17 - final val PreparedStatement = 0x18 - final val AuthSwitchResponse = 0xfe + final val ClientProtocolVersion = 0x09 // COM_STATISTICS + final val Quit = 0x01 // COM_QUIT + final val Query = 0x03 // COM_QUERY + final val PreparedStatementPrepare = 0x16 // COM_STMT_PREPARE + final val PreparedStatementExecute = 0x17 // COM_STMT_EXECUTE + final val PreparedStatementSendLongData = 0x18 // COM_STMT_SEND_LONG_DATA + final val AuthSwitchResponse = 0xfe // AuthSwitchRequest } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala new file mode 100644 index 00000000..cf213614 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala @@ -0,0 +1,10 @@ +package com.github.mauricio.async.db.mysql.message.client + +case class SendLongDataMessage ( + statementId : Array[Byte], + value : Any, + paramId : Int ) + extends ClientMessage( ClientMessage.PreparedStatementSendLongData ) { + + override def toString = "SendLongDataMessage(statementId=" + statementId + ",paramId=" + paramId + ",value.getClass=" + value.getClass.getName +")" +} \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala index 5ff25f99..6c7c1313 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala @@ -100,32 +100,48 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { "support BLOB type" in { - val create = - """CREATE TEMPORARY TABLE POSTS ( - | id INT NOT NULL AUTO_INCREMENT, - | blob_column BLOB(20), - | primary key (id)) - """.stripMargin - - val insert = "INSERT INTO POSTS (blob_column) VALUES (?)" - val select = "SELECT * FROM POSTS" val bytes = (1 to 10).map(_.toByte).toArray - withConnection { - connection => - executeQuery(connection, create) - executePreparedStatement(connection, insert, bytes) - executePreparedStatement(connection, insert, ByteBuffer.wrap(bytes)) - executePreparedStatement(connection, insert, Unpooled.copiedBuffer(bytes)) - - val Some(rows) = executeQuery(connection, select).rows - rows foreach { - row => - row("blob_column") === bytes - } - rows.size === 3 - } + testBlob(bytes) + + } + + "support BLOB type with large values" in { + + val bytes = (1 to 2100).map(_.toByte).toArray + + testBlob(bytes) + + } + + } + def testBlob(bytes: Array[Byte]) = { + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL, + | blob_column BLOB, + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (id,blob_column) VALUES (?,?)" + val select = "SELECT id,blob_column FROM POSTS ORDER BY id" + + withConnection { + connection => + executeQuery(connection, create) + executePreparedStatement(connection, insert, 1, Some(bytes)) + executePreparedStatement(connection, insert, 2, ByteBuffer.wrap(bytes)) + executePreparedStatement(connection, insert, 3, Unpooled.wrappedBuffer(bytes)) + + val Some(rows) = executeQuery(connection, select).rows + rows(0)("id") === 1 + rows(0)("blob_column") === bytes + rows(1)("id") === 2 + rows(1)("blob_column") === bytes + rows(2)("id") === 3 + rows(2)("blob_column") === bytes + rows.size === 3 } } From 1527900db40ba6a79b022e37d0145bcc217e41de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Tue, 9 Dec 2014 11:23:18 +0100 Subject: [PATCH 277/357] Remove accidently committed file --- .../async/db/mysql/blob/LargeBlobSpec.scala | 99 ------------------- 1 file changed, 99 deletions(-) delete mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/blob/LargeBlobSpec.scala diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/blob/LargeBlobSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/blob/LargeBlobSpec.scala deleted file mode 100644 index c33d07db..00000000 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/blob/LargeBlobSpec.scala +++ /dev/null @@ -1,99 +0,0 @@ -package com.github.mauricio.async.db.mysql.blob - -import java.io.{BufferedOutputStream, File, FileInputStream, FileOutputStream} -import java.nio.ByteBuffer - -import com.github.mauricio.async.db.mysql.ConnectionHelper -import io.netty.buffer.Unpooled -import org.specs2.mutable.{After, Specification} -import org.specs2.specification.Scope - -class LargeBlobSpec extends Specification with ConnectionHelper { - - val create = """CREATE TEMPORARY TABLE t ( - | id BIGINT NOT NULL AUTO_INCREMENT, - | theblob LONGBLOB NOT NULL, - | PRIMARY KEY (id) - |);""".stripMargin - - val preparedInsert = "INSERT INTO t (theblob) VALUES (?)" - // val select = "SELECT theblob FROM t WHERE ID=?" - - "connection" should { - - "handle large BLOBs from InputStream" in new BlobFile { - - withConnection { - connection => - executeQuery(connection, create) - - val stream = new FileInputStream(blobFile) - executePreparedStatement(connection, preparedInsert, stream) - } - - } - - "handle BLOBs from ByteBuffer" in new BlobBuffer { - - val preparedInsert = "INSERT INTO t (theblob) VALUES (?)" - // val select = "SELECT theblob FROM t WHERE ID=?" - - withConnection { - connection => - executeQuery(connection, create) - - executePreparedStatement(connection, preparedInsert, blobBuffer) - } - - } - - "handle BLOBs from ByteBuf" in new BlobBuf { - - val preparedInsert = "INSERT INTO t (theblob) VALUES (?)" - // val select = "SELECT theblob FROM t WHERE ID=?" - - withConnection { - connection => - executeQuery(connection, create) - - executePreparedStatement(connection, preparedInsert, blobBuf) - } - - } - - } - -} - -trait BlobFile extends After { - lazy val blobFile = { - val file = File.createTempFile("blob1", null) - val bos = new BufferedOutputStream(new FileOutputStream(file)) - 0 to ((16 * 1024 * 1024)-1) foreach { n => bos.write(n & 128) } - bos.close() - file - } - - // lazy val outFile = File.createTempFile("blob2", null) - - def after = { - blobFile.delete() - // outFile.delete() - } -} - -trait BlobBuffer extends Scope { - lazy val blobBuffer = { - val array = new Array[Byte](1024) - 0 to (1024-1) foreach { n => array(n) = (n & 128).asInstanceOf[Byte] } - ByteBuffer.wrap(array) - } -} - -trait BlobBuf extends Scope { - lazy val blobBuf = { - val array = new Array[Byte](1024) - 0 to (1024-1) foreach { n => array(n) = (n & 128).asInstanceOf[Byte] } - Unpooled.copiedBuffer(array) - } -} \ No newline at end of file From 8657281c815d20829734851d45e5fd763097ad03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Tue, 9 Dec 2014 15:38:22 +0100 Subject: [PATCH 278/357] Constant in companion object --- .../async/db/mysql/binary/encoder/BinaryEncoder.scala | 6 +++++- .../async/db/mysql/binary/encoder/ByteArrayEncoder.scala | 2 +- .../async/db/mysql/binary/encoder/ByteBufEncoder.scala | 2 +- .../async/db/mysql/binary/encoder/ByteBufferEncoder.scala | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala index c4f87687..de774371 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala @@ -18,10 +18,14 @@ package com.github.mauricio.async.db.mysql.binary.encoder import io.netty.buffer.ByteBuf -trait BinaryEncoder { +object BinaryEncoder { val LONG_THRESHOLD = 1023 +} + +trait BinaryEncoder { + def isLong( value : Any ) : Boolean = false def encodeLong( value : Any ) : ByteBuf = throw new UnsupportedOperationException() diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala index 07d9d3e2..5d1693c3 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala @@ -23,7 +23,7 @@ import com.github.mauricio.async.db.mysql.column.ColumnTypes object ByteArrayEncoder extends BinaryEncoder { - override def isLong(value: Any): Boolean = value.asInstanceOf[Array[Byte]].length > LONG_THRESHOLD + override def isLong(value: Any): Boolean = value.asInstanceOf[Array[Byte]].length > BinaryEncoder.LONG_THRESHOLD override def encodeLong(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[Array[Byte]]) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala index 4ba79072..3b6b5d21 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala @@ -6,7 +6,7 @@ import io.netty.buffer.ByteBuf object ByteBufEncoder extends BinaryEncoder { - override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuf].readableBytes() > LONG_THRESHOLD + override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuf].readableBytes() > BinaryEncoder.LONG_THRESHOLD override def encodeLong(value: Any): ByteBuf = value.asInstanceOf[ByteBuf] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala index a562c84d..6a436539 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala @@ -8,7 +8,7 @@ import io.netty.buffer.{Unpooled, ByteBuf} object ByteBufferEncoder extends BinaryEncoder { - override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuffer].remaining() > LONG_THRESHOLD + override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuffer].remaining() > BinaryEncoder.LONG_THRESHOLD override def encodeLong(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[ByteBuffer]) From f4ae78f385556f2b4826c87f04c5b5f44459e353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Tue, 9 Dec 2014 16:46:38 +0100 Subject: [PATCH 279/357] Refactor binary encoding --- .../db/mysql/binary/BinaryRowEncoder.scala | 74 +------------------ .../mysql/codec/MySQLConnectionHandler.scala | 16 +++- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 5 +- .../PreparedStatementExecuteEncoder.scala | 44 ++++++++++- .../mysql/encoder/SendLongDataEncoder.scala | 15 +++- ...PreparedStatementExecuteEncoderSpec.scala} | 9 ++- 6 files changed, 80 insertions(+), 83 deletions(-) rename mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/{binary/BinaryRowEncoderSpec.scala => encoder/PreparedStatementExecuteEncoderSpec.scala} (80%) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 1d660dce..8792f08f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -16,15 +16,13 @@ package com.github.mauricio.async.db.mysql.binary -import io.netty.buffer.{Unpooled, ByteBuf} +import io.netty.buffer.ByteBuf import java.nio.ByteBuffer import java.nio.charset.Charset import com.github.mauricio.async.db.mysql.binary.encoder._ import com.github.mauricio.async.db.util._ import org.joda.time._ import scala.Some -import com.github.mauricio.async.db.mysql.column.ColumnTypes -import java.nio.ByteOrder object BinaryRowEncoder { final val log = Log.get[BinaryRowEncoder] @@ -66,75 +64,7 @@ class BinaryRowEncoder( charset : Charset ) { classOf[java.lang.Boolean] -> BooleanEncoder ) - def encode( values : Seq[Any] ) : ByteBuf = { - - val nullBitsCount = (values.size + 7) / 8 - val nullBits = new Array[Byte](nullBitsCount) - val bitMapBuffer = ByteBufferUtils.mysqlBuffer(1 + nullBitsCount) - val parameterTypesBuffer = ByteBufferUtils.mysqlBuffer(values.size * 2) - val parameterValuesBuffer = ByteBufferUtils.mysqlBuffer() - - - var index = 0 - - while ( index < values.length ) { - val value = values(index) - if ( value == null || value == None ) { - nullBits(index / 8) = (nullBits(index / 8) | (1 << (index & 7))).asInstanceOf[Byte] - parameterTypesBuffer.writeShort(ColumnTypes.FIELD_TYPE_NULL) - } else { - value match { - case Some(v) => encode(parameterTypesBuffer, parameterValuesBuffer, v) - case _ => encode(parameterTypesBuffer, parameterValuesBuffer, value) - } - } - index += 1 - } - - bitMapBuffer.writeBytes(nullBits) - if ( values.size > 0 ) { - bitMapBuffer.writeByte(1) - } else { - bitMapBuffer.writeByte(0) - } - - Unpooled.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) - } - - private def encode(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any): Unit = { - val encoder = encoderFor(value) - parameterTypesBuffer.writeShort(encoder.encodesTo) - if (!encoder.isLong(value)) - encoder.encode(value, parameterValuesBuffer) - } - - def isLong( maybeValue : Any ) : Boolean = { - if ( maybeValue == null || maybeValue == None ) { - false - } else { - val value = maybeValue match { - case Some(v) => v - case _ => maybeValue - } - val encoder = encoderFor(value) - encoder.isLong(value) - } - } - - def encodeLong( maybeValue: Any ) : ByteBuf = { - if ( maybeValue == null || maybeValue == None ) { - throw new UnsupportedOperationException("Cannot encode NULL as long value") - } else { - val value = maybeValue match { - case Some(v) => v - case _ => maybeValue - } - val encoder = encoderFor(value) - encoder.encodeLong(value) - } - } - - private def encoderFor( v : Any ) : BinaryEncoder = { + def encoderFor( v : Any ) : BinaryEncoder = { this.encoders.get(v.getClass) match { case Some(encoder) => encoder diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 4d7d3498..b5886891 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -237,13 +237,25 @@ class MySQLConnectionHandler( this.currentParameters.clear() values.zipWithIndex.foreach { case (value, index) => - if (encoder.rowEncoder.isLong(value)) - writeAndHandleError(new SendLongDataMessage(statementId, value, index)) + if (isLong(value)) + writeAndHandleError(new SendLongDataMessage( statementId, value, index )) } writeAndHandleError(new PreparedStatementExecuteMessage( statementId, values, parameters )) } + private def isLong( maybeValue : Any ) : Boolean = { + if ( maybeValue == null || maybeValue == None ) { + false + } else { + val value = maybeValue match { + case Some(v) => v + case _ => maybeValue + } + encoder.isLong(value) + } + } + private def onPreparedStatementPrepareResponse( message : PreparedStatementPrepareResponse ) { this.currentPreparedStatementHolder = new PreparedStatementHolder( this.currentPreparedStatement.statement, message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 667b2e4e..60254338 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -36,10 +36,9 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten import MySQLOneToOneEncoder.log - final val rowEncoder = new BinaryRowEncoder(charset) - private final val handshakeResponseEncoder = new HandshakeResponseEncoder(charset, charsetMapper) private final val queryEncoder = new QueryMessageEncoder(charset) + private final val rowEncoder = new BinaryRowEncoder(charset) private final val prepareEncoder = new PreparedStatementPrepareEncoder(charset) private final val sendLongDataEncoder = new SendLongDataEncoder(rowEncoder) private final val executeEncoder = new PreparedStatementExecuteEncoder(rowEncoder) @@ -47,6 +46,8 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten private var sequence = 1 + def isLong(value: Any): Boolean = rowEncoder.encoderFor(value).isLong(value) + def encode(ctx: ChannelHandlerContext, msg: Any, out: java.util.List[Object]): Unit = { msg match { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala index e21b15f6..6dbfce5c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.mysql.encoder import io.netty.buffer.{ByteBuf, Unpooled} +import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import com.github.mauricio.async.db.mysql.message.client.{PreparedStatementExecuteMessage, ClientMessage} import com.github.mauricio.async.db.util.ByteBufferUtils @@ -35,10 +36,49 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M if ( m.parameters.isEmpty ) { buffer } else { - val parametersBuffer = rowEncoder.encode(m.values) - Unpooled.wrappedBuffer(buffer, parametersBuffer) + Unpooled.wrappedBuffer(buffer, encode(m.values)) } } + private[encoder] def encode( values : Seq[Any] ) : ByteBuf = { + val nullBitsCount = (values.size + 7) / 8 + val nullBits = new Array[Byte](nullBitsCount) + val bitMapBuffer = ByteBufferUtils.mysqlBuffer(1 + nullBitsCount) + val parameterTypesBuffer = ByteBufferUtils.mysqlBuffer(values.size * 2) + val parameterValuesBuffer = ByteBufferUtils.mysqlBuffer() + + var index = 0 + + while ( index < values.length ) { + val value = values(index) + if ( value == null || value == None ) { + nullBits(index / 8) = (nullBits(index / 8) | (1 << (index & 7))).asInstanceOf[Byte] + parameterTypesBuffer.writeShort(ColumnTypes.FIELD_TYPE_NULL) + } else { + value match { + case Some(v) => encode(parameterTypesBuffer, parameterValuesBuffer, v) + case _ => encode(parameterTypesBuffer, parameterValuesBuffer, value) + } + } + index += 1 + } + + bitMapBuffer.writeBytes(nullBits) + if ( values.size > 0 ) { + bitMapBuffer.writeByte(1) + } else { + bitMapBuffer.writeByte(0) + } + + Unpooled.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) + } + + private def encode(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any): Unit = { + val encoder = rowEncoder.encoderFor(value) + parameterTypesBuffer.writeShort(encoder.encodesTo) + if (!encoder.isLong(value)) + encoder.encode(value, parameterValuesBuffer) + } + } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala index b2bd2353..bfce510f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala @@ -15,7 +15,20 @@ class SendLongDataEncoder( rowEncoder : BinaryRowEncoder ) extends MessageEncode buffer.writeBytes(m.statementId) buffer.writeShort(m.paramId) - Unpooled.wrappedBuffer(buffer, rowEncoder.encodeLong(m.value)) + Unpooled.wrappedBuffer(buffer, encodeLong(m.value)) + } + + private def encodeLong( maybeValue: Any ) : ByteBuf = { + if ( maybeValue == null || maybeValue == None ) { + throw new UnsupportedOperationException("Cannot encode NULL as long value") + } else { + val value = maybeValue match { + case Some(v) => v + case _ => maybeValue + } + val encoder = rowEncoder.encoderFor(value) + encoder.encodeLong(value) + } } } \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala similarity index 80% rename from mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala rename to mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala index 78bce249..dd0203b0 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala @@ -14,14 +14,15 @@ * under the License. */ -package com.github.mauricio.async.db.mysql.binary +package com.github.mauricio.async.db.mysql.encoder -import org.specs2.mutable.Specification +import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder import io.netty.util.CharsetUtil +import org.specs2.mutable.Specification -class BinaryRowEncoderSpec extends Specification { +class PreparedStatementExecuteEncoderSpec extends Specification { - val encoder = new BinaryRowEncoder(CharsetUtil.UTF_8) + val encoder = new PreparedStatementExecuteEncoder(new BinaryRowEncoder(CharsetUtil.UTF_8)) "binary row encoder" should { From 4ff4189374c255a750b1b58a5480c9b93bec6426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Tue, 9 Dec 2014 17:23:47 +0100 Subject: [PATCH 280/357] Avoid overloading confusion --- .../encoder/PreparedStatementExecuteEncoder.scala | 10 +++++----- .../async/db/mysql/encoder/SendLongDataEncoder.scala | 4 ++-- .../encoder/PreparedStatementExecuteEncoderSpec.scala | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala index 6dbfce5c..6bcc5a50 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala @@ -36,12 +36,12 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M if ( m.parameters.isEmpty ) { buffer } else { - Unpooled.wrappedBuffer(buffer, encode(m.values)) + Unpooled.wrappedBuffer(buffer, encodeValues(m.values)) } } - private[encoder] def encode( values : Seq[Any] ) : ByteBuf = { + private[encoder] def encodeValues( values : Seq[Any] ) : ByteBuf = { val nullBitsCount = (values.size + 7) / 8 val nullBits = new Array[Byte](nullBitsCount) val bitMapBuffer = ByteBufferUtils.mysqlBuffer(1 + nullBitsCount) @@ -57,8 +57,8 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M parameterTypesBuffer.writeShort(ColumnTypes.FIELD_TYPE_NULL) } else { value match { - case Some(v) => encode(parameterTypesBuffer, parameterValuesBuffer, v) - case _ => encode(parameterTypesBuffer, parameterValuesBuffer, value) + case Some(v) => encodeValue(parameterTypesBuffer, parameterValuesBuffer, v) + case _ => encodeValue(parameterTypesBuffer, parameterValuesBuffer, value) } } index += 1 @@ -74,7 +74,7 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M Unpooled.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) } - private def encode(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any): Unit = { + private def encodeValue(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any): Unit = { val encoder = rowEncoder.encoderFor(value) parameterTypesBuffer.writeShort(encoder.encodesTo) if (!encoder.isLong(value)) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala index bfce510f..5cbacbca 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala @@ -15,10 +15,10 @@ class SendLongDataEncoder( rowEncoder : BinaryRowEncoder ) extends MessageEncode buffer.writeBytes(m.statementId) buffer.writeShort(m.paramId) - Unpooled.wrappedBuffer(buffer, encodeLong(m.value)) + Unpooled.wrappedBuffer(buffer, encodeValue(m.value)) } - private def encodeLong( maybeValue: Any ) : ByteBuf = { + private def encodeValue( maybeValue: Any ) : ByteBuf = { if ( maybeValue == null || maybeValue == None ) { throw new UnsupportedOperationException("Cannot encode NULL as long value") } else { diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala index dd0203b0..d62b929e 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala @@ -27,16 +27,16 @@ class PreparedStatementExecuteEncoderSpec extends Specification { "binary row encoder" should { "encode Some(value) like value" in { - val actual = encoder.encode(List(Some(1l), Some("foo"))) - val expected = encoder.encode(List(1l, "foo")) + val actual = encoder.encodeValues(List(Some(1l), Some("foo"))) + val expected = encoder.encodeValues(List(1l, "foo")) actual mustEqual expected } "encode None as null" in { - val actual = encoder.encode(List(None)) - val expected = encoder.encode(List(null)) + val actual = encoder.encodeValues(List(None)) + val expected = encoder.encodeValues(List(null)) actual mustEqual expected } From b96b48f2b97ddb03bd43059e6e9730c4f756c458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Tue, 9 Dec 2014 18:52:39 +0100 Subject: [PATCH 281/357] Separate protocol encoder for MySQL SendLongData --- .../mysql/codec/MySQLConnectionHandler.scala | 4 +- .../db/mysql/codec/MySQLOneToOneEncoder.scala | 78 ++++++++----------- .../db/mysql/codec/SendLongDataEncoder.scala | 59 ++++++++++++++ .../mysql/encoder/SendLongDataEncoder.scala | 34 -------- .../message/client/SendLongDataMessage.scala | 4 +- 5 files changed, 96 insertions(+), 83 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index b5886891..7e599681 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -52,6 +52,7 @@ class MySQLConnectionHandler( private final val connectionPromise = Promise[MySQLConnectionHandler] private final val decoder = new MySQLFrameDecoder(configuration.charset, connectionId) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) + private final val sendLongDataEncoder = new SendLongDataEncoder(configuration.charset) private final val currentParameters = new ArrayBuffer[ColumnDefinitionMessage]() private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() private final val parsedStatements = new HashMap[String,PreparedStatementHolder]() @@ -70,6 +71,7 @@ class MySQLConnectionHandler( channel.pipeline.addLast( decoder, encoder, + sendLongDataEncoder, MySQLConnectionHandler.this) } @@ -252,7 +254,7 @@ class MySQLConnectionHandler( case Some(v) => v case _ => maybeValue } - encoder.isLong(value) + sendLongDataEncoder.isLong(value) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala index 60254338..f666cbc8 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLOneToOneEncoder.scala @@ -32,7 +32,8 @@ object MySQLOneToOneEncoder { val log = Log.get[MySQLOneToOneEncoder] } -class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) extends MessageToMessageEncoder[Any] { +class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) + extends MessageToMessageEncoder[ClientMessage](classOf[ClientMessage]) { import MySQLOneToOneEncoder.log @@ -40,61 +41,48 @@ class MySQLOneToOneEncoder(charset: Charset, charsetMapper: CharsetMapper) exten private final val queryEncoder = new QueryMessageEncoder(charset) private final val rowEncoder = new BinaryRowEncoder(charset) private final val prepareEncoder = new PreparedStatementPrepareEncoder(charset) - private final val sendLongDataEncoder = new SendLongDataEncoder(rowEncoder) private final val executeEncoder = new PreparedStatementExecuteEncoder(rowEncoder) private final val authenticationSwitchEncoder = new AuthenticationSwitchResponseEncoder(charset) private var sequence = 1 - def isLong(value: Any): Boolean = rowEncoder.encoderFor(value).isLong(value) - - def encode(ctx: ChannelHandlerContext, msg: Any, out: java.util.List[Object]): Unit = { - - msg match { - case message: ClientMessage => { - val encoder = (message.kind: @switch) match { - case ClientMessage.ClientProtocolVersion => this.handshakeResponseEncoder - case ClientMessage.Quit => { - sequence = 0 - QuitMessageEncoder - } - case ClientMessage.Query => { - sequence = 0 - this.queryEncoder - } - case ClientMessage.PreparedStatementExecute => { - sequence = 0 - this.executeEncoder - } - case ClientMessage.PreparedStatementPrepare => { - sequence = 0 - this.prepareEncoder - } - case ClientMessage.PreparedStatementSendLongData => { - sequence = 0 - this.sendLongDataEncoder - } - case ClientMessage.AuthSwitchResponse => { - sequence += 1 - this.authenticationSwitchEncoder - } - case _ => throw new EncoderNotAvailableException(message) - } - - val result = encoder.encode(message) + def encode(ctx: ChannelHandlerContext, message: ClientMessage, out: java.util.List[Object]): Unit = { + val encoder = (message.kind: @switch) match { + case ClientMessage.ClientProtocolVersion => this.handshakeResponseEncoder + case ClientMessage.Quit => { + sequence = 0 + QuitMessageEncoder + } + case ClientMessage.Query => { + sequence = 0 + this.queryEncoder + } + case ClientMessage.PreparedStatementExecute => { + sequence = 0 + this.executeEncoder + } + case ClientMessage.PreparedStatementPrepare => { + sequence = 0 + this.prepareEncoder + } + case ClientMessage.AuthSwitchResponse => { + sequence += 1 + this.authenticationSwitchEncoder + } + case _ => throw new EncoderNotAvailableException(message) + } - ByteBufferUtils.writePacketLength(result, sequence) + val result: ByteBuf = encoder.encode(message) - sequence += 1 + ByteBufferUtils.writePacketLength(result, sequence) - if ( log.isTraceEnabled ) { - log.trace(s"Writing message ${message.getClass.getName} - \n${BufferDumper.dumpAsHex(result)}") - } + sequence += 1 - out.add(result) - } + if ( log.isTraceEnabled ) { + log.trace(s"Writing message ${message.getClass.getName} - \n${BufferDumper.dumpAsHex(result)}") } + out.add(result) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala new file mode 100644 index 00000000..b1c836cc --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala @@ -0,0 +1,59 @@ +package com.github.mauricio.async.db.mysql.codec + +import java.nio.charset.Charset + +import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder +import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, SendLongDataMessage} +import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} +import io.netty.buffer.{Unpooled, ByteBuf} +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.MessageToMessageEncoder + +object SendLongDataEncoder { + val log = Log.get[SendLongDataEncoder] +} + +class SendLongDataEncoder(charset: Charset) + extends MessageToMessageEncoder[SendLongDataMessage](classOf[SendLongDataMessage]) { + + import com.github.mauricio.async.db.mysql.codec.SendLongDataEncoder.log + + private final val rowEncoder = new BinaryRowEncoder(charset) + + def isLong(value: Any): Boolean = rowEncoder.encoderFor(value).isLong(value) + + def encode(ctx: ChannelHandlerContext, message: SendLongDataMessage, out: java.util.List[Object]): Unit = { + val result: ByteBuf = encode(message) + + ByteBufferUtils.writePacketLength(result, 0) + + if ( log.isTraceEnabled ) { + log.trace(s"Writing message ${message.toString}") + } + + out.add(result) + } + + private def encode(message: SendLongDataMessage): ByteBuf = { + val buffer = ByteBufferUtils.packetBuffer() + buffer.writeByte(ClientMessage.PreparedStatementSendLongData) + buffer.writeBytes(message.statementId) + buffer.writeShort(message.paramId) + + Unpooled.wrappedBuffer(buffer, encodeValue(message.value)) + } + + private def encodeValue(maybeValue: Any) : ByteBuf = { + if ( maybeValue == null || maybeValue == None ) { + throw new UnsupportedOperationException("Cannot encode NULL as long value") + } else { + val value = maybeValue match { + case Some(v) => v + case _ => maybeValue + } + val encoder = rowEncoder.encoderFor(value) + encoder.encodeLong(value) + } + } + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala deleted file mode 100644 index 5cbacbca..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/SendLongDataEncoder.scala +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.mauricio.async.db.mysql.encoder - -import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder -import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, SendLongDataMessage} -import com.github.mauricio.async.db.util.ByteBufferUtils -import io.netty.buffer.{Unpooled, ByteBuf} - -class SendLongDataEncoder( rowEncoder : BinaryRowEncoder ) extends MessageEncoder { - - def encode(message: ClientMessage): ByteBuf = { - val m = message.asInstanceOf[SendLongDataMessage] - - val buffer = ByteBufferUtils.packetBuffer() - buffer.writeByte(m.kind) - buffer.writeBytes(m.statementId) - buffer.writeShort(m.paramId) - - Unpooled.wrappedBuffer(buffer, encodeValue(m.value)) - } - - private def encodeValue( maybeValue: Any ) : ByteBuf = { - if ( maybeValue == null || maybeValue == None ) { - throw new UnsupportedOperationException("Cannot encode NULL as long value") - } else { - val value = maybeValue match { - case Some(v) => v - case _ => maybeValue - } - val encoder = rowEncoder.encoderFor(value) - encoder.encodeLong(value) - } - } - -} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala index cf213614..af09980a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala @@ -3,8 +3,6 @@ package com.github.mauricio.async.db.mysql.message.client case class SendLongDataMessage ( statementId : Array[Byte], value : Any, - paramId : Int ) - extends ClientMessage( ClientMessage.PreparedStatementSendLongData ) { - + paramId : Int ) { override def toString = "SendLongDataMessage(statementId=" + statementId + ",paramId=" + paramId + ",value.getClass=" + value.getClass.getName +")" } \ No newline at end of file From c2cffc55aa108412f45aa36f5518d4d7dfffb905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 10 Dec 2014 10:30:54 +0100 Subject: [PATCH 282/357] Remove unnecessary null / None check --- .../async/db/mysql/codec/SendLongDataEncoder.scala | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala index b1c836cc..61d5d595 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala @@ -44,16 +44,12 @@ class SendLongDataEncoder(charset: Charset) } private def encodeValue(maybeValue: Any) : ByteBuf = { - if ( maybeValue == null || maybeValue == None ) { - throw new UnsupportedOperationException("Cannot encode NULL as long value") - } else { - val value = maybeValue match { - case Some(v) => v - case _ => maybeValue - } - val encoder = rowEncoder.encoderFor(value) - encoder.encodeLong(value) + val value = maybeValue match { + case Some(v) => v + case _ => maybeValue } + val encoder = rowEncoder.encoderFor(value) + encoder.encodeLong(value) } } From 58532a263b6333452d7b66b6695eb9ce031a245b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 10 Dec 2014 11:41:02 +0100 Subject: [PATCH 283/357] Blob encoder --- .../mysql/binary/encoder/BinaryEncoder.scala | 10 ------ .../binary/encoder/ByteArrayEncoder.scala | 8 ++--- .../mysql/binary/encoder/ByteBufEncoder.scala | 4 --- .../binary/encoder/ByteBufferEncoder.scala | 6 +--- .../db/mysql/blob/encoder/BlobEncoder.scala | 31 +++++++++++++++++++ .../mysql/blob/encoder/ByteArrayEncoder.scala | 11 +++++++ .../mysql/blob/encoder/ByteBufEncoder.scala | 11 +++++++ .../blob/encoder/ByteBufferEncoder.scala | 13 ++++++++ .../mysql/codec/MySQLConnectionHandler.scala | 7 +++-- .../db/mysql/codec/SendLongDataEncoder.scala | 13 +++----- .../PreparedStatementExecuteEncoder.scala | 12 +++---- .../PreparedStatementExecuteMessage.scala | 1 + .../PreparedStatementExecuteEncoderSpec.scala | 8 ++--- 13 files changed, 89 insertions(+), 46 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala index de774371..bb504ce6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/BinaryEncoder.scala @@ -18,18 +18,8 @@ package com.github.mauricio.async.db.mysql.binary.encoder import io.netty.buffer.ByteBuf -object BinaryEncoder { - - val LONG_THRESHOLD = 1023 - -} - trait BinaryEncoder { - def isLong( value : Any ) : Boolean = false - - def encodeLong( value : Any ) : ByteBuf = throw new UnsupportedOperationException() - def encode( value : Any, buffer : ByteBuf ) def encodesTo : Int diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala index 5d1693c3..3a6c7ce6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala @@ -17,16 +17,12 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import io.netty.buffer.{Unpooled, ByteBuf} -import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import com.github.mauricio.async.db.mysql.column.ColumnTypes +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import io.netty.buffer.ByteBuf object ByteArrayEncoder extends BinaryEncoder { - override def isLong(value: Any): Boolean = value.asInstanceOf[Array[Byte]].length > BinaryEncoder.LONG_THRESHOLD - - override def encodeLong(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[Array[Byte]]) - def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[Array[Byte]] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala index 3b6b5d21..e7156943 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala @@ -6,10 +6,6 @@ import io.netty.buffer.ByteBuf object ByteBufEncoder extends BinaryEncoder { - override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuf].readableBytes() > BinaryEncoder.LONG_THRESHOLD - - override def encodeLong(value: Any): ByteBuf = value.asInstanceOf[ByteBuf] - def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[ByteBuf] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala index 6a436539..06012f6b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala @@ -4,14 +4,10 @@ import java.nio.ByteBuffer import com.github.mauricio.async.db.mysql.column.ColumnTypes import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper -import io.netty.buffer.{Unpooled, ByteBuf} +import io.netty.buffer.ByteBuf object ByteBufferEncoder extends BinaryEncoder { - override def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuffer].remaining() > BinaryEncoder.LONG_THRESHOLD - - override def encodeLong(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[ByteBuffer]) - def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[ByteBuffer] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala new file mode 100644 index 00000000..2475c0e9 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala @@ -0,0 +1,31 @@ +package com.github.mauricio.async.db.mysql.blob.encoder + +import java.nio.ByteBuffer + +import io.netty.buffer.ByteBuf + +object BlobEncoder { + + val LONG_THRESHOLD = 1023 + + def encoderFor( v : Any ) : Option[BlobEncoder] = { + + v match { + case v : Array[Byte] => Some(ByteArrayEncoder) + case v : ByteBuffer => Some(ByteBufferEncoder) + case v : ByteBuf => Some(ByteBufEncoder) + + case _ => None + } + + } + +} + +trait BlobEncoder { + + def isLong(value: Any): Boolean + + def encode(value: Any): ByteBuf + +} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala new file mode 100644 index 00000000..c20b8c35 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala @@ -0,0 +1,11 @@ +package com.github.mauricio.async.db.mysql.blob.encoder + +import io.netty.buffer.{ByteBuf, Unpooled} + +object ByteArrayEncoder extends BlobEncoder { + + def isLong(value: Any): Boolean = value.asInstanceOf[Array[Byte]].length > BlobEncoder.LONG_THRESHOLD + + def encode(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[Array[Byte]]) + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala new file mode 100644 index 00000000..086c93e2 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala @@ -0,0 +1,11 @@ +package com.github.mauricio.async.db.mysql.blob.encoder + +import io.netty.buffer.ByteBuf + +object ByteBufEncoder extends BlobEncoder { + + def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuf].readableBytes() > BlobEncoder.LONG_THRESHOLD + + def encode(value: Any): ByteBuf = value.asInstanceOf[ByteBuf] + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala new file mode 100644 index 00000000..566104b8 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala @@ -0,0 +1,13 @@ +package com.github.mauricio.async.db.mysql.blob.encoder + +import java.nio.ByteBuffer + +import io.netty.buffer.{ByteBuf, Unpooled} + +object ByteBufferEncoder extends BlobEncoder { + + def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuffer].remaining() > BlobEncoder.LONG_THRESHOLD + + def encode(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[ByteBuffer]) + +} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 7e599681..f25e326c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -52,7 +52,7 @@ class MySQLConnectionHandler( private final val connectionPromise = Promise[MySQLConnectionHandler] private final val decoder = new MySQLFrameDecoder(configuration.charset, connectionId) private final val encoder = new MySQLOneToOneEncoder(configuration.charset, charsetMapper) - private final val sendLongDataEncoder = new SendLongDataEncoder(configuration.charset) + private final val sendLongDataEncoder = new SendLongDataEncoder() private final val currentParameters = new ArrayBuffer[ColumnDefinitionMessage]() private final val currentColumns = new ArrayBuffer[ColumnDefinitionMessage]() private final val parsedStatements = new HashMap[String,PreparedStatementHolder]() @@ -238,12 +238,15 @@ class MySQLConnectionHandler( this.currentColumns.clear() this.currentParameters.clear() + var nonBlobIndices: Set[Int] = Set() values.zipWithIndex.foreach { case (value, index) => if (isLong(value)) writeAndHandleError(new SendLongDataMessage( statementId, value, index )) + else + nonBlobIndices += index } - writeAndHandleError(new PreparedStatementExecuteMessage( statementId, values, parameters )) + writeAndHandleError(new PreparedStatementExecuteMessage( statementId, values, nonBlobIndices, parameters)) } private def isLong( maybeValue : Any ) : Boolean = { diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala index 61d5d595..294917fe 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala @@ -1,8 +1,6 @@ package com.github.mauricio.async.db.mysql.codec -import java.nio.charset.Charset - -import com.github.mauricio.async.db.mysql.binary.BinaryRowEncoder +import com.github.mauricio.async.db.mysql.blob.encoder.BlobEncoder import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, SendLongDataMessage} import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} import io.netty.buffer.{Unpooled, ByteBuf} @@ -13,14 +11,12 @@ object SendLongDataEncoder { val log = Log.get[SendLongDataEncoder] } -class SendLongDataEncoder(charset: Charset) +class SendLongDataEncoder extends MessageToMessageEncoder[SendLongDataMessage](classOf[SendLongDataMessage]) { import com.github.mauricio.async.db.mysql.codec.SendLongDataEncoder.log - private final val rowEncoder = new BinaryRowEncoder(charset) - - def isLong(value: Any): Boolean = rowEncoder.encoderFor(value).isLong(value) + def isLong(value: Any): Boolean = BlobEncoder.encoderFor(value).map(_.isLong(value)).getOrElse(false) def encode(ctx: ChannelHandlerContext, message: SendLongDataMessage, out: java.util.List[Object]): Unit = { val result: ByteBuf = encode(message) @@ -48,8 +44,7 @@ class SendLongDataEncoder(charset: Charset) case Some(v) => v case _ => maybeValue } - val encoder = rowEncoder.encoderFor(value) - encoder.encodeLong(value) + BlobEncoder.encoderFor(value).get.encode(value) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala index 6bcc5a50..c52658c9 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoder.scala @@ -36,12 +36,12 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M if ( m.parameters.isEmpty ) { buffer } else { - Unpooled.wrappedBuffer(buffer, encodeValues(m.values)) + Unpooled.wrappedBuffer(buffer, encodeValues(m.values, m.valuesToInclude)) } } - private[encoder] def encodeValues( values : Seq[Any] ) : ByteBuf = { + private[encoder] def encodeValues( values : Seq[Any], valuesToInclude: Set[Int] ) : ByteBuf = { val nullBitsCount = (values.size + 7) / 8 val nullBits = new Array[Byte](nullBitsCount) val bitMapBuffer = ByteBufferUtils.mysqlBuffer(1 + nullBitsCount) @@ -57,8 +57,8 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M parameterTypesBuffer.writeShort(ColumnTypes.FIELD_TYPE_NULL) } else { value match { - case Some(v) => encodeValue(parameterTypesBuffer, parameterValuesBuffer, v) - case _ => encodeValue(parameterTypesBuffer, parameterValuesBuffer, value) + case Some(v) => encodeValue(parameterTypesBuffer, parameterValuesBuffer, v, valuesToInclude(index)) + case _ => encodeValue(parameterTypesBuffer, parameterValuesBuffer, value, valuesToInclude(index)) } } index += 1 @@ -74,10 +74,10 @@ class PreparedStatementExecuteEncoder( rowEncoder : BinaryRowEncoder ) extends M Unpooled.wrappedBuffer( bitMapBuffer, parameterTypesBuffer, parameterValuesBuffer ) } - private def encodeValue(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any): Unit = { + private def encodeValue(parameterTypesBuffer: ByteBuf, parameterValuesBuffer: ByteBuf, value: Any, includeValue: Boolean) : Unit = { val encoder = rowEncoder.encoderFor(value) parameterTypesBuffer.writeShort(encoder.encodesTo) - if (!encoder.isLong(value)) + if (includeValue) encoder.encode(value, parameterValuesBuffer) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala index 805ef51e..f87ddede 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/PreparedStatementExecuteMessage.scala @@ -21,5 +21,6 @@ import com.github.mauricio.async.db.mysql.message.server.ColumnDefinitionMessage case class PreparedStatementExecuteMessage ( statementId : Array[Byte], values : Seq[Any], + valuesToInclude : Set[Int], parameters : Seq[ColumnDefinitionMessage] ) extends ClientMessage( ClientMessage.PreparedStatementExecute ) \ No newline at end of file diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala index d62b929e..427dde17 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/encoder/PreparedStatementExecuteEncoderSpec.scala @@ -27,16 +27,16 @@ class PreparedStatementExecuteEncoderSpec extends Specification { "binary row encoder" should { "encode Some(value) like value" in { - val actual = encoder.encodeValues(List(Some(1l), Some("foo"))) - val expected = encoder.encodeValues(List(1l, "foo")) + val actual = encoder.encodeValues(List(Some(1l), Some("foo")), Set(0, 1)) + val expected = encoder.encodeValues(List(1l, "foo"), Set(0, 1)) actual mustEqual expected } "encode None as null" in { - val actual = encoder.encodeValues(List(None)) - val expected = encoder.encodeValues(List(null)) + val actual = encoder.encodeValues(List(None), Set(0)) + val expected = encoder.encodeValues(List(null), Set(0)) actual mustEqual expected } From a737d934cecfb00c8567d4aaf78e8b17132d5d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 10 Dec 2014 12:00:09 +0100 Subject: [PATCH 284/357] Revert insignificant changes --- .../async/db/mysql/binary/encoder/ByteArrayEncoder.scala | 5 ++--- .../async/db/mysql/binary/encoder/ByteBufEncoder.scala | 1 - .../async/db/mysql/binary/encoder/ByteBufferEncoder.scala | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala index 3a6c7ce6..260f22a4 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteArrayEncoder.scala @@ -17,12 +17,11 @@ package com.github.mauricio.async.db.mysql.binary.encoder -import com.github.mauricio.async.db.mysql.column.ColumnTypes -import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import io.netty.buffer.ByteBuf +import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper +import com.github.mauricio.async.db.mysql.column.ColumnTypes object ByteArrayEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[Array[Byte]] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala index e7156943..62b62560 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufEncoder.scala @@ -5,7 +5,6 @@ import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import io.netty.buffer.ByteBuf object ByteBufEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[ByteBuf] diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala index 06012f6b..329709ad 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/ByteBufferEncoder.scala @@ -7,7 +7,6 @@ import com.github.mauricio.async.db.util.ChannelWrapper.bufferToWrapper import io.netty.buffer.ByteBuf object ByteBufferEncoder extends BinaryEncoder { - def encode(value: Any, buffer: ByteBuf) { val bytes = value.asInstanceOf[ByteBuffer] From 5f31084ca64f56d2c1af46de73ecdf1270e91298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 10 Dec 2014 13:40:54 +0100 Subject: [PATCH 285/357] Document max size of a BLOB for MySQL --- mysql-async/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mysql-async/README.md b/mysql-async/README.md index adff299e..b83613ed 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -92,5 +92,7 @@ Array[Byte] | blob java.nio.ByteBuffer | blob io.netty.buffer.ByteBuf | blob +The maximum size of a blob is 2^24-8 bytes (almost 16 MiB). + You don't have to match exact values when sending parameters for your prepared statements, MySQL is usually smart enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. From 5337e8a9fd02e27fe5e1643eb8190b56e6623f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 10 Dec 2014 14:06:12 +0100 Subject: [PATCH 286/357] Use correct size --- mysql-async/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-async/README.md b/mysql-async/README.md index b83613ed..3a152286 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -92,7 +92,7 @@ Array[Byte] | blob java.nio.ByteBuffer | blob io.netty.buffer.ByteBuf | blob -The maximum size of a blob is 2^24-8 bytes (almost 16 MiB). +The maximum size of a blob is 2^24-9 bytes (almost 16 MiB). You don't have to match exact values when sending parameters for your prepared statements, MySQL is usually smart enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. From 6c715874de371c3695e672cf06d5c9a277ac0261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 10 Dec 2014 14:56:40 +0100 Subject: [PATCH 287/357] Remove unnecessary toString --- .../async/db/mysql/message/client/SendLongDataMessage.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala index af09980a..6b1ef7e2 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala @@ -3,6 +3,4 @@ package com.github.mauricio.async.db.mysql.message.client case class SendLongDataMessage ( statementId : Array[Byte], value : Any, - paramId : Int ) { - override def toString = "SendLongDataMessage(statementId=" + statementId + ",paramId=" + paramId + ",value.getClass=" + value.getClass.getName +")" -} \ No newline at end of file + paramId : Int ) \ No newline at end of file From 2908af5453cc9f437bca5fe2df345d1380532c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 10 Dec 2014 15:20:05 +0100 Subject: [PATCH 288/357] Refactor sending long data --- .../db/mysql/blob/encoder/BlobEncoder.scala | 31 ----------- .../mysql/blob/encoder/ByteArrayEncoder.scala | 11 ---- .../mysql/blob/encoder/ByteBufEncoder.scala | 11 ---- .../blob/encoder/ByteBufferEncoder.scala | 13 ----- .../mysql/codec/MySQLConnectionHandler.scala | 55 +++++++++++++------ .../db/mysql/codec/SendLongDataEncoder.scala | 38 +++++-------- .../message/client/SendLongDataMessage.scala | 4 +- 7 files changed, 55 insertions(+), 108 deletions(-) delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala deleted file mode 100644 index 2475c0e9..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/BlobEncoder.scala +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.mauricio.async.db.mysql.blob.encoder - -import java.nio.ByteBuffer - -import io.netty.buffer.ByteBuf - -object BlobEncoder { - - val LONG_THRESHOLD = 1023 - - def encoderFor( v : Any ) : Option[BlobEncoder] = { - - v match { - case v : Array[Byte] => Some(ByteArrayEncoder) - case v : ByteBuffer => Some(ByteBufferEncoder) - case v : ByteBuf => Some(ByteBufEncoder) - - case _ => None - } - - } - -} - -trait BlobEncoder { - - def isLong(value: Any): Boolean - - def encode(value: Any): ByteBuf - -} \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala deleted file mode 100644 index c20b8c35..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteArrayEncoder.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mauricio.async.db.mysql.blob.encoder - -import io.netty.buffer.{ByteBuf, Unpooled} - -object ByteArrayEncoder extends BlobEncoder { - - def isLong(value: Any): Boolean = value.asInstanceOf[Array[Byte]].length > BlobEncoder.LONG_THRESHOLD - - def encode(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[Array[Byte]]) - -} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala deleted file mode 100644 index 086c93e2..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufEncoder.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mauricio.async.db.mysql.blob.encoder - -import io.netty.buffer.ByteBuf - -object ByteBufEncoder extends BlobEncoder { - - def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuf].readableBytes() > BlobEncoder.LONG_THRESHOLD - - def encode(value: Any): ByteBuf = value.asInstanceOf[ByteBuf] - -} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala deleted file mode 100644 index 566104b8..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/blob/encoder/ByteBufferEncoder.scala +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.mauricio.async.db.mysql.blob.encoder - -import java.nio.ByteBuffer - -import io.netty.buffer.{ByteBuf, Unpooled} - -object ByteBufferEncoder extends BlobEncoder { - - def isLong(value: Any): Boolean = value.asInstanceOf[ByteBuffer].remaining() > BlobEncoder.LONG_THRESHOLD - - def encode(value: Any): ByteBuf = Unpooled.wrappedBuffer(value.asInstanceOf[ByteBuffer]) - -} diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index f25e326c..aec8d24c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -16,6 +16,8 @@ package com.github.mauricio.async.db.mysql.codec +import java.nio.ByteBuffer + import com.github.mauricio.async.db.Configuration import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.mysql.binary.BinaryRowDecoder @@ -25,7 +27,7 @@ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import io.netty.bootstrap.Bootstrap -import io.netty.buffer.ByteBufAllocator +import io.netty.buffer.{Unpooled, ByteBuf, ByteBufAllocator} import io.netty.channel._ import io.netty.channel.socket.nio.NioSocketChannel import io.netty.handler.codec.CodecException @@ -238,29 +240,48 @@ class MySQLConnectionHandler( this.currentColumns.clear() this.currentParameters.clear() - var nonBlobIndices: Set[Int] = Set() - values.zipWithIndex.foreach { case (value, index) => - if (isLong(value)) - writeAndHandleError(new SendLongDataMessage( statementId, value, index )) - else - nonBlobIndices += index + var nonLongIndices: Set[Int] = Set() + values.zipWithIndex.foreach { + case (Some(value), index) if isLong(value) => + sendLongParameter(statementId, index, value) + + case (value, index) if isLong(value) => + sendLongParameter(statementId, index, value) + + case (_, index) => + nonLongIndices += index } - writeAndHandleError(new PreparedStatementExecuteMessage( statementId, values, nonBlobIndices, parameters)) + writeAndHandleError(new PreparedStatementExecuteMessage(statementId, values, nonLongIndices, parameters)) } - private def isLong( maybeValue : Any ) : Boolean = { - if ( maybeValue == null || maybeValue == None ) { - false - } else { - val value = maybeValue match { - case Some(v) => v - case _ => maybeValue - } - sendLongDataEncoder.isLong(value) + private def isLong(value: Any): Boolean = { + value match { + case v : Array[Byte] => v.length > SendLongDataEncoder.LONG_THRESHOLD + case v : ByteBuffer => v.remaining() > SendLongDataEncoder.LONG_THRESHOLD + case v : ByteBuf => v.readableBytes() > SendLongDataEncoder.LONG_THRESHOLD + + case _ => false } } + private def sendLongParameter(statementId: Array[Byte], index: Int, longValue: Any) { + longValue match { + case v : Array[Byte] => + sendBuffer(Unpooled.wrappedBuffer(v), statementId, index) + + case v : ByteBuffer => + sendBuffer(Unpooled.wrappedBuffer(v), statementId, index) + + case v : ByteBuf => + sendBuffer(v, statementId, index) + } + } + + private def sendBuffer(buffer: ByteBuf, statementId: Array[Byte], paramId: Int) { + writeAndHandleError(new SendLongDataMessage(statementId, buffer, paramId)) + } + private def onPreparedStatementPrepareResponse( message : PreparedStatementPrepareResponse ) { this.currentPreparedStatementHolder = new PreparedStatementHolder( this.currentPreparedStatement.statement, message) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala index 294917fe..ce51140f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala @@ -1,14 +1,15 @@ package com.github.mauricio.async.db.mysql.codec -import com.github.mauricio.async.db.mysql.blob.encoder.BlobEncoder import com.github.mauricio.async.db.mysql.message.client.{ClientMessage, SendLongDataMessage} -import com.github.mauricio.async.db.util.{Log, ByteBufferUtils} -import io.netty.buffer.{Unpooled, ByteBuf} +import com.github.mauricio.async.db.util.{ByteBufferUtils, Log} +import io.netty.buffer.Unpooled import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.MessageToMessageEncoder object SendLongDataEncoder { val log = Log.get[SendLongDataEncoder] + + val LONG_THRESHOLD = 1023 } class SendLongDataEncoder @@ -16,35 +17,24 @@ class SendLongDataEncoder import com.github.mauricio.async.db.mysql.codec.SendLongDataEncoder.log - def isLong(value: Any): Boolean = BlobEncoder.encoderFor(value).map(_.isLong(value)).getOrElse(false) - def encode(ctx: ChannelHandlerContext, message: SendLongDataMessage, out: java.util.List[Object]): Unit = { - val result: ByteBuf = encode(message) - - ByteBufferUtils.writePacketLength(result, 0) - if ( log.isTraceEnabled ) { log.trace(s"Writing message ${message.toString}") } - out.add(result) - } + val sequence = 0 - private def encode(message: SendLongDataMessage): ByteBuf = { - val buffer = ByteBufferUtils.packetBuffer() - buffer.writeByte(ClientMessage.PreparedStatementSendLongData) - buffer.writeBytes(message.statementId) - buffer.writeShort(message.paramId) + val headerBuffer = ByteBufferUtils.mysqlBuffer(3 + 1 + 1 + 4 + 2) + ByteBufferUtils.write3BytesInt(headerBuffer, 1 + 4 + 2 + message.value.readableBytes()) + headerBuffer.writeByte(sequence) - Unpooled.wrappedBuffer(buffer, encodeValue(message.value)) - } + headerBuffer.writeByte(ClientMessage.PreparedStatementSendLongData) + headerBuffer.writeBytes(message.statementId) + headerBuffer.writeShort(message.paramId) - private def encodeValue(maybeValue: Any) : ByteBuf = { - val value = maybeValue match { - case Some(v) => v - case _ => maybeValue - } - BlobEncoder.encoderFor(value).get.encode(value) + val result = Unpooled.wrappedBuffer(headerBuffer, message.value) + + out.add(result) } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala index 6b1ef7e2..db66db1f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/message/client/SendLongDataMessage.scala @@ -1,6 +1,8 @@ package com.github.mauricio.async.db.mysql.message.client +import io.netty.buffer.ByteBuf + case class SendLongDataMessage ( statementId : Array[Byte], - value : Any, + value : ByteBuf, paramId : Int ) \ No newline at end of file From 3da528049061a52d06cbe7b6a91e078480000a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Thu, 11 Dec 2014 09:55:05 +0100 Subject: [PATCH 289/357] Set BLOB via ScatteringByteChannel (first attempt) --- mysql-async/README.md | 4 +- .../db/mysql/binary/BinaryRowEncoder.scala | 3 + .../binary/encoder/DummyBlobEncoder.scala | 14 ++++ .../mysql/codec/MySQLConnectionHandler.scala | 20 +++++ .../db/mysql/codec/SendLongDataEncoder.scala | 4 + .../async/db/mysql/BinaryColumnsSpec.scala | 73 ++++++++++++++----- 6 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala diff --git a/mysql-async/README.md b/mysql-async/README.md index 3a152286..b979fc2f 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -91,8 +91,10 @@ String | string Array[Byte] | blob java.nio.ByteBuffer | blob io.netty.buffer.ByteBuf | blob +java.nio.channels.ScatteringByteChannel | blob -The maximum size of a blob is 2^24-9 bytes (almost 16 MiB). +The maximum size of a blob you set via Array[Byte], ByteBuffer or ByteBuf is 2^24-9 bytes (almost 16 MiB). +Blobs set via a Channel can be larger, the Channel is read until EOF and then closed. You don't have to match exact values when sending parameters for your prepared statements, MySQL is usually smart enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 8792f08f..9f38fd05 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -16,6 +16,8 @@ package com.github.mauricio.async.db.mysql.binary +import java.nio.channels.ScatteringByteChannel + import io.netty.buffer.ByteBuf import java.nio.ByteBuffer import java.nio.charset.Charset @@ -88,6 +90,7 @@ class BinaryRowEncoder( charset : Charset ) { case v : java.util.Date => JavaDateEncoder case v : ByteBuffer => ByteBufferEncoder case v : ByteBuf => ByteBufEncoder + case v : ScatteringByteChannel => DummyBlobEncoder } } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala new file mode 100644 index 00000000..6bfa86d4 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala @@ -0,0 +1,14 @@ +package com.github.mauricio.async.db.mysql.binary.encoder + +import com.github.mauricio.async.db.mysql.column.ColumnTypes +import io.netty.buffer.ByteBuf + +object DummyBlobEncoder extends BinaryEncoder { + + def encode(value: Any, buffer: ByteBuf): Unit = { + throw new UnsupportedOperationException() + } + + def encodesTo: Int = ColumnTypes.FIELD_TYPE_BLOB + + } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index aec8d24c..ad7d3ac1 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.mysql.codec import java.nio.ByteBuffer +import java.nio.channels.ScatteringByteChannel import com.github.mauricio.async.db.Configuration import com.github.mauricio.async.db.general.MutableResultSet @@ -260,6 +261,7 @@ class MySQLConnectionHandler( case v : Array[Byte] => v.length > SendLongDataEncoder.LONG_THRESHOLD case v : ByteBuffer => v.remaining() > SendLongDataEncoder.LONG_THRESHOLD case v : ByteBuf => v.readableBytes() > SendLongDataEncoder.LONG_THRESHOLD + case _ : ScatteringByteChannel => true case _ => false } @@ -275,9 +277,27 @@ class MySQLConnectionHandler( case v : ByteBuf => sendBuffer(v, statementId, index) + + case channel : ScatteringByteChannel => + sendChannel(channel, statementId, index) } } + // TODO this is blocking + private def sendChannel(channel: ScatteringByteChannel, statementId: Array[Byte], paramId: Int) { + var bytesWritten = 0 + do { + val dataBuffer = Unpooled.directBuffer(SendLongDataEncoder.INITIAL_BUFFER_SIZE, SendLongDataEncoder.MAX_BUFFER_SIZE) + do { + bytesWritten = dataBuffer.writeBytes(channel, SendLongDataEncoder.MAX_BUFFER_SIZE) + } while (bytesWritten == 0) + if (bytesWritten > 0) { + sendBuffer(dataBuffer, statementId, paramId) + } + } while (bytesWritten > -1) + channel.close() + } + private def sendBuffer(buffer: ByteBuf, statementId: Array[Byte], paramId: Int) { writeAndHandleError(new SendLongDataMessage(statementId, buffer, paramId)) } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala index ce51140f..af02131b 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala @@ -10,6 +10,10 @@ object SendLongDataEncoder { val log = Log.get[SendLongDataEncoder] val LONG_THRESHOLD = 1023 + + val INITIAL_BUFFER_SIZE = 1024 // 1 KiB + + val MAX_BUFFER_SIZE = 1024*1024 // 1 MiB } class SendLongDataEncoder diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala index 6c7c1313..b39f9356 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala @@ -1,6 +1,8 @@ package com.github.mauricio.async.db.mysql -import org.specs2.mutable.Specification +import java.io.{FileInputStream, FileOutputStream, BufferedOutputStream, File} + +import org.specs2.mutable.{After, Specification} import java.util.UUID import java.nio.ByteBuffer import io.netty.buffer.Unpooled @@ -9,6 +11,17 @@ import com.github.mauricio.async.db.RowData class BinaryColumnsSpec extends Specification with ConnectionHelper { + val createBlobTable = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL, + | blob_column LONGBLOB, + | primary key (id)) + """.stripMargin + + val insertIntoBlobTable = "INSERT INTO POSTS (id,blob_column) VALUES (?,?)" + + val selectFromBlobTable = "SELECT id,blob_column FROM POSTS ORDER BY id" + "connection" should { "correctly load fields as byte arrays" in { @@ -106,7 +119,7 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { } - "support BLOB type with large values" in { + "support BLOB type with long values" in { val bytes = (1 to 2100).map(_.toByte).toArray @@ -114,27 +127,35 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { } - } + "support BLOB type with ScatteringByteChannel input" in new BlobFile { - def testBlob(bytes: Array[Byte]) = { - val create = - """CREATE TEMPORARY TABLE POSTS ( - | id INT NOT NULL, - | blob_column BLOB, - | primary key (id)) - """.stripMargin + withConnection { + connection => + executeQuery(connection, createBlobTable) + + val channel = new FileInputStream(blobFile).getChannel + executePreparedStatement(connection, insertIntoBlobTable, 1, channel) + + val Some(rows) = executeQuery(connection, selectFromBlobTable).rows + rows(0)("id") === 1 + val retrievedBlob = rows(0)("blob_column").asInstanceOf[Array[Byte]] + retrievedBlob.length === BlobSize + 0 to retrievedBlob.length-1 foreach { n => retrievedBlob(n) === n.toByte } + } - val insert = "INSERT INTO POSTS (id,blob_column) VALUES (?,?)" - val select = "SELECT id,blob_column FROM POSTS ORDER BY id" + } + } + + def testBlob(bytes: Array[Byte]) = { withConnection { connection => - executeQuery(connection, create) - executePreparedStatement(connection, insert, 1, Some(bytes)) - executePreparedStatement(connection, insert, 2, ByteBuffer.wrap(bytes)) - executePreparedStatement(connection, insert, 3, Unpooled.wrappedBuffer(bytes)) + executeQuery(connection, createBlobTable) + executePreparedStatement(connection, insertIntoBlobTable, 1, Some(bytes)) + executePreparedStatement(connection, insertIntoBlobTable, 2, ByteBuffer.wrap(bytes)) + executePreparedStatement(connection, insertIntoBlobTable, 3, Unpooled.wrappedBuffer(bytes)) - val Some(rows) = executeQuery(connection, select).rows + val Some(rows) = executeQuery(connection, selectFromBlobTable).rows rows(0)("id") === 1 rows(0)("blob_column") === bytes rows(1)("id") === 2 @@ -149,4 +170,20 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { def compareBytes( row : RowData, column : String, expected : String ) = row(column) === expected.getBytes(CharsetUtil.UTF_8) -} \ No newline at end of file +} + +trait BlobFile extends After { + val BlobSize = (16 * 1024 * 1024)-9 + + lazy val blobFile = { + val file = File.createTempFile("blob", null) + val bos = new BufferedOutputStream(new FileOutputStream(file)) + 0 to BlobSize-1 foreach { n => bos.write(n.toByte) } + bos.close() + file + } + + def after = { + blobFile.delete() + } +} From 7f0f2495b946013a1e6556b8927df85b6a12666d Mon Sep 17 00:00:00 2001 From: nyavro Date: Fri, 12 Dec 2014 12:52:15 +0700 Subject: [PATCH 290/357] #102 Provided stored procedure tests --- .../async/db/mysql/StoredProceduresSpec.scala | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/StoredProceduresSpec.scala 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 new file mode 100644 index 00000000..3c1e66f0 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/StoredProceduresSpec.scala @@ -0,0 +1,69 @@ +/* + * 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.mysql + +import com.github.mauricio.async.db.ResultSet +import com.github.mauricio.async.db.util.FutureUtils._ +import org.specs2.mutable.Specification + +class StoredProceduresSpec extends Specification with ConnectionHelper { + + "connection" should { + + "be able to execute create stored procedure" in { + withConnection { + connection => + val future = for( + drop <- connection.sendQuery("DROP PROCEDURE IF exists helloWorld;"); + create <- connection.sendQuery( + """ + CREATE PROCEDURE helloWorld(OUT param1 VARCHAR(20)) + BEGIN + SELECT 'hello' INTO param1; + END + """ + ) + ) yield create + awaitFuture(future).statusMessage === "" + } + } + + "be able to call stored procedure" in { + withConnection { + connection => + val future = for( + drop <- connection.sendQuery("DROP PROCEDURE IF exists constTest;"); + create <- connection.sendQuery( + """ + CREATE PROCEDURE constTest(OUT param INT) + BEGIN + SELECT 125 INTO param; + END + """ + ); + call <- connection.sendQuery("CALL constTest(@arg)"); + arg <- connection.sendQuery("SELECT @arg") + ) yield arg + val result: Option[ResultSet] = awaitFuture(future).rows + result.isDefined === true + val rows = result.get + rows.size === 1 + rows(0)(rows.columnNames.head) == 125 + } + } + } +} \ No newline at end of file From 2ca73cd992f364efa77b4da1dadd218f8b82f132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Fri, 12 Dec 2014 10:51:43 +0100 Subject: [PATCH 291/357] Set BLOB via ScatteringByteChannel (avoid blocking) --- .../mysql/codec/MySQLConnectionHandler.scala | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index ad7d3ac1..9cf213d9 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -190,7 +190,7 @@ class MySQLConnectionHandler( writeAndHandleError(message) } - def sendPreparedStatement( query: String, values: Seq[Any] ) { + def sendPreparedStatement( query: String, values: Seq[Any] ): Future[ChannelFuture] = { val preparedStatement = new PreparedStatement(query, values) this.currentColumns.clear() @@ -236,24 +236,33 @@ class MySQLConnectionHandler( } } - private def executePreparedStatement( statementId : Array[Byte], columnsCount : Int, values : Seq[Any], parameters : Seq[ColumnDefinitionMessage] ) { + private def executePreparedStatement( statementId : Array[Byte], columnsCount : Int, values : Seq[Any], parameters : Seq[ColumnDefinitionMessage] ): Future[ChannelFuture] = { decoder.preparedStatementExecuteStarted(columnsCount, parameters.size) this.currentColumns.clear() this.currentParameters.clear() - var nonLongIndices: Set[Int] = Set() - values.zipWithIndex.foreach { - case (Some(value), index) if isLong(value) => - sendLongParameter(statementId, index, value) - - case (value, index) if isLong(value) => - sendLongParameter(statementId, index, value) - - case (_, index) => - nonLongIndices += index + val (nonLongIndicesOpt, longValuesOpt) = values.zipWithIndex.map { + case (Some(value), index) if isLong(value) => (None, Some(index, value)) + case (value, index) if isLong(value) => (None, Some(index, value)) + case (_, index) => (Some(index), None) + }.unzip + val nonLongIndices: Seq[Int] = nonLongIndicesOpt.flatten + val longValues: Seq[(Int, Any)] = longValuesOpt.flatten + + if (longValues.nonEmpty) { + val (firstIndex, firstValue) = longValues.head + var channelFuture: Future[ChannelFuture] = sendLongParameter(statementId, firstIndex, firstValue) + longValues.tail foreach { case (index, value) => + channelFuture = channelFuture.flatMap { _ => + sendLongParameter(statementId, index, value) + } + } + channelFuture flatMap { _ => + writeAndHandleError(new PreparedStatementExecuteMessage(statementId, values, nonLongIndices.toSet, parameters)) + } + } else { + writeAndHandleError(new PreparedStatementExecuteMessage(statementId, values, nonLongIndices.toSet, parameters)) } - - writeAndHandleError(new PreparedStatementExecuteMessage(statementId, values, nonLongIndices, parameters)) } private def isLong(value: Any): Boolean = { @@ -267,7 +276,7 @@ class MySQLConnectionHandler( } } - private def sendLongParameter(statementId: Array[Byte], index: Int, longValue: Any) { + private def sendLongParameter(statementId: Array[Byte], index: Int, longValue: Any): Future[ChannelFuture] = { longValue match { case v : Array[Byte] => sendBuffer(Unpooled.wrappedBuffer(v), statementId, index) @@ -283,22 +292,25 @@ class MySQLConnectionHandler( } } - // TODO this is blocking - private def sendChannel(channel: ScatteringByteChannel, statementId: Array[Byte], paramId: Int) { - var bytesWritten = 0 - do { - val dataBuffer = Unpooled.directBuffer(SendLongDataEncoder.INITIAL_BUFFER_SIZE, SendLongDataEncoder.MAX_BUFFER_SIZE) + private def sendChannel(channel: ScatteringByteChannel, statementId: Array[Byte], paramId: Int): Future[ChannelFuture] = { + Future { + var bytesWritten = 0 + var channelFuture: ChannelFuture = null do { - bytesWritten = dataBuffer.writeBytes(channel, SendLongDataEncoder.MAX_BUFFER_SIZE) - } while (bytesWritten == 0) - if (bytesWritten > 0) { - sendBuffer(dataBuffer, statementId, paramId) - } - } while (bytesWritten > -1) - channel.close() + val dataBuffer = Unpooled.directBuffer(SendLongDataEncoder.INITIAL_BUFFER_SIZE, SendLongDataEncoder.MAX_BUFFER_SIZE) + do { + bytesWritten = dataBuffer.writeBytes(channel, SendLongDataEncoder.MAX_BUFFER_SIZE) + } while (bytesWritten == 0) + if (bytesWritten > 0) { + channelFuture = sendBuffer(dataBuffer, statementId, paramId) + } + } while (bytesWritten > -1) + channel.close() + channelFuture + } } - private def sendBuffer(buffer: ByteBuf, statementId: Array[Byte], paramId: Int) { + private def sendBuffer(buffer: ByteBuf, statementId: Array[Byte], paramId: Int): ChannelFuture = { writeAndHandleError(new SendLongDataMessage(statementId, buffer, paramId)) } From 42b5a11dd951d6fc5ec18e8d553088553a488d54 Mon Sep 17 00:00:00 2001 From: nyavro Date: Fri, 12 Dec 2014 21:45:11 +0600 Subject: [PATCH 292/357] #102 Test passing input parameters into stored procedure --- .../async/db/mysql/StoredProceduresSpec.scala | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) 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 3c1e66f0..9aa4f66b 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 @@ -62,7 +62,31 @@ class StoredProceduresSpec extends Specification with ConnectionHelper { result.isDefined === true val rows = result.get rows.size === 1 - rows(0)(rows.columnNames.head) == 125 + rows(0)(rows.columnNames.head) === 125 + } + } + + "be able to call stored procedure with input parameter" in { + withConnection { + connection => + val future = for( + drop <- connection.sendQuery("DROP PROCEDURE IF exists addTest;"); + create <- connection.sendQuery( + """ + CREATE PROCEDURE addTest(IN a INT, IN b INT, OUT sum INT) + BEGIN + SELECT a+b INTO sum; + END + """ + ); + call <- connection.sendQuery("CALL addTest(132, 245, @sm)"); + res <- connection.sendQuery("SELECT @sm") + ) yield res + val result: Option[ResultSet] = awaitFuture(future).rows + result.isDefined === true + val rows = result.get + rows.size === 1 + rows(0)(rows.columnNames.head) === 377 } } } From 39a7b52f4f5cc477151c79e38777ecadcbe7ca13 Mon Sep 17 00:00:00 2001 From: nyavro Date: Tue, 16 Dec 2014 11:23:45 +0600 Subject: [PATCH 293/357] #102 remove procedure test --- .../async/db/mysql/StoredProceduresSpec.scala | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) 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 9aa4f66b..3d68563b 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 @@ -89,5 +89,44 @@ class StoredProceduresSpec extends Specification with ConnectionHelper { rows(0)(rows.columnNames.head) === 377 } } + + "be able to remove stored procedure" in { + withConnection { + connection => + val createResult: Option[ResultSet] = awaitFuture( + for( + drop <- connection.sendQuery("DROP PROCEDURE IF exists remTest;"); + create <- connection.sendQuery( + """ + CREATE PROCEDURE remTest(OUT cnst INT) + BEGIN + SELECT 987 INTO cnst; + END + """ + ); + routine <- connection.sendQuery( + """ + SELECT routine_name FROM INFORMATION_SCHEMA.ROUTINES WHERE routine_name="remTest" + """ + ) + ) yield routine + ).rows + createResult.isDefined === true + createResult.get.size === 1 + createResult.get(0)("routine_name") === "remTest" + val removeResult: Option[ResultSet] = awaitFuture( + for( + drop <- connection.sendQuery("DROP PROCEDURE remTest;"); + routine <- connection.sendQuery( + """ + SELECT routine_name FROM INFORMATION_SCHEMA.ROUTINES WHERE routine_name="remTest" + """ + ) + ) yield routine + ).rows + removeResult.isDefined === true + removeResult.get.isEmpty === true + } + } } } \ No newline at end of file From 117f760ded1e6347a389eb6f6fae313681f0f8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Wed, 17 Dec 2014 11:40:48 +0100 Subject: [PATCH 294/357] Remove channel support for now --- mysql-async/README.md | 4 +- .../db/mysql/binary/BinaryRowEncoder.scala | 9 +-- .../binary/encoder/DummyBlobEncoder.scala | 14 ---- .../mysql/codec/MySQLConnectionHandler.scala | 31 +------- .../db/mysql/codec/SendLongDataEncoder.scala | 4 - .../async/db/mysql/BinaryColumnsSpec.scala | 73 +++++-------------- 6 files changed, 25 insertions(+), 110 deletions(-) delete mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala diff --git a/mysql-async/README.md b/mysql-async/README.md index b979fc2f..3a152286 100644 --- a/mysql-async/README.md +++ b/mysql-async/README.md @@ -91,10 +91,8 @@ String | string Array[Byte] | blob java.nio.ByteBuffer | blob io.netty.buffer.ByteBuf | blob -java.nio.channels.ScatteringByteChannel | blob -The maximum size of a blob you set via Array[Byte], ByteBuffer or ByteBuf is 2^24-9 bytes (almost 16 MiB). -Blobs set via a Channel can be larger, the Channel is read until EOF and then closed. +The maximum size of a blob is 2^24-9 bytes (almost 16 MiB). You don't have to match exact values when sending parameters for your prepared statements, MySQL is usually smart enough to understand that if you have sent an Int to `smallint` column it has to truncate the 4 bytes into 2. diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala index 9f38fd05..aff0b36f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowEncoder.scala @@ -16,15 +16,13 @@ package com.github.mauricio.async.db.mysql.binary -import java.nio.channels.ScatteringByteChannel - -import io.netty.buffer.ByteBuf import java.nio.ByteBuffer import java.nio.charset.Charset + import com.github.mauricio.async.db.mysql.binary.encoder._ import com.github.mauricio.async.db.util._ +import io.netty.buffer.ByteBuf import org.joda.time._ -import scala.Some object BinaryRowEncoder { final val log = Log.get[BinaryRowEncoder] @@ -32,8 +30,6 @@ object BinaryRowEncoder { class BinaryRowEncoder( charset : Charset ) { - import BinaryRowEncoder.log - private final val stringEncoder = new StringEncoder(charset) private final val encoders = Map[Class[_],BinaryEncoder]( classOf[String] -> this.stringEncoder, @@ -90,7 +86,6 @@ class BinaryRowEncoder( charset : Charset ) { case v : java.util.Date => JavaDateEncoder case v : ByteBuffer => ByteBufferEncoder case v : ByteBuf => ByteBufEncoder - case v : ScatteringByteChannel => DummyBlobEncoder } } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala deleted file mode 100644 index 6bfa86d4..00000000 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/encoder/DummyBlobEncoder.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.mauricio.async.db.mysql.binary.encoder - -import com.github.mauricio.async.db.mysql.column.ColumnTypes -import io.netty.buffer.ByteBuf - -object DummyBlobEncoder extends BinaryEncoder { - - def encode(value: Any, buffer: ByteBuf): Unit = { - throw new UnsupportedOperationException() - } - - def encodesTo: Int = ColumnTypes.FIELD_TYPE_BLOB - - } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 9cf213d9..6ce93145 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -16,10 +16,11 @@ package com.github.mauricio.async.db.mysql.codec +import java.net.InetSocketAddress import java.nio.ByteBuffer -import java.nio.channels.ScatteringByteChannel import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.exceptions.DatabaseException import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.mysql.binary.BinaryRowDecoder import com.github.mauricio.async.db.mysql.message.client._ @@ -28,16 +29,14 @@ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import io.netty.bootstrap.Bootstrap -import io.netty.buffer.{Unpooled, ByteBuf, ByteBufAllocator} +import io.netty.buffer.{ByteBuf, ByteBufAllocator, Unpooled} import io.netty.channel._ import io.netty.channel.socket.nio.NioSocketChannel import io.netty.handler.codec.CodecException -import java.net.InetSocketAddress -import scala.Some + import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent._ -import com.github.mauricio.async.db.exceptions.DatabaseException class MySQLConnectionHandler( configuration: Configuration, @@ -270,7 +269,6 @@ class MySQLConnectionHandler( case v : Array[Byte] => v.length > SendLongDataEncoder.LONG_THRESHOLD case v : ByteBuffer => v.remaining() > SendLongDataEncoder.LONG_THRESHOLD case v : ByteBuf => v.readableBytes() > SendLongDataEncoder.LONG_THRESHOLD - case _ : ScatteringByteChannel => true case _ => false } @@ -286,27 +284,6 @@ class MySQLConnectionHandler( case v : ByteBuf => sendBuffer(v, statementId, index) - - case channel : ScatteringByteChannel => - sendChannel(channel, statementId, index) - } - } - - private def sendChannel(channel: ScatteringByteChannel, statementId: Array[Byte], paramId: Int): Future[ChannelFuture] = { - Future { - var bytesWritten = 0 - var channelFuture: ChannelFuture = null - do { - val dataBuffer = Unpooled.directBuffer(SendLongDataEncoder.INITIAL_BUFFER_SIZE, SendLongDataEncoder.MAX_BUFFER_SIZE) - do { - bytesWritten = dataBuffer.writeBytes(channel, SendLongDataEncoder.MAX_BUFFER_SIZE) - } while (bytesWritten == 0) - if (bytesWritten > 0) { - channelFuture = sendBuffer(dataBuffer, statementId, paramId) - } - } while (bytesWritten > -1) - channel.close() - channelFuture } } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala index af02131b..ce51140f 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/SendLongDataEncoder.scala @@ -10,10 +10,6 @@ object SendLongDataEncoder { val log = Log.get[SendLongDataEncoder] val LONG_THRESHOLD = 1023 - - val INITIAL_BUFFER_SIZE = 1024 // 1 KiB - - val MAX_BUFFER_SIZE = 1024*1024 // 1 MiB } class SendLongDataEncoder diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala index b39f9356..6c7c1313 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BinaryColumnsSpec.scala @@ -1,8 +1,6 @@ package com.github.mauricio.async.db.mysql -import java.io.{FileInputStream, FileOutputStream, BufferedOutputStream, File} - -import org.specs2.mutable.{After, Specification} +import org.specs2.mutable.Specification import java.util.UUID import java.nio.ByteBuffer import io.netty.buffer.Unpooled @@ -11,17 +9,6 @@ import com.github.mauricio.async.db.RowData class BinaryColumnsSpec extends Specification with ConnectionHelper { - val createBlobTable = - """CREATE TEMPORARY TABLE POSTS ( - | id INT NOT NULL, - | blob_column LONGBLOB, - | primary key (id)) - """.stripMargin - - val insertIntoBlobTable = "INSERT INTO POSTS (id,blob_column) VALUES (?,?)" - - val selectFromBlobTable = "SELECT id,blob_column FROM POSTS ORDER BY id" - "connection" should { "correctly load fields as byte arrays" in { @@ -119,7 +106,7 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { } - "support BLOB type with long values" in { + "support BLOB type with large values" in { val bytes = (1 to 2100).map(_.toByte).toArray @@ -127,35 +114,27 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { } - "support BLOB type with ScatteringByteChannel input" in new BlobFile { - - withConnection { - connection => - executeQuery(connection, createBlobTable) - - val channel = new FileInputStream(blobFile).getChannel - executePreparedStatement(connection, insertIntoBlobTable, 1, channel) - - val Some(rows) = executeQuery(connection, selectFromBlobTable).rows - rows(0)("id") === 1 - val retrievedBlob = rows(0)("blob_column").asInstanceOf[Array[Byte]] - retrievedBlob.length === BlobSize - 0 to retrievedBlob.length-1 foreach { n => retrievedBlob(n) === n.toByte } - } - - } - } def testBlob(bytes: Array[Byte]) = { + val create = + """CREATE TEMPORARY TABLE POSTS ( + | id INT NOT NULL, + | blob_column BLOB, + | primary key (id)) + """.stripMargin + + val insert = "INSERT INTO POSTS (id,blob_column) VALUES (?,?)" + val select = "SELECT id,blob_column FROM POSTS ORDER BY id" + withConnection { connection => - executeQuery(connection, createBlobTable) - executePreparedStatement(connection, insertIntoBlobTable, 1, Some(bytes)) - executePreparedStatement(connection, insertIntoBlobTable, 2, ByteBuffer.wrap(bytes)) - executePreparedStatement(connection, insertIntoBlobTable, 3, Unpooled.wrappedBuffer(bytes)) + executeQuery(connection, create) + executePreparedStatement(connection, insert, 1, Some(bytes)) + executePreparedStatement(connection, insert, 2, ByteBuffer.wrap(bytes)) + executePreparedStatement(connection, insert, 3, Unpooled.wrappedBuffer(bytes)) - val Some(rows) = executeQuery(connection, selectFromBlobTable).rows + val Some(rows) = executeQuery(connection, select).rows rows(0)("id") === 1 rows(0)("blob_column") === bytes rows(1)("id") === 2 @@ -170,20 +149,4 @@ class BinaryColumnsSpec extends Specification with ConnectionHelper { def compareBytes( row : RowData, column : String, expected : String ) = row(column) === expected.getBytes(CharsetUtil.UTF_8) -} - -trait BlobFile extends After { - val BlobSize = (16 * 1024 * 1024)-9 - - lazy val blobFile = { - val file = File.createTempFile("blob", null) - val bos = new BufferedOutputStream(new FileOutputStream(file)) - 0 to BlobSize-1 foreach { n => bos.write(n.toByte) } - bos.close() - file - } - - def after = { - blobFile.delete() - } -} +} \ No newline at end of file From 1f74c57a8f9c72822e34bedf45dedc5ae08001d5 Mon Sep 17 00:00:00 2001 From: haski Date: Sun, 4 Jan 2015 12:32:19 +0200 Subject: [PATCH 295/357] making sure connection is back in pool before result returns --- .../mauricio/async/db/pool/AsyncObjectPool.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala index 39179737..a288eea5 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.pool -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} /** * @@ -70,10 +70,12 @@ trait AsyncObjectPool[T] { * @return f wrapped with take and giveBack */ - def use[A](f : T => Future[A])(implicit executionContext : scala.concurrent.ExecutionContext) : Future[A] = + def use[A](f: (T) => Future[A])(implicit executionContext: ExecutionContext): Future[A] = take.flatMap { item => - f(item).andThen { case _ => - giveBack(item) + f(item) recoverWith { + case error => giveBack(item).flatMap(_ => Future.failed(error)) + } flatMap { res => + giveBack(item).map { _ => res } } } From 2be41d52d6de45031750f096c7c1b1227b4912c3 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 4 Jan 2015 10:52:01 -0300 Subject: [PATCH 296/357] Updating changelog to close 0.2.16 --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d05f17d5..8f7a65f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,9 +24,14 @@ # Changelog -## 0.2.16 - in progress +## 0.2.16 - 2015-01-04 * Add support to byte arrays for PostgreSQL 8 and older - @SattaiLanfear - #21; +* Make sure connections are returned to the pool before the result is returned to the user - @haski - #119; +* Support to `SEND_LONG_DATA` to MySQL - @mst-appear - #115; +* Support for `ByteBuffer` and `ByteBuf` for binary data - @mst-appear - #113 #112; +* Fixed encoding backslashes in PostgreSQL arrays - @dylex - #110; +* Included `escape` encoding method for bytes in PostgreSQL - @SattaiLanfear - #107; ## 0.2.15 - 2014-09-12 From d479047eb35a1b5021c90f4435ea1b1647a7f086 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 4 Jan 2015 11:00:19 -0300 Subject: [PATCH 297/357] Upgrading netty --- project/Build.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index dff7c993..172ed90d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.16-SNAPSHOT" + val commonVersion = "0.2.16" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" @@ -55,7 +55,7 @@ object Configuration { "org.slf4j" % "slf4j-api" % "1.7.5", "joda-time" % "joda-time" % "2.3", "org.joda" % "joda-convert" % "1.5", - "io.netty" % "netty-all" % "4.0.23.Final", + "io.netty" % "netty-all" % "4.0.25.Final", "org.javassist" % "javassist" % "3.18.1-GA", specs2Dependency, logbackDependency From 598a2658fbd62065abe076d8abffbb82996ae716 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 4 Jan 2015 11:12:26 -0300 Subject: [PATCH 298/357] Starting v0.2.17 cycle --- CHANGELOG.md | 5 ++++- README.markdown | 4 ++-- project/Build.scala | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7a65f8..c80603d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ **Table of Contents** - [Changelog](#changelog) - - [0.2.16 - in progress](#0216---in-progress) + - [0.2.17 - in progresss](#0217---in-progresss) + - [0.2.16 - 2015-01-04](#0216---2015-01-04) - [0.2.15 - 2014-09-12](#0215---2014-09-12) - [0.2.14 - 2014-08-30](#0214---2014-08-30) - [0.2.13 - 2014-04-07](#0213---2014-04-07) @@ -24,6 +25,8 @@ # Changelog +## 0.2.17 - in progresss + ## 0.2.16 - 2015-01-04 * Add support to byte arrays for PostgreSQL 8 and older - @SattaiLanfear - #21; diff --git a/README.markdown b/README.markdown index 342425b9..0d90697a 100644 --- a/README.markdown +++ b/README.markdown @@ -62,7 +62,7 @@ Or Maven: com.github.mauricio postgresql-async_2.11 - 0.2.15 + 0.2.16 ``` @@ -78,7 +78,7 @@ Or Maven: com.github.mauricio mysql-async_2.11 - 0.2.15 + 0.2.16 ``` diff --git a/project/Build.scala b/project/Build.scala index 172ed90d..adf61daa 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.16" + val commonVersion = "0.2.17-SNAPSHOT" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" From 90e00736f950124384309f9ba5f78a4848d9dca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20St=C3=A5ldal?= Date: Mon, 12 Jan 2015 19:01:48 +0100 Subject: [PATCH 299/357] Do not assume MySQL root user without password --- .../mauricio/async/db/mysql/MySQLConnectionSpec.scala | 8 ++++---- script/prepare_build.sh | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala index aebf18dd..5e5500fa 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/MySQLConnectionSpec.scala @@ -30,8 +30,8 @@ class MySQLConnectionSpec extends Specification { database = Some("mysql_async_tests") ) - val rootConfiguration = new Configuration( - "root", + val configurationWithoutPassword = new Configuration( + "mysql_async_nopw", "localhost", port = 3306, password = None, @@ -39,7 +39,7 @@ class MySQLConnectionSpec extends Specification { ) val configurationWithoutDatabase = new Configuration( - "root", + "mysql_async_nopw", "localhost", port = 3306, password = None, @@ -69,7 +69,7 @@ class MySQLConnectionSpec extends Specification { withNonConnectedConnection({ connection => awaitFuture(connection.connect) === connection - }) (rootConfiguration) + }) (configurationWithoutPassword) } "connect to a MySQL instance without a database" in { diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 9992e442..a3a4e183 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -5,6 +5,7 @@ mysql -u root -e 'create database mysql_async_tests;' mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async'@'localhost' IDENTIFIED BY 'root' WITH GRANT OPTION"; mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async_old'@'localhost' WITH GRANT OPTION"; mysql -u root -e "UPDATE mysql.user SET Password = OLD_PASSWORD('do_not_use_this'), plugin = 'mysql_old_password' where User = 'mysql_async_old'; flush privileges;"; +mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async_nopw'@'localhost' WITH GRANT OPTION"; echo "preparing postgresql configs" From 68f58f0f60de9e8b62c86a2d39a0ebdf8c644e1f Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Wed, 14 Jan 2015 18:57:38 +0000 Subject: [PATCH 300/357] Added Gitter badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..4fedd098 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# postgresql-async + +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mauricio/postgresql-async?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) \ No newline at end of file From 4a9a9fd938bd73828babe8fd39cc8316adfb3d9c Mon Sep 17 00:00:00 2001 From: Dragisa Krsmanovic Date: Wed, 28 Jan 2015 17:27:58 -0800 Subject: [PATCH 301/357] Add support for UUID --- .../async/db/column/UUIDEncoderDecoder.scala | 25 ++++++++ .../db/postgresql/column/ColumnTypes.scala | 1 + .../PostgreSQLColumnDecoderRegistry.scala | 4 +- .../PostgreSQLColumnEncoderRegistry.scala | 2 + .../db/postgresql/PreparedStatementSpec.scala | 57 ++++++++++++++++++- 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/column/UUIDEncoderDecoder.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/UUIDEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/UUIDEncoderDecoder.scala new file mode 100644 index 00000000..11987835 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/UUIDEncoderDecoder.scala @@ -0,0 +1,25 @@ +/* + * 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.util.UUID + +object UUIDEncoderDecoder extends ColumnEncoderDecoder { + + override def decode(value: String): UUID = UUID.fromString(value) + +} 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 7f15b0f6..29c6b736 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 @@ -63,6 +63,7 @@ object ColumnTypes { final val MoneyArray = 791 final val NameArray = 1003 + final val UUID = 2950 final val UUIDArray = 2951 final val XMLArray = 143 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 734c0902..606bb442 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 @@ -45,6 +45,7 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e private final val timeArrayDecoder = new ArrayDecoder(TimeEncoderDecoder.Instance) private final val timeWithTimestampArrayDecoder = new ArrayDecoder(TimeWithTimezoneEncoderDecoder) private final val intervalArrayDecoder = new ArrayDecoder(PostgreSQLIntervalEncoderDecoder) + private final val uuidArrayDecoder = new ArrayDecoder(UUIDEncoderDecoder) override def decode(kind: ColumnData, value: ByteBuf, charset: Charset): Any = { decoderFor(kind.dataType).decode(kind, value, charset) @@ -108,7 +109,8 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e case MoneyArray => this.stringArrayDecoder case NameArray => this.stringArrayDecoder - case UUIDArray => this.stringArrayDecoder + case UUID => UUIDEncoderDecoder + case UUIDArray => this.uuidArrayDecoder case XMLArray => this.stringArrayDecoder case ByteA => ByteArrayEncoderDecoder 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 5afb5aa8..24641336 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.util.UUID] -> (UUIDEncoderDecoder -> ColumnTypes.UUID), + classOf[LocalDate] -> ( DateEncoderDecoder -> ColumnTypes.Date ), classOf[LocalDateTime] -> (TimestampEncoderDecoder.Instance -> ColumnTypes.Timestamp), classOf[DateTime] -> (TimestampWithTimezoneEncoderDecoder -> ColumnTypes.TimestampWithTimezone), diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 20c645cc..6fd7d9a6 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -20,7 +20,7 @@ import org.specs2.mutable.Specification import org.joda.time.LocalDate import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.exceptions.InsufficientParametersException -import java.util.Date +import java.util.UUID import com.github.mauricio.async.db.postgresql.exceptions.GenericDatabaseException class PreparedStatementSpec extends Specification with DatabaseTestHelper { @@ -282,6 +282,61 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { } } + "support UUID" in { + if ( System.getenv("TRAVIS") == null ) { + withHandler { + handler => + val create = """create temp table uuids + |( + |id bigserial primary key, + |my_id uuid + |);""".stripMargin + + val insert = "INSERT INTO uuids (my_id) VALUES (?) RETURNING id" + val select = "SELECT * FROM uuids" + + val uuid = UUID.randomUUID() + + executeDdl(handler, create) + executePreparedStatement(handler, insert, Array(uuid) ) + val result = executePreparedStatement(handler, select).rows.get + + result(0)("my_id").asInstanceOf[UUID] === uuid + } + success + } else { + pending + } + } + + "support UUID array" in { + if ( System.getenv("TRAVIS") == null ) { + withHandler { + handler => + val create = """create temp table uuids + |( + |id bigserial primary key, + |my_id uuid[] + |);""".stripMargin + + val insert = "INSERT INTO uuids (my_id) VALUES (?) RETURNING id" + val select = "SELECT * FROM uuids" + + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + + executeDdl(handler, create) + executePreparedStatement(handler, insert, Array(Array(uuid1, uuid2)) ) + val result = executePreparedStatement(handler, select).rows.get + + result(0)("my_id").asInstanceOf[Seq[UUID]] === Seq(uuid1, uuid2) + } + success + } else { + pending + } + } + } } From 16f9f18d606e5251599d025e4770935251d2c8fa Mon Sep 17 00:00:00 2001 From: Dylan Simon Date: Wed, 11 Feb 2015 12:57:03 -0500 Subject: [PATCH 302/357] Ignore errors generated by AsyncObjectPool.giveBack Possible fix for #132, untested. --- .../mauricio/async/db/pool/AsyncObjectPool.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala index a288eea5..4e9050ba 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala @@ -16,7 +16,7 @@ package com.github.mauricio.async.db.pool -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContext, Future, Promise} /** * @@ -72,11 +72,13 @@ trait AsyncObjectPool[T] { def use[A](f: (T) => Future[A])(implicit executionContext: ExecutionContext): Future[A] = take.flatMap { item => - f(item) recoverWith { - case error => giveBack(item).flatMap(_ => Future.failed(error)) - } flatMap { res => - giveBack(item).map { _ => res } + val p = Promise[A]() + f(item).onComplete { r => + giveBack(item).onComplete { _ => + p.complete(r) + } } + p.future } } From efaacba482ada65b9ebbdc218aba4a92d4b1036e Mon Sep 17 00:00:00 2001 From: nkgm Date: Thu, 19 Mar 2015 11:08:01 +0200 Subject: [PATCH 303/357] Add LISTEN/NOTIFY to README --- README.markdown | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.markdown b/README.markdown index 0d90697a..e1e54004 100644 --- a/README.markdown +++ b/README.markdown @@ -17,6 +17,7 @@ - [Prepared statements](#prepared-statements) - [Transactions](#transactions) - [Example usage (for PostgreSQL, but it looks almost the same on MySQL)](#example-usage-for-postgresql-but-it-looks-almost-the-same-on-mysql) + - [LISTEN/NOTIFY support (PostgreSQL only)](#listennotify-support-postgresql-only) - [Contributing](#contributing) - [Licence](#licence) @@ -269,6 +270,21 @@ disconnect and the connection is closed. You can also use the `ConnectionPool` provided by the driver to simplify working with database connections in your app. Check the blog post above for more details and the project's ScalaDocs. +## LISTEN/NOTIFY support (PostgreSQL only) + +LISTEN/NOTIFY is a PostgreSQL-specific feature for database-wide publish-subscribe scenarios. You can listen to database +notifications as such: + +```scala + val connection: Connection = ... + + connection.sendQuery("LISTEN my_channel") + connection.registerNotifyListener { + message => + println(s"channel: ${message.channel}, payload: ${message.payload}") + } +``` + ## Contributing Contributing to the project is simple, fork it on Github, hack on what you're insterested in seeing done or at the From 29c23628af05cf24b4f14d548e2c9c0e0b520321 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sun, 3 May 2015 17:58:28 -0300 Subject: [PATCH 304/357] Include transaction specs for both MySQL and PostgreSQL --- .gitignore | 1 + Procfile | 2 +- .../async/db/mysql/TransactionSpec.scala | 54 +++++++++++++++++-- .../postgresql/pool/ConnectionPoolSpec.scala | 32 +++++++++++ script/prepare_build.sh | 4 +- 5 files changed, 87 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 0122f1ca..1aaf8978 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +databases/* out/* generate_bundles.rb .cache diff --git a/Procfile b/Procfile index 6c1b0717..1288bcfe 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -postgresql: postgres -D /Users/mauricio/databases/postgresql +postgresql: postgres -D databases/postgresql mysql: mysqld --log-warnings --console \ 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 0312f0d5..0ef2f86b 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 @@ -1,14 +1,28 @@ package com.github.mauricio.async.db.mysql +import java.util.UUID +import java.util.concurrent.TimeUnit + import org.specs2.mutable.Specification import com.github.mauricio.async.db.util.FutureUtils.awaitFuture import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.Connection +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Future} +import scala.util.{Success, Failure} + +object TransactionSpec { + + val BrokenInsert = """INSERT INTO users (id, name) VALUES (1, 'Maurício Aragão')""" + val InsertUser = """INSERT INTO users (name) VALUES (?)""" + val TransactionInsert = "insert into transaction_test (id) values (?)" + +} + class TransactionSpec extends Specification with ConnectionHelper { - val brokenInsert = """INSERT INTO users (id, name) VALUES (1, 'Maurício Aragão')""" - val insertUser = """INSERT INTO users (name) VALUES (?)""" + import TransactionSpec._ "connection in transaction" should { @@ -42,7 +56,7 @@ class TransactionSpec extends Specification with ConnectionHelper { val future = connection.inTransaction { c => - c.sendQuery(this.insert).flatMap(r => c.sendQuery(brokenInsert)) + c.sendQuery(this.insert).flatMap(r => c.sendQuery(BrokenInsert)) } try { @@ -77,7 +91,7 @@ class TransactionSpec extends Specification with ConnectionHelper { val future = pool.inTransaction { c => connection = c - c.sendQuery(this.brokenInsert) + c.sendQuery(BrokenInsert) } try { @@ -97,6 +111,38 @@ class TransactionSpec extends Specification with ConnectionHelper { } + "runs commands for a transaction in a single connection" in { + + val id = UUID.randomUUID().toString + + withPool { + pool => + val operations = pool.inTransaction { + connection => + connection.sendPreparedStatement(TransactionInsert, List(id)).flatMap { + result => + connection.sendPreparedStatement(TransactionInsert, List(id)).map { + failure => + List(result, failure) + } + } + } + + Await.ready(operations, Duration(5, TimeUnit.SECONDS)) + + operations.value.get match { + case Success(e) => failure("should not have executed") + case Failure(e) => { + e.asInstanceOf[MySQLException].errorMessage.errorCode === 1062 + executePreparedStatement(pool, "select * from transaction_test where id = ?", id).rows.get.size === 0 + success("ok") + } + } + + } + + } + } } 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 02295b16..dc9053d7 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 @@ -16,12 +16,20 @@ package com.github.mauricio.async.db.postgresql.pool +import java.util.UUID + import com.github.mauricio.async.db.pool.{ConnectionPool, PoolConfiguration} import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} import org.specs2.mutable.Specification +object ConnectionPoolSpec { + val Insert = "insert into transaction_test (id) values (?)" +} + class ConnectionPoolSpec extends Specification with DatabaseTestHelper { + import ConnectionPoolSpec.Insert + "pool" should { "give you a connection when sending statements" in { @@ -51,6 +59,30 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { } } + "runs commands for a transaction in a single connection" in { + + val id = UUID.randomUUID().toString + + withPool { + pool => + val operations = pool.inTransaction { + connection => + connection.sendPreparedStatement(Insert, List(id)).flatMap { + result => + connection.sendPreparedStatement(Insert, List(id)).map { + failure => + List(result, failure) + } + } + } + + val resultSets = await(operations) + + resultSets.size mustEqual(2) + } + + } + } def withPool[R]( fn : (ConnectionPool[PostgreSQLConnection]) => R ) : R = { diff --git a/script/prepare_build.sh b/script/prepare_build.sh index a3a4e183..96aa8345 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -2,6 +2,7 @@ echo "Preparing MySQL configs" mysql -u root -e 'create database mysql_async_tests;' +mysql -u root -e "create table mysql_async_tests.transaction_test (id varchar(255) not null, primary key (id))" mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async'@'localhost' IDENTIFIED BY 'root' WITH GRANT OPTION"; mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async_old'@'localhost' WITH GRANT OPTION"; mysql -u root -e "UPDATE mysql.user SET Password = OLD_PASSWORD('do_not_use_this'), plugin = 'mysql_old_password' where User = 'mysql_async_old'; flush privileges;"; @@ -12,10 +13,11 @@ echo "preparing postgresql configs" psql -c 'create database netty_driver_test;' -U postgres psql -c 'create database netty_driver_time_test;' -U postgres psql -c "alter database netty_driver_time_test set timezone to 'GMT'" -U postgres +psql -c "create table transaction_test ( id varchar(255) not null, constraint id_unique primary key (id))" -U postgres netty_driver_test psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres -psql -d "netty_driver_test" -c "CREATE TYPE example_mood AS ENUM ('sad', 'ok', 'happy');" +psql -d "netty_driver_test" -c "CREATE TYPE example_mood AS ENUM ('sad', 'ok', 'happy');" -U postgres sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf From edabfa955368d02e4ccc97c1cf2eb9c7a6df9645 Mon Sep 17 00:00:00 2001 From: haski Date: Wed, 24 Jun 2015 12:07:34 +0300 Subject: [PATCH 305/357] 1. fix connection leak by returning an error on inactive channel inside the future 2. catch exceptions on user function in the object pool 3. add request timeout for queries --- .../mauricio/async/db/Configuration.scala | 11 +++---- .../async/db/pool/AsyncObjectPool.scala | 17 ++++++++-- .../async/db/mysql/MySQLConnection.scala | 31 ++++++++++++------- .../mysql/codec/MySQLConnectionHandler.scala | 19 +++++++++--- 4 files changed, 53 insertions(+), 25 deletions(-) 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 b9d3041f..e5fb7d6a 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 @@ -17,11 +17,10 @@ package com.github.mauricio.async.db import java.nio.charset.Charset -import scala.Predef._ -import scala.{None, Option, Int} + +import io.netty.buffer.{AbstractByteBufAllocator, PooledByteBufAllocator} import io.netty.util.CharsetUtil -import io.netty.buffer.AbstractByteBufAllocator -import io.netty.buffer.PooledByteBufAllocator + import scala.concurrent.duration._ object Configuration { @@ -55,5 +54,5 @@ case class Configuration(username: String, maximumMessageSize: Int = 16777216, allocator: AbstractByteBufAllocator = PooledByteBufAllocator.DEFAULT, connectTimeout: Duration = 5.seconds, - testTimeout: Duration = 5.seconds - ) + testTimeout: Duration = 5.seconds, + requestTimeout: Duration = 5.seconds) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala index 4e9050ba..3e4345a8 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/AsyncObjectPool.scala @@ -73,11 +73,22 @@ trait AsyncObjectPool[T] { def use[A](f: (T) => Future[A])(implicit executionContext: ExecutionContext): Future[A] = take.flatMap { item => val p = Promise[A]() - f(item).onComplete { r => - giveBack(item).onComplete { _ => - p.complete(r) + try { + f(item).onComplete { r => + giveBack(item).onComplete { _ => + p.complete(r) + } } + } catch { + // calling f might throw exception. + // in that case the item will be removed from the pool if identified as invalid by the factory. + // the error returned to the user is the original error thrown by f. + case error: Throwable => + giveBack(item).onComplete { _ => + p.failure(error) + } } + p.future } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 210cebfc..bb0f905c 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -16,21 +16,22 @@ package com.github.mauricio.async.db.mysql +import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.{AtomicLong, AtomicReference} + import com.github.mauricio.async.db._ import com.github.mauricio.async.db.exceptions._ -import com.github.mauricio.async.db.mysql.codec.{MySQLHandlerDelegate, MySQLConnectionHandler} +import com.github.mauricio.async.db.mysql.codec.{MySQLConnectionHandler, MySQLHandlerDelegate} import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ -import java.util.concurrent.atomic.{AtomicLong,AtomicReference} -import scala.concurrent.{ExecutionContext, Promise, Future} -import io.netty.channel.{EventLoopGroup, ChannelHandlerContext} -import scala.util.Failure -import scala.Some -import scala.util.Success +import io.netty.channel.{ChannelHandlerContext, EventLoopGroup} + +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.util.{Failure, Success} object MySQLConnection { final val Counter = new AtomicLong() @@ -185,18 +186,24 @@ class MySQLConnection( def sendQuery(query: String): Future[QueryResult] = { this.validateIsReadyForQuery() - val promise = Promise[QueryResult] + val promise = Promise[QueryResult]() this.setQueryPromise(promise) this.connectionHandler.write(new QueryMessage(query)) + addTimeout(promise) + promise.future } - private def failQueryPromise(t: Throwable) { + private def addTimeout(promise: Promise[QueryResult]): Unit = { + this.connectionHandler.schedule( + promise.tryFailure(new TimeoutException(s"response took too long to return(${configuration.requestTimeout})")), + configuration.requestTimeout) + } + private def failQueryPromise(t: Throwable) { this.clearQueryPromise.foreach { _.tryFailure(t) } - } private def succeedQueryPromise(queryResult: QueryResult) { @@ -234,9 +241,11 @@ class MySQLConnection( if ( values.length != totalParameters ) { throw new InsufficientParametersException(totalParameters, values) } - val promise = Promise[QueryResult] + val promise = Promise[QueryResult]() this.setQueryPromise(promise) this.connectionHandler.sendPreparedStatement(query, values) + addTimeout(promise) + promise.future } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala index 6ce93145..792aff77 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/MySQLConnectionHandler.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.mysql.codec import java.net.InetSocketAddress import java.nio.ByteBuffer +import java.util.concurrent.TimeUnit import com.github.mauricio.async.db.Configuration import com.github.mauricio.async.db.exceptions.DatabaseException @@ -37,6 +38,7 @@ import io.netty.handler.codec.CodecException import scala.annotation.switch import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.concurrent._ +import scala.concurrent.duration.Duration class MySQLConnectionHandler( configuration: Configuration, @@ -319,17 +321,18 @@ class MySQLConnectionHandler( } private def writeAndHandleError( message : Any ) : ChannelFuture = { - if ( this.currentContext.channel().isActive ) { - val future = this.currentContext.writeAndFlush(message) + val res = this.currentContext.writeAndFlush(message) - future.onFailure { + res.onFailure { case e : Throwable => handleException(e) } - future + res } else { - throw new DatabaseException("This channel is not active and can't take messages") + val error = new DatabaseException("This channel is not active and can't take messages") + handleException(error) + this.currentContext.channel().newFailedFuture(error) } } @@ -351,4 +354,10 @@ class MySQLConnectionHandler( } } + def schedule(block: => Unit, duration: Duration): Unit = { + this.currentContext.channel().eventLoop().schedule(new Runnable { + override def run(): Unit = block + }, duration.toMillis, TimeUnit.MILLISECONDS) + } + } From 37e5fc5dad1a55922360e30ec462d1294d788ca8 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 25 Jun 2015 09:52:38 -0300 Subject: [PATCH 306/357] Fix PostgreSQL pool spec --- .../async/db/postgresql/pool/ConnectionPoolSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 dc9053d7..b71ebe65 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 @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.postgresql.pool import java.util.UUID 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 @@ -76,9 +77,8 @@ class ConnectionPoolSpec extends Specification with DatabaseTestHelper { } } - val resultSets = await(operations) + await(operations) must throwA[GenericDatabaseException] - resultSets.size mustEqual(2) } } From 7a0fc6c234ba215961b9c27b2c912ac81a38bbe4 Mon Sep 17 00:00:00 2001 From: Chris Kahn Date: Tue, 7 Jul 2015 11:35:52 -0400 Subject: [PATCH 307/357] TimeAndDateSpec should test LocalTime printing/encoding using a prepared statement --- .../github/mauricio/async/db/postgresql/TimeAndDateSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index e671a5b4..88fee1dd 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -35,7 +35,7 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { )""" executeDdl(handler, create) - executeQuery(handler, "INSERT INTO messages (moment) VALUES ('04:05:06')") + executePreparedStatement(handler, "INSERT INTO messages (moment) VALUES (?)", Array[Any](new LocalTime(4, 5, 6))) val rows = executePreparedStatement(handler, "select * from messages").rows.get @@ -60,7 +60,7 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { )""" executeDdl(handler, create) - executeQuery(handler, "INSERT INTO messages (moment) VALUES ('04:05:06.134')") + executePreparedStatement(handler, "INSERT INTO messages (moment) VALUES (?)", Array[Any](new LocalTime(4, 5, 6, 134))) val rows = executePreparedStatement(handler, "select * from messages").rows.get From 8d2050075fe2cae8ab18a06cf6e36e93024d0136 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 Jul 2015 22:15:03 -0300 Subject: [PATCH 308/357] Don't use a formatter with an optional value when producing times - fixes #142 --- .../async/db/column/TimeEncoderDecoder.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala index 9a801775..a7d0c879 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/TimeEncoderDecoder.scala @@ -33,14 +33,16 @@ class TimeEncoderDecoder extends ColumnEncoderDecoder { .appendOptional(optional) .toFormatter + final private val printer = new DateTimeFormatterBuilder() + .appendPattern("HH:mm:ss.SSSSSS") + .toFormatter + def formatter = format - override def decode(value: String): LocalTime = { + override def decode(value: String): LocalTime = format.parseLocalTime(value) - } - override def encode(value: Any): String = { - this.format.print(value.asInstanceOf[LocalTime]) - } + override def encode(value: Any): String = + this.printer.print(value.asInstanceOf[LocalTime]) } From c201c41277164da1ad2c57478aaf5be4ce87339f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 Jul 2015 22:44:35 -0300 Subject: [PATCH 309/357] Add test for processing LocalDateTime objects --- .../async/db/postgresql/TimeAndDateSpec.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index 88fee1dd..67e7b877 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -200,6 +200,22 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { } + "handle sending a LocalDateTime and return a LocalDateTime for a timestamp without timezone column" in { + + withTimeHandler { + conn => + val date1 = new LocalDateTime(2190319) + + await(conn.sendPreparedStatement("CREATE TEMP TABLE TEST(T TIMESTAMP)")) + await(conn.sendPreparedStatement("INSERT INTO TEST(T) VALUES(?)", Seq(date1))) + val result = await(conn.sendPreparedStatement("SELECT T FROM TEST")) + val date2 = result.rows.get.head(0) + + date2 === date1 + } + + } + "handle sending a date with timezone and retrieving the date with the same time zone" in { withTimeHandler { From c06eb58fa729207c137a2c406505481216356137 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Mon, 13 Jul 2015 09:57:59 -0300 Subject: [PATCH 310/357] Closing 0.2.17 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index adf61daa..d35aaa34 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.17-SNAPSHOT" + val commonVersion = "0.2.17" val projectScalaVersion = "2.11.0" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" From 19d7269be72a6a81b620ccaef353559716e9bc69 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 24 Jul 2015 10:09:33 -0300 Subject: [PATCH 311/357] Removing timeout implementation for MySQL, client code should be responsible for deciding a timeout on it's futures --- .../com/github/mauricio/async/db/Configuration.scala | 3 +-- .../mauricio/async/db/mysql/MySQLConnection.scala | 9 --------- project/Build.scala | 12 ++++++------ 3 files changed, 7 insertions(+), 17 deletions(-) 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 e5fb7d6a..111852c2 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 @@ -54,5 +54,4 @@ case class Configuration(username: String, maximumMessageSize: Int = 16777216, allocator: AbstractByteBufAllocator = PooledByteBufAllocator.DEFAULT, connectTimeout: Duration = 5.seconds, - testTimeout: Duration = 5.seconds, - requestTimeout: Duration = 5.seconds) + testTimeout: Duration = 5.seconds) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index bb0f905c..042f6bf6 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -16,7 +16,6 @@ package com.github.mauricio.async.db.mysql -import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.{AtomicLong, AtomicReference} import com.github.mauricio.async.db._ @@ -189,17 +188,10 @@ class MySQLConnection( val promise = Promise[QueryResult]() this.setQueryPromise(promise) this.connectionHandler.write(new QueryMessage(query)) - addTimeout(promise) promise.future } - private def addTimeout(promise: Promise[QueryResult]): Unit = { - this.connectionHandler.schedule( - promise.tryFailure(new TimeoutException(s"response took too long to return(${configuration.requestTimeout})")), - configuration.requestTimeout) - } - private def failQueryPromise(t: Throwable) { this.clearQueryPromise.foreach { _.tryFailure(t) @@ -244,7 +236,6 @@ class MySQLConnection( val promise = Promise[QueryResult]() this.setQueryPromise(promise) this.connectionHandler.sendPreparedStatement(query, values) - addTimeout(promise) promise.future } diff --git a/project/Build.scala b/project/Build.scala index d35aaa34..dc1c2c8b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,18 +45,18 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.17" - val projectScalaVersion = "2.11.0" + val commonVersion = "0.2.18-SNAPSHOT" + val projectScalaVersion = "2.11.7" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" - val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.0.13" % "test" + val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.1.3" % "test" val commonDependencies = Seq( - "org.slf4j" % "slf4j-api" % "1.7.5", + "org.slf4j" % "slf4j-api" % "1.7.12", "joda-time" % "joda-time" % "2.3", "org.joda" % "joda-convert" % "1.5", - "io.netty" % "netty-all" % "4.0.25.Final", - "org.javassist" % "javassist" % "3.18.1-GA", + "io.netty" % "netty-all" % "4.0.29.Final", + "org.javassist" % "javassist" % "3.20.0-GA", specs2Dependency, logbackDependency ) From 77226d8d474945341659ab80ae7cdc0b3cc43fbf Mon Sep 17 00:00:00 2001 From: lifey Date: Tue, 28 Jul 2015 09:52:13 +0300 Subject: [PATCH 312/357] Add timeout support for queries fixes on timeout scheduler fixes on timeout scheduler fixes on timeout scheduler styling issues Improve unit test --- .../mauricio/async/db/Configuration.scala | 3 +- .../ConnectionTimeoutedException.scala | 6 ++ .../async/db/pool/TimeoutScheduler.scala | 37 +++++++++ .../async/db/pool/DummyTimeoutScheduler.scala | 29 +++++++ .../async/db/pool/TimeoutSchedulerSpec.scala | 71 ++++++++++++++++ .../async/db/mysql/MySQLConnection.scala | 9 ++- .../mysql/pool/MySQLConnectionFactory.scala | 7 +- .../async/db/mysql/ConnectionHelper.scala | 13 +++ .../async/db/mysql/QueryTimeoutSpec.scala | 80 +++++++++++++++++++ .../db/postgresql/PostgreSQLConnection.scala | 11 ++- .../pool/PostgreSQLConnectionFactory.scala | 4 + 11 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionTimeoutedException.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/pool/TimeoutSchedulerSpec.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.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 111852c2..089a634b 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 @@ -54,4 +54,5 @@ case class Configuration(username: String, maximumMessageSize: Int = 16777216, allocator: AbstractByteBufAllocator = PooledByteBufAllocator.DEFAULT, connectTimeout: Duration = 5.seconds, - testTimeout: Duration = 5.seconds) + testTimeout: Duration = 5.seconds, + queryTimeout: Duration = Duration.Inf) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionTimeoutedException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionTimeoutedException.scala new file mode 100644 index 00000000..7e02c17c --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/ConnectionTimeoutedException.scala @@ -0,0 +1,6 @@ +package com.github.mauricio.async.db.exceptions + +import com.github.mauricio.async.db.Connection + +class ConnectionTimeoutedException( val connection : Connection ) + extends DatabaseException( "The connection %s has a timeouted query and is being closed".format(connection) ) \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala new file mode 100644 index 00000000..b2c2616b --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala @@ -0,0 +1,37 @@ +package com.github.mauricio.async.db.pool + +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.{TimeUnit, TimeoutException, ScheduledFuture} +import com.github.mauricio.async.db.util.NettyUtils +import scala.concurrent.{ExecutionContext, Promise} +import scala.concurrent.duration.Duration + +trait TimeoutScheduler { + implicit val internalPool: ExecutionContext + def onTimeout // implementors should decide here what they want to do when a timeout occur + private var isTimeoutedBool = new AtomicBoolean(false); + def isTimeouted = isTimeoutedBool.get // We need this property as isClosed takes time to complete and + // we don't want the connection to be used again. + + def addTimeout[A](promise: Promise[A], duration: Duration) : Option[ScheduledFuture[_]] = { + if (duration != Duration.Inf) { + val scheduledFuture = schedule( + { + if (promise.tryFailure(new TimeoutException(s"Operation is timeouted after it took too long to return (${duration})"))) { + isTimeoutedBool.set(true) + onTimeout + } + }, + duration) + promise.future.onComplete(x => scheduledFuture.cancel(false)) + + return Some(scheduledFuture) + } + return None + } + + def schedule(block: => Unit, duration: Duration) : ScheduledFuture[_] = + NettyUtils.DefaultEventLoopGroup.schedule(new Runnable { + override def run(): Unit = block + }, duration.toMillis, TimeUnit.MILLISECONDS) +} diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala new file mode 100644 index 00000000..302ac321 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala @@ -0,0 +1,29 @@ +/* + * 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.pool + +import java.util.concurrent.atomic.AtomicInteger +import com.github.mauricio.async.db.util.ExecutorServiceUtils +/** + * Implementation of TimeoutScheduler used for testing + */ +class DummyTimeoutScheduler extends TimeoutScheduler { + implicit val internalPool = ExecutorServiceUtils.CachedExecutionContext + private val timeOuts = new AtomicInteger + override def onTimeout = timeOuts.incrementAndGet + def timeoutCount = timeOuts.get() +} 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 new file mode 100644 index 00000000..46f20866 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/TimeoutSchedulerSpec.scala @@ -0,0 +1,71 @@ +/* + * 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.pool + +import java.util.concurrent.{ScheduledFuture, TimeoutException} +import com.github.mauricio.async.db.util.{ByteBufferUtils, ExecutorServiceUtils} +import org.specs2.mutable.SpecificationWithJUnit +import scala.concurrent.duration._ +import scala.concurrent.{Future, Promise} + +/** + * Tests for TimeoutScheduler + */ +class TimeoutSchedulerSpec extends SpecificationWithJUnit { + + val TIMEOUT_DID_NOT_PASS = "timeout did not pass" + + + "test timeout did not pass" in { + val timeoutScheduler = new DummyTimeoutScheduler() + val promise = Promise[String]() + val scheduledFuture = timeoutScheduler.addTimeout(promise,Duration(1000, MILLISECONDS)) + Thread.sleep(100); + promise.isCompleted === false + promise.success(TIMEOUT_DID_NOT_PASS) + Thread.sleep(1500) + promise.future.value.get.get === TIMEOUT_DID_NOT_PASS + scheduledFuture.get.isCancelled === true + timeoutScheduler.timeoutCount === 0 + } + + "test timeout passed" in { + val timeoutMillis = 100 + val promise = Promise[String]() + val timeoutScheduler = new DummyTimeoutScheduler() + val scheduledFuture = timeoutScheduler.addTimeout(promise,Duration(timeoutMillis, MILLISECONDS)) + Thread.sleep(1000) + promise.isCompleted === true + scheduledFuture.get.isCancelled === false + promise.trySuccess(TIMEOUT_DID_NOT_PASS) + timeoutScheduler.timeoutCount === 1 + promise.future.value.get.get must throwA[TimeoutException](message = s"Operation is timeouted after it took too long to return \\(${timeoutMillis} milliseconds\\)") + } + + + "test no timeout" in { + val timeoutScheduler = new DummyTimeoutScheduler() + val promise = Promise[String]() + val scheduledFuture = timeoutScheduler.addTimeout(promise,Duration.Inf) + Thread.sleep(1000) + scheduledFuture === None + promise.isCompleted === false + promise.success(TIMEOUT_DID_NOT_PASS) + promise.future.value.get.get === TIMEOUT_DID_NOT_PASS + timeoutScheduler.timeoutCount === 0 + } +} + diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index 042f6bf6..f4e7cc60 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -25,6 +25,7 @@ import com.github.mauricio.async.db.mysql.exceptions.MySQLException import com.github.mauricio.async.db.mysql.message.client._ import com.github.mauricio.async.db.mysql.message.server._ import com.github.mauricio.async.db.mysql.util.CharsetMapper +import com.github.mauricio.async.db.pool.TimeoutScheduler import com.github.mauricio.async.db.util.ChannelFutureTransformer.toFuture import com.github.mauricio.async.db.util._ import io.netty.channel.{ChannelHandlerContext, EventLoopGroup} @@ -46,6 +47,7 @@ class MySQLConnection( ) extends MySQLHandlerDelegate with Connection + with TimeoutScheduler { import MySQLConnection.log @@ -56,7 +58,7 @@ class MySQLConnection( private final val connectionCount = MySQLConnection.Counter.incrementAndGet() private final val connectionId = s"[mysql-connection-$connectionCount]" - private implicit val internalPool = executionContext + override implicit val internalPool = executionContext private final val connectionHandler = new MySQLConnectionHandler( configuration, @@ -188,7 +190,7 @@ class MySQLConnection( val promise = Promise[QueryResult]() this.setQueryPromise(promise) this.connectionHandler.write(new QueryMessage(query)) - + addTimeout(promise, configuration.queryTimeout) promise.future } @@ -224,6 +226,7 @@ class MySQLConnection( } def disconnect: Future[Connection] = this.close + override def onTimeout = disconnect def isConnected: Boolean = this.connectionHandler.isConnected @@ -236,7 +239,7 @@ class MySQLConnection( val promise = Promise[QueryResult]() this.setQueryPromise(promise) this.connectionHandler.sendPreparedStatement(query, values) - + addTimeout(promise,configuration.queryTimeout) promise.future } diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala index 83791366..273e76af 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/pool/MySQLConnectionFactory.scala @@ -21,9 +21,8 @@ import com.github.mauricio.async.db.pool.ObjectFactory import com.github.mauricio.async.db.mysql.MySQLConnection import scala.util.Try import scala.concurrent.Await -import scala.concurrent.duration._ import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.exceptions.{ConnectionStillRunningQueryException, ConnectionNotConnectedException} +import com.github.mauricio.async.db.exceptions.{ConnectionTimeoutedException, ConnectionStillRunningQueryException, ConnectionNotConnectedException} object MySQLConnectionFactory { final val log = Log.get[MySQLConnectionFactory] @@ -90,7 +89,9 @@ class MySQLConnectionFactory( configuration : Configuration ) extends ObjectFact */ def validate(item: MySQLConnection): Try[MySQLConnection] = { Try{ - + if ( item.isTimeouted ) { + throw new ConnectionTimeoutedException(item) + } if ( !item.isConnected ) { throw new ConnectionNotConnectedException(item) } diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala index 771fe1e3..8ace95e7 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/ConnectionHelper.scala @@ -115,6 +115,19 @@ trait ConnectionHelper { } + def withConfigurablePool[T]( configuration : Configuration )( fn : (ConnectionPool[MySQLConnection]) => T ) : T = { + + val factory = new MySQLConnectionFactory(configuration) + val pool = new ConnectionPool[MySQLConnection](factory, PoolConfiguration.Default) + + try { + fn(pool) + } finally { + awaitFuture( pool.close ) + } + + } + def withConnection[T]( fn : (MySQLConnection) => T ) : T = withConfigurableConnection(this.defaultConfiguration)(fn) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.scala new file mode 100644 index 00000000..09324c40 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.scala @@ -0,0 +1,80 @@ +/* + * 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.mysql + +import java.util.concurrent.TimeoutException +import com.github.mauricio.async.db.Configuration +import org.specs2.execute.{AsResult, Success, ResultExecution} +import org.specs2.mutable.Specification +import scala.concurrent.Await +import scala.concurrent.duration._ + +class QueryTimeoutSpec extends Specification with ConnectionHelper { + implicit def unitAsResult: AsResult[Unit] = new AsResult[Unit] { + def asResult(r: =>Unit) = + ResultExecution.execute(r)(_ => Success()) + } + "Simple query with 1 nanosec timeout" in { + withConfigurablePool(shortTimeoutConfiguration) { + pool => { + val connection = Await.result(pool.take, Duration(10,SECONDS)) + connection.isTimeouted === false + connection.isConnected === true + val queryResultFuture = connection.sendQuery("select sleep(1)") + Await.result(queryResultFuture, Duration(10,SECONDS)) must throwA[TimeoutException]() + connection.isTimeouted === true + Await.ready(pool.giveBack(connection), Duration(10,SECONDS)) + pool.availables.count(_ == connection) === 0 // connection removed from pool + // we do not know when the connection will be closed. + } + } + } + + "Simple query with 5 sec timeout" in { + withConfigurablePool(longTimeoutConfiguration) { + pool => { + val connection = Await.result(pool.take, Duration(10,SECONDS)) + connection.isTimeouted === false + connection.isConnected === true + val queryResultFuture = connection.sendQuery("select sleep(1)") + Await.result(queryResultFuture, Duration(10,SECONDS)).rows.get.size === 1 + connection.isTimeouted === false + connection.isConnected === true + Await.ready(pool.giveBack(connection), Duration(10,SECONDS)) + pool.availables.count(_ == connection) === 1 // connection returned to pool + } + } + } + + def shortTimeoutConfiguration = new Configuration( + "mysql_async", + "localhost", + port = 3306, + password = Some("root"), + database = Some("mysql_async_tests"), + queryTimeout = Duration(1,NANOSECONDS) + ) + + def longTimeoutConfiguration = new Configuration( + "mysql_async", + "localhost", + port = 3306, + password = Some("root"), + database = Some("mysql_async_tests"), + queryTimeout = Duration(5,SECONDS) + ) +} 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 45d53901..3cf5c2b3 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 @@ -20,6 +20,7 @@ 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.general.MutableResultSet +import com.github.mauricio.async.db.pool.TimeoutScheduler import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler} import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRegistry, PostgreSQLColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ @@ -48,7 +49,8 @@ class PostgreSQLConnection executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends PostgreSQLConnectionDelegate - with Connection { + with Connection + with TimeoutScheduler { import PostgreSQLConnection._ @@ -63,7 +65,7 @@ class PostgreSQLConnection private final val currentCount = Counter.incrementAndGet() private final val preparedStatementsCounter = new AtomicInteger() - private final implicit val internalExecutionContext = executionContext + override implicit val internalPool = executionContext private val parameterStatus = new scala.collection.mutable.HashMap[String, String]() private val parsedStatements = new scala.collection.mutable.HashMap[String, PreparedStatementHolder]() @@ -91,6 +93,7 @@ class PostgreSQLConnection } override def disconnect: Future[Connection] = this.connectionHandler.disconnect.map( c => this ) + override def onTimeout = disconnect override def isConnected: Boolean = this.connectionHandler.isConnected @@ -103,7 +106,7 @@ class PostgreSQLConnection this.setQueryPromise(promise) write(new QueryMessage(query)) - + addTimeout(promise,configuration.queryTimeout) promise.future } @@ -130,7 +133,7 @@ class PostgreSQLConnection holder.prepared = true new PreparedStatementOpeningMessage(holder.statementId, holder.realQuery, values, this.encoderRegistry) }) - + addTimeout(promise,configuration.queryTimeout) promise.future } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala index 62bcfd1a..ae3c5255 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/pool/PostgreSQLConnectionFactory.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.pool import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.exceptions.ConnectionTimeoutedException import com.github.mauricio.async.db.pool.ObjectFactory import com.github.mauricio.async.db.postgresql.PostgreSQLConnection import com.github.mauricio.async.db.util.Log @@ -69,6 +70,9 @@ class PostgreSQLConnectionFactory( def validate( item : PostgreSQLConnection ) : Try[PostgreSQLConnection] = { Try { + if ( item.isTimeouted ) { + throw new ConnectionTimeoutedException(item) + } if ( !item.isConnected || item.hasRecentError ) { throw new ClosedChannelException() } From 01d7c70442b78368bfafab018b4fe155166a5838 Mon Sep 17 00:00:00 2001 From: lifey Date: Tue, 28 Jul 2015 23:49:24 +0300 Subject: [PATCH 313/357] fix PartitionedAsyncObjectPoolSpec factory can't return the same int twice to two different partitions --- .../async/db/pool/PartitionedAsyncObjectPoolSpec.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala index 3b84755d..51d58fb0 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/PartitionedAsyncObjectPoolSpec.scala @@ -1,5 +1,7 @@ package com.github.mauricio.async.db.pool +import java.util.concurrent.atomic.AtomicInteger + import org.specs2.mutable.Specification import scala.util.Try import scala.concurrent.Await @@ -17,17 +19,16 @@ class PartitionedAsyncObjectPoolSpec extends SpecificationWithJUnit { val config = PoolConfiguration(100, Long.MaxValue, 100, Int.MaxValue) - + private var current = new AtomicInteger val factory = new ObjectFactory[Int] { var reject = Set[Int]() var failCreate = false - private var current = 0 + def create = if (failCreate) throw new IllegalStateException else { - current += 1 - current + current.incrementAndGet() } def destroy(item: Int) = {} def validate(item: Int) = From 2c18a425c7b1d2d87d3b6530a0c1d48bfeb192cb Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 4 Aug 2015 19:03:30 -0300 Subject: [PATCH 314/357] Using option for the timeout, letting connections declare their event loops and execution contexts --- .../mauricio/async/db/Configuration.scala | 11 ++-- .../async/db/pool/TimeoutScheduler.scala | 50 ++++++++++++++----- .../async/db/pool/DummyTimeoutScheduler.scala | 5 +- .../async/db/pool/TimeoutSchedulerSpec.scala | 8 ++- .../async/db/mysql/MySQLConnection.scala | 6 +-- .../async/db/mysql/QueryTimeoutSpec.scala | 4 +- .../db/postgresql/PostgreSQLConnection.scala | 4 +- .../postgresql/PostgreSQLConnectionSpec.scala | 10 ++-- 8 files changed, 63 insertions(+), 35 deletions(-) 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 089a634b..841999e1 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 @@ -18,7 +18,7 @@ package com.github.mauricio.async.db import java.nio.charset.Charset -import io.netty.buffer.{AbstractByteBufAllocator, PooledByteBufAllocator} +import io.netty.buffer.{ByteBufAllocator, PooledByteBufAllocator} import io.netty.util.CharsetUtil import scala.concurrent.duration._ @@ -43,6 +43,11 @@ object Configuration { * OOM or eternal loop attacks the client could have, defaults to 16 MB. You can set this * to any value you would like but again, make sure you know what you are doing if you do * change it. + * @param allocator the netty buffer allocator to be used + * @param connectTimeout the timeout for connecting to servers + * @param testTimeout the timeout for connection tests performed by pools + * @param queryTimeout the optional query timeout + * */ case class Configuration(username: String, @@ -52,7 +57,7 @@ case class Configuration(username: String, database: Option[String] = None, charset: Charset = Configuration.DefaultCharset, maximumMessageSize: Int = 16777216, - allocator: AbstractByteBufAllocator = PooledByteBufAllocator.DEFAULT, + allocator: ByteBufAllocator = PooledByteBufAllocator.DEFAULT, connectTimeout: Duration = 5.seconds, testTimeout: Duration = 5.seconds, - queryTimeout: Duration = Duration.Inf) + queryTimeout: Option[Duration] = None) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala index b2c2616b..d97a9ca1 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/TimeoutScheduler.scala @@ -2,20 +2,47 @@ package com.github.mauricio.async.db.pool import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.{TimeUnit, TimeoutException, ScheduledFuture} -import com.github.mauricio.async.db.util.NettyUtils +import io.netty.channel.EventLoopGroup import scala.concurrent.{ExecutionContext, Promise} import scala.concurrent.duration.Duration trait TimeoutScheduler { - implicit val internalPool: ExecutionContext + + private var isTimeoutedBool = new AtomicBoolean(false) + + /** + * + * The event loop group to be used for scheduling. + * + * @return + */ + + def eventLoopGroup : EventLoopGroup + + /** + * Implementors should decide here what they want to do when a timeout occur + */ + def onTimeout // implementors should decide here what they want to do when a timeout occur - private var isTimeoutedBool = new AtomicBoolean(false); - def isTimeouted = isTimeoutedBool.get // We need this property as isClosed takes time to complete and - // we don't want the connection to be used again. - def addTimeout[A](promise: Promise[A], duration: Duration) : Option[ScheduledFuture[_]] = { - if (duration != Duration.Inf) { - val scheduledFuture = schedule( + /** + * + * We need this property as isClosed takes time to complete and + * we don't want the connection to be used again. + * + * @return + */ + + def isTimeouted : Boolean = + isTimeoutedBool.get + + def addTimeout[A]( + promise: Promise[A], + durationOption: Option[Duration]) + (implicit executionContext : ExecutionContext) : Option[ScheduledFuture[_]] = { + durationOption.map { + duration => + val scheduledFuture = schedule( { if (promise.tryFailure(new TimeoutException(s"Operation is timeouted after it took too long to return (${duration})"))) { isTimeoutedBool.set(true) @@ -23,15 +50,14 @@ trait TimeoutScheduler { } }, duration) - promise.future.onComplete(x => scheduledFuture.cancel(false)) + promise.future.onComplete(x => scheduledFuture.cancel(false)) - return Some(scheduledFuture) + scheduledFuture } - return None } def schedule(block: => Unit, duration: Duration) : ScheduledFuture[_] = - NettyUtils.DefaultEventLoopGroup.schedule(new Runnable { + eventLoopGroup.schedule(new Runnable { override def run(): Unit = block }, duration.toMillis, TimeUnit.MILLISECONDS) } diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala index 302ac321..6935259e 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/DummyTimeoutScheduler.scala @@ -17,7 +17,9 @@ package com.github.mauricio.async.db.pool import java.util.concurrent.atomic.AtomicInteger -import com.github.mauricio.async.db.util.ExecutorServiceUtils +import com.github.mauricio.async.db.util.{NettyUtils, ExecutorServiceUtils} +import io.netty.channel.EventLoopGroup + /** * Implementation of TimeoutScheduler used for testing */ @@ -26,4 +28,5 @@ class DummyTimeoutScheduler extends TimeoutScheduler { private val timeOuts = new AtomicInteger override def onTimeout = timeOuts.incrementAndGet def timeoutCount = timeOuts.get() + def eventLoopGroup : EventLoopGroup = NettyUtils.DefaultEventLoopGroup } 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 46f20866..acc952e7 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 @@ -28,11 +28,10 @@ class TimeoutSchedulerSpec extends SpecificationWithJUnit { val TIMEOUT_DID_NOT_PASS = "timeout did not pass" - "test timeout did not pass" in { val timeoutScheduler = new DummyTimeoutScheduler() val promise = Promise[String]() - val scheduledFuture = timeoutScheduler.addTimeout(promise,Duration(1000, MILLISECONDS)) + val scheduledFuture = timeoutScheduler.addTimeout(promise,Some(Duration(1000, MILLISECONDS))) Thread.sleep(100); promise.isCompleted === false promise.success(TIMEOUT_DID_NOT_PASS) @@ -46,7 +45,7 @@ class TimeoutSchedulerSpec extends SpecificationWithJUnit { val timeoutMillis = 100 val promise = Promise[String]() val timeoutScheduler = new DummyTimeoutScheduler() - val scheduledFuture = timeoutScheduler.addTimeout(promise,Duration(timeoutMillis, MILLISECONDS)) + val scheduledFuture = timeoutScheduler.addTimeout(promise,Some(Duration(timeoutMillis, MILLISECONDS))) Thread.sleep(1000) promise.isCompleted === true scheduledFuture.get.isCancelled === false @@ -55,11 +54,10 @@ class TimeoutSchedulerSpec extends SpecificationWithJUnit { promise.future.value.get.get must throwA[TimeoutException](message = s"Operation is timeouted after it took too long to return \\(${timeoutMillis} milliseconds\\)") } - "test no timeout" in { val timeoutScheduler = new DummyTimeoutScheduler() val promise = Promise[String]() - val scheduledFuture = timeoutScheduler.addTimeout(promise,Duration.Inf) + val scheduledFuture = timeoutScheduler.addTimeout(promise,None) Thread.sleep(1000) scheduledFuture === None promise.isCompleted === false diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala index f4e7cc60..cb4a85b0 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/MySQLConnection.scala @@ -43,7 +43,7 @@ class MySQLConnection( configuration: Configuration, charsetMapper: CharsetMapper = CharsetMapper.Instance, group : EventLoopGroup = NettyUtils.DefaultEventLoopGroup, - executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext + implicit val executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends MySQLHandlerDelegate with Connection @@ -55,10 +55,8 @@ class MySQLConnection( // validate that this charset is supported charsetMapper.toInt(configuration.charset) - private final val connectionCount = MySQLConnection.Counter.incrementAndGet() private final val connectionId = s"[mysql-connection-$connectionCount]" - override implicit val internalPool = executionContext private final val connectionHandler = new MySQLConnectionHandler( configuration, @@ -80,6 +78,8 @@ class MySQLConnection( def lastException : Throwable = this._lastException def count : Long = this.connectionCount + override def eventLoopGroup : EventLoopGroup = group + def connect: Future[Connection] = { this.connectionHandler.connect.onFailure { case e => this.connectionPromise.tryFailure(e) diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.scala index 09324c40..65827432 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/QueryTimeoutSpec.scala @@ -66,7 +66,7 @@ class QueryTimeoutSpec extends Specification with ConnectionHelper { port = 3306, password = Some("root"), database = Some("mysql_async_tests"), - queryTimeout = Duration(1,NANOSECONDS) + queryTimeout = Some(Duration(1,NANOSECONDS)) ) def longTimeoutConfiguration = new Configuration( @@ -75,6 +75,6 @@ class QueryTimeoutSpec extends Specification with ConnectionHelper { port = 3306, password = Some("root"), database = Some("mysql_async_tests"), - queryTimeout = Duration(5,SECONDS) + queryTimeout = Some(Duration(5,SECONDS)) ) } 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 3cf5c2b3..8c58076b 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 @@ -46,7 +46,7 @@ class PostgreSQLConnection encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance, group : EventLoopGroup = NettyUtils.DefaultEventLoopGroup, - executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext + implicit val executionContext : ExecutionContext = ExecutorServiceUtils.CachedExecutionContext ) extends PostgreSQLConnectionDelegate with Connection @@ -65,7 +65,6 @@ class PostgreSQLConnection private final val currentCount = Counter.incrementAndGet() private final val preparedStatementsCounter = new AtomicInteger() - override implicit val internalPool = executionContext private val parameterStatus = new scala.collection.mutable.HashMap[String, String]() private val parsedStatements = new scala.collection.mutable.HashMap[String, PreparedStatementHolder]() @@ -82,6 +81,7 @@ class PostgreSQLConnection private var queryResult: Option[QueryResult] = None + override def eventLoopGroup : EventLoopGroup = group def isReadyForQuery: Boolean = this.queryPromise.isEmpty def connect: Future[Connection] = { 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 c9876721..ac297226 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 @@ -285,16 +285,12 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { try { withHandler(configuration, { handler => - executeQuery(handler, "SELECT 0") - throw new IllegalStateException("should not have come here") + val result = executeQuery(handler, "SELECT 0") + throw new IllegalStateException("should not have arrived") }) } catch { - case e: GenericDatabaseException => { + case e: GenericDatabaseException => e.errorMessage.fields(InformationMessage.Routine) === "auth_failed" - } - case e: Exception => { - throw new IllegalStateException("should not have come here") - } } } From 7588e3c405ddc7911f446daa6ac1f968c64f1dc8 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Tue, 4 Aug 2015 19:07:54 -0300 Subject: [PATCH 315/357] Updating SBT and JDK versions --- .travis.yml | 10 ++++++++-- project/build.properties | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53e614cf..2c1a7a84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,19 @@ language: scala scala: - 2.10.4 - - 2.11.0 + - 2.11.7 jdk: - oraclejdk7 - - openjdk7 + - oraclejdk8 services: - postgresql - mysql +cache: + directories: + - vendor/bundle + - $HOME/.m2 + - $HOME/.ivy2 + - $HOME/.sbt before_script: - ./script/prepare_build.sh diff --git a/project/build.properties b/project/build.properties index 8ac605a3..d638b4f3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.2 +sbt.version = 0.13.8 \ No newline at end of file From ed4f06a205417c86f72bce368b3ee463f8b4544e Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 8 Aug 2015 08:46:34 -0300 Subject: [PATCH 316/357] Closing 0.2.18 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index dc1c2c8b..d0c3d454 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.18-SNAPSHOT" + val commonVersion = "0.2.18" val projectScalaVersion = "2.11.7" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" From 2f93fd1e1905ad8cca4039224c442013354bf2f2 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 8 Aug 2015 08:51:02 -0300 Subject: [PATCH 317/357] Starting next development cycle --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index d0c3d454..bb361625 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -45,7 +45,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.18" + val commonVersion = "0.2.19-SNAPSHOT" val projectScalaVersion = "2.11.7" val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" From 14e232ffd2be55b61296117d93b5f2270d54aebf Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 8 Aug 2015 09:38:15 -0300 Subject: [PATCH 318/357] Updated changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c80603d2..6ab0d079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,14 @@ # Changelog -## 0.2.17 - in progresss +## 0.2.18 - 2015-08-08 + +* Timeouts implemented queries for MySQL and PostgreSQL - @lifey - #147 + +## 0.2.17 - 2015-07-13 + +* Fixed pool leak issue - @haski +* Fixed date time formatting issue - #142 ## 0.2.16 - 2015-01-04 From 654a377596cc2f7694b0940d04a2cca1ece95f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Sun, 9 Aug 2015 10:40:37 -0300 Subject: [PATCH 319/357] Updating to latest version --- README.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.markdown b/README.markdown index e1e54004..42f549d8 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.15" +"com.github.mauricio" %% "postgresql-async" % "0.2.18" ``` Or Maven: @@ -63,14 +63,14 @@ Or Maven: com.github.mauricio postgresql-async_2.11 - 0.2.16 + 0.2.18 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.15" +"com.github.mauricio" %% "mysql-async" % "0.2.18" ``` Or Maven: @@ -79,7 +79,7 @@ Or Maven: com.github.mauricio mysql-async_2.11 - 0.2.16 + 0.2.18 ``` From 1c9b98d0ee3ec18a3fc68278c5dab175775e5ad8 Mon Sep 17 00:00:00 2001 From: Anton Zherdev Date: Mon, 10 Aug 2015 20:32:23 +1200 Subject: [PATCH 320/357] array row data refactoring. Avoiding copy data by columns. --- .../async/db/general/ArrayRowData.scala | 17 +---------------- .../async/db/general/MutableResultSet.scala | 10 ++-------- .../db/mysql/binary/BinaryRowDecoder.scala | 4 ++-- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala index c232a12a..fe582481 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/ArrayRowData.scala @@ -17,14 +17,10 @@ package com.github.mauricio.async.db.general import com.github.mauricio.async.db.RowData -import scala.collection.mutable -class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] ) - extends RowData +class ArrayRowData(row : Int, val mapping : Map[String, Int], val columns : Array[Any]) extends RowData { - private val columns = new Array[Any](columnCount) - /** * * Returns a column value by it's position in the originating query. @@ -51,16 +47,5 @@ class ArrayRowData( columnCount : Int, row : Int, val mapping : Map[String, Int] */ def rowNumber: Int = row - /** - * - * Sets a value to a column in this collection. - * - * @param i - * @param x - */ - - def update(i: Int, x: Any) = columns(i) = x - def length: Int = columns.length - } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index 0422a4cf..603e7602 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -39,14 +39,8 @@ class MutableResultSet[T <: ColumnData]( override def apply(idx: Int): RowData = this.rows(idx) - def addRow( row : Seq[Any] ) { - val realRow = new ArrayRowData( columnTypes.size, this.rows.size, this.columnMapping ) - var x = 0 - while ( x < row.size ) { - realRow(x) = row(x) - x += 1 - } - this.rows += realRow + def addRow(row : Array[Any] ) { + this.rows += new ArrayRowData(this.rows.size, this.columnMapping, row) } } \ No newline at end of file diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala index 0f59ca5e..22c6cee5 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/binary/BinaryRowDecoder.scala @@ -31,7 +31,7 @@ class BinaryRowDecoder { //import BinaryRowDecoder._ - def decode(buffer: ByteBuf, columns: Seq[ColumnDefinitionMessage]): IndexedSeq[Any] = { + def decode(buffer: ByteBuf, columns: Seq[ColumnDefinitionMessage]): Array[Any] = { //log.debug("columns are {} - {}", buffer.readableBytes(), columns) //log.debug( "decoding row\n{}", MySQLHelper.dumpAsHex(buffer)) @@ -79,7 +79,7 @@ class BinaryRowDecoder { throw new BufferNotFullyConsumedException(buffer) } - row + row.toArray } } \ No newline at end of file From f39a2daf028899f9cf52d978dbe33f0d75476c29 Mon Sep 17 00:00:00 2001 From: Anton Zherdev Date: Mon, 10 Aug 2015 20:39:12 +1200 Subject: [PATCH 321/357] Fix package name for the test and removing unused imports --- .../db/postgresql/PostgreSQLConnectionSpec.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 ac297226..2843e95e 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 @@ -14,23 +14,22 @@ * under the License. */ -package com.github.mauricio.postgresql +package com.github.mauricio.async.db.postgresql import java.nio.ByteBuffer -import com.github.mauricio.async.db.column.{TimestampEncoderDecoder, TimeEncoderDecoder, DateEncoderDecoder} +import com.github.mauricio.async.db.column.{DateEncoderDecoder, TimeEncoderDecoder, TimestampEncoderDecoder} import com.github.mauricio.async.db.exceptions.UnsupportedAuthenticationMethodException -import com.github.mauricio.async.db.postgresql.exceptions.{QueryMustNotBeNullOrEmptyException, GenericDatabaseException} +import com.github.mauricio.async.db.postgresql.exceptions.{GenericDatabaseException, QueryMustNotBeNullOrEmptyException} import com.github.mauricio.async.db.postgresql.messages.backend.InformationMessage -import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} import com.github.mauricio.async.db.util.Log -import com.github.mauricio.async.db.{Configuration, QueryResult, Connection} +import com.github.mauricio.async.db.{Configuration, Connection, QueryResult} import io.netty.buffer.Unpooled -import concurrent.{Future, Await} +import org.joda.time.LocalDateTime import org.specs2.mutable.Specification -import scala.concurrent.ExecutionContext.Implicits.global + import scala.concurrent.duration._ -import org.joda.time.LocalDateTime +import scala.concurrent.{Await, Future} object PostgreSQLConnectionSpec { val log = Log.get[PostgreSQLConnectionSpec] From e5e725f82b68d9317c34f240d9a57dd4a3aa7d2b Mon Sep 17 00:00:00 2001 From: Indraneel Mukherjee Date: Wed, 26 Aug 2015 13:44:48 -0700 Subject: [PATCH 322/357] Added types field to MutableResultSet. This will allow inferring the types of RowData fields, which can be useful in different situations, e.g. converting the results to json format --- .../github/mauricio/async/db/general/MutableResultSet.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala index 603e7602..00cc712b 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/general/MutableResultSet.scala @@ -31,10 +31,12 @@ class MutableResultSet[T <: ColumnData]( private val columnMapping: Map[String, Int] = this.columnTypes.indices.map( index => ( this.columnTypes(index).name, index ) ).toMap - + val columnNames : IndexedSeq[String] = this.columnTypes.map(c => c.name) + val types : IndexedSeq[Int] = this.columnTypes.map(c => c.dataType) + override def length: Int = this.rows.length override def apply(idx: Int): RowData = this.rows(idx) @@ -43,4 +45,4 @@ class MutableResultSet[T <: ColumnData]( this.rows += new ArrayRowData(this.rows.size, this.columnMapping, row) } -} \ No newline at end of file +} From 95a6a55d30398b8085fe21a2d2d9281e4e0e1348 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Fri, 27 Nov 2015 09:00:00 -0800 Subject: [PATCH 323/357] Update README.markdown --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 42f549d8..73302b6c 100644 --- a/README.markdown +++ b/README.markdown @@ -48,6 +48,7 @@ You can view the project's [CHANGELOG here](CHANGELOG.md). the driver into a vert.x application; * [dbmapper](https://github.com/njeuk/dbmapper) - enables SQL queries with automatic mapping from the database table to the Scala class and a mechanism to create a Table Date Gateway model with very little boiler plate code; +* [Quill](http://getquill.io) - A compile-time language integrated query library for Scala. ## Include them as dependencies From 577b6a87842b34a13ce38bab21055045d0694fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=83=20pitr?= Date: Mon, 14 Dec 2015 16:48:49 -0500 Subject: [PATCH 324/357] Switch to LIFO for object pool strategy Switch to use a stack datastructure to store connections. Current implementation uses FIFO strategy, which prevents unused connections from being closed because object pool goes through all connections. With LIFO strategy, unused connections will stay at the bottom of the stack and will be cleaned up during `testObjects` call. Additionally, declare `waitQueue` as `Queue` for clarity. --- .../db/pool/SingleThreadedAsyncObjectPool.scala | 16 ++++++++-------- 1 file changed, 8 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 84387cb0..2b2e28d9 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 @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.pool import com.github.mauricio.async.db.util.{Log, Worker} import java.util.concurrent.atomic.AtomicLong import java.util.{TimerTask, Timer} -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.{ArrayBuffer, Queue, Stack} import scala.concurrent.{Promise, Future} import scala.util.{Failure, Success} @@ -49,9 +49,9 @@ class SingleThreadedAsyncObjectPool[T]( import SingleThreadedAsyncObjectPool.{Counter, log} private val mainPool = Worker() - private val poolables = new ArrayBuffer[PoolableHolder[T]](configuration.maxObjects) + private var poolables = new Stack[PoolableHolder[T]]() private val checkouts = new ArrayBuffer[T](configuration.maxObjects) - private val waitQueue = new ArrayBuffer[Promise[T]](configuration.maxQueueSize) + private val waitQueue = new Queue[Promise[T]]() private val timer = new Timer("async-object-pool-timer-" + Counter.incrementAndGet(), true) timer.scheduleAtFixedRate(new TimerTask { def run() { @@ -150,10 +150,10 @@ class SingleThreadedAsyncObjectPool[T]( */ private def addBack(item: T, promise: Promise[AsyncObjectPool[T]]) { - this.poolables += new PoolableHolder[T](item) + this.poolables.push(new PoolableHolder[T](item)) - if (!this.waitQueue.isEmpty) { - this.checkout(this.waitQueue.remove(0)) + if (this.waitQueue.nonEmpty) { + this.checkout(this.waitQueue.dequeue()) } promise.success(this) @@ -205,7 +205,7 @@ class SingleThreadedAsyncObjectPool[T]( case e: Exception => promise.failure(e) } } else { - val item = this.poolables.remove(0).item + val item = this.poolables.pop().item this.checkouts += item promise.success(item) } @@ -241,7 +241,7 @@ class SingleThreadedAsyncObjectPool[T]( } } } - this.poolables --= removals + this.poolables = this.poolables.diff(removals) } private class PoolableHolder[T](val item: T) { From 761ed938bae8558014f051387010ef4a93ac0a80 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 26 Jan 2016 05:02:10 +0900 Subject: [PATCH 325/357] disable publish settings of root project --- project/Build.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index bb361625..4b9c879e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -10,7 +10,11 @@ object ProjectBuild extends Build { lazy val root = Project( id = "db-async-base", base = file("."), - settings = Configuration.baseSettings, + settings = Configuration.baseSettings ++ Seq( + publish := (), + publishLocal := (), + publishArtifact := false + ), aggregate = Seq(common, postgresql, mysql) ) From b96aaf163e6ce757e722e95763a9dbc6f90211d5 Mon Sep 17 00:00:00 2001 From: Joern Bernhardt Date: Thu, 28 Jan 2016 01:48:42 +0100 Subject: [PATCH 326/357] Add test to show issue with numeric columns Signed-off-by: Joern Bernhardt --- .../async/db/postgresql/NumericSpec.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala new file mode 100644 index 00000000..26c13f4d --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala @@ -0,0 +1,57 @@ +package com.github.mauricio.async.db.postgresql + +import org.specs2.mutable.Specification + +class NumericSpec extends Specification with DatabaseTestHelper { + + "when processing numeric columns" should { + + "support first update of num column with floating" in { + + withHandler { + handler => + executeDdl(handler, "CREATE TEMP TABLE numeric_test (id BIGSERIAL, numcol NUMERIC)") + + val id = executePreparedStatement(handler, "INSERT INTO numeric_test DEFAULT VALUES RETURNING id").rows.get(0)("id") + executePreparedStatement(handler, "UPDATE numeric_test SET numcol = ? WHERE id = ?", Array[Any](123.123, id)) + executePreparedStatement(handler, "UPDATE numeric_test SET numcol = ? WHERE id = ?", Array[Any](1234, id)) + executePreparedStatement(handler, "UPDATE numeric_test SET numcol = ? WHERE id = ?", Array[Any](123.123, id)) + + id === 1 + } + + } + + "support first update of num column with integer (fails currently)" in { + + withHandler { + handler => + executeDdl(handler, "CREATE TEMP TABLE numeric_test (id BIGSERIAL, numcol NUMERIC)") + + val id = executePreparedStatement(handler, "INSERT INTO numeric_test DEFAULT VALUES RETURNING id").rows.get(0)("id") + executePreparedStatement(handler, "UPDATE numeric_test SET numcol = ? WHERE id = ?", Array[Any](1234, id)) + executePreparedStatement(handler, "UPDATE numeric_test SET numcol = ? WHERE id = ?", Array[Any](123.123, id)) + + id === 1 + } + + } + + "support using first update with queries instead of prepared statements" in { + + withHandler { + handler => + executeDdl(handler, "CREATE TEMP TABLE numeric_test (id BIGSERIAL, numcol NUMERIC)") + + val id = executeQuery(handler, "INSERT INTO numeric_test DEFAULT VALUES RETURNING id").rows.get(0)("id") + executeQuery(handler, s"UPDATE numeric_test SET numcol = 1234 WHERE id = $id") + executeQuery(handler, s"UPDATE numeric_test SET numcol = 123.123 WHERE id = $id") + + id === 1 + } + + } + + } + +} From 5cf8b666e3e49762cbddfbd26534d08f0201cfde Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 5 Mar 2016 13:28:31 -0500 Subject: [PATCH 327/357] Upgrading some of the dependent libraries --- .gitignore | 1 + Procfile | 2 +- project/Build.scala | 14 +++++++------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 1aaf8978..c83ec207 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ mysql-async/target/* *.iml .project .vagrant/* +vendor/* diff --git a/Procfile b/Procfile index 1288bcfe..13e2e8fd 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -postgresql: postgres -D databases/postgresql +postgresql: postgres -D vendor/postgresql mysql: mysqld --log-warnings --console \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index 4b9c879e..aad5b051 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -52,14 +52,14 @@ object Configuration { val commonVersion = "0.2.19-SNAPSHOT" val projectScalaVersion = "2.11.7" - val specs2Dependency = "org.specs2" %% "specs2" % "2.3.11" % "test" - val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.1.3" % "test" + val specs2Dependency = "org.specs2" %% "specs2" % "2.5" % "test" + val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.1.6" % "test" val commonDependencies = Seq( - "org.slf4j" % "slf4j-api" % "1.7.12", - "joda-time" % "joda-time" % "2.3", - "org.joda" % "joda-convert" % "1.5", - "io.netty" % "netty-all" % "4.0.29.Final", + "org.slf4j" % "slf4j-api" % "1.7.18", + "joda-time" % "joda-time" % "2.9.2", + "org.joda" % "joda-convert" % "1.8.1", + "io.netty" % "netty-all" % "4.0.34.Final", "org.javassist" % "javassist" % "3.20.0-GA", specs2Dependency, logbackDependency @@ -78,7 +78,7 @@ object Configuration { :+ "-feature" , 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.4"), + crossScalaVersions := Seq(projectScalaVersion, "2.10.6"), javacOptions := Seq("-source", "1.6", "-target", "1.6", "-encoding", "UTF8"), organization := "com.github.mauricio", version := commonVersion, From 90e4194adb2d02b229fcccdc754513e486518bb7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 5 Mar 2016 16:44:35 -0500 Subject: [PATCH 328/357] Fixing issue with NUMERIC columns and mixing integer and floating point numbers - fixes #169 --- .../PostgreSQLColumnEncoderRegistry.scala | 20 +++++++------- .../async/db/postgresql/NumericSpec.scala | 2 +- .../async/db/postgresql/TimeAndDateSpec.scala | 27 +++++++++++++++---- 3 files changed, 33 insertions(+), 16 deletions(-) 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 24641336..5292839c 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 @@ -31,23 +31,23 @@ object PostgreSQLColumnEncoderRegistry { class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { private val classesSequence_ : List[(Class[_], (ColumnEncoder, Int))] = List( - classOf[Int] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), - classOf[java.lang.Integer] -> (IntegerEncoderDecoder -> ColumnTypes.Integer), + classOf[Int] -> (IntegerEncoderDecoder -> ColumnTypes.Numeric), + classOf[java.lang.Integer] -> (IntegerEncoderDecoder -> ColumnTypes.Numeric), - classOf[java.lang.Short] -> (ShortEncoderDecoder -> ColumnTypes.Smallint), - classOf[Short] -> (ShortEncoderDecoder -> ColumnTypes.Smallint), + classOf[java.lang.Short] -> (ShortEncoderDecoder -> ColumnTypes.Numeric), + classOf[Short] -> (ShortEncoderDecoder -> ColumnTypes.Numeric), - classOf[Long] -> (LongEncoderDecoder -> ColumnTypes.Bigserial), - classOf[java.lang.Long] -> (LongEncoderDecoder -> ColumnTypes.Bigserial), + classOf[Long] -> (LongEncoderDecoder -> ColumnTypes.Numeric), + classOf[java.lang.Long] -> (LongEncoderDecoder -> ColumnTypes.Numeric), classOf[String] -> (StringEncoderDecoder -> ColumnTypes.Varchar), classOf[java.lang.String] -> (StringEncoderDecoder -> ColumnTypes.Varchar), - classOf[Float] -> (FloatEncoderDecoder -> ColumnTypes.Real), - classOf[java.lang.Float] -> (FloatEncoderDecoder -> ColumnTypes.Real), + classOf[Float] -> (FloatEncoderDecoder -> ColumnTypes.Numeric), + classOf[java.lang.Float] -> (FloatEncoderDecoder -> ColumnTypes.Numeric), - classOf[Double] -> (DoubleEncoderDecoder -> ColumnTypes.Double), - classOf[java.lang.Double] -> (DoubleEncoderDecoder -> ColumnTypes.Double), + classOf[Double] -> (DoubleEncoderDecoder -> ColumnTypes.Numeric), + classOf[java.lang.Double] -> (DoubleEncoderDecoder -> ColumnTypes.Numeric), classOf[BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), classOf[java.math.BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala index 26c13f4d..ad38a64e 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/NumericSpec.scala @@ -22,7 +22,7 @@ class NumericSpec extends Specification with DatabaseTestHelper { } - "support first update of num column with integer (fails currently)" in { + "support first update of num column with integer" in { withHandler { handler => diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala index 67e7b877..03703f21 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/TimeAndDateSpec.scala @@ -188,14 +188,31 @@ class TimeAndDateSpec extends Specification with DatabaseTestHelper { withTimeHandler { conn => - val date1 = new DateTime(2190319) + val date = new DateTime(2190319) - await(conn.sendPreparedStatement("CREATE TEMP TABLE TEST(T TIMESTAMP)")) - await(conn.sendPreparedStatement("INSERT INTO TEST(T) VALUES(?)", Seq(date1))) - val result = await(conn.sendPreparedStatement("SELECT T FROM TEST")) + executePreparedStatement(conn, "CREATE TEMP TABLE TEST(T TIMESTAMP)") + executePreparedStatement(conn, "INSERT INTO TEST(T) VALUES(?)", Array(date)) + val result = executePreparedStatement(conn, "SELECT T FROM TEST") val date2 = result.rows.get.head(0) + date2 === date.toDateTime(DateTimeZone.UTC).toLocalDateTime + } + + } + + "supports sending a local date and later a date time object for the same field" in { + + withTimeHandler { + conn => + val date = new LocalDate(2016, 3, 5) + + executePreparedStatement(conn, "CREATE TEMP TABLE TEST(T TIMESTAMP)") + executePreparedStatement(conn, "INSERT INTO TEST(T) VALUES(?)", Array(date)) + val result = executePreparedStatement(conn, "SELECT T FROM TEST WHERE T = ?", Array(date)) + result.rows.get.size === 1 - date2 === date1.toDateTime(DateTimeZone.UTC).toLocalDateTime + val dateTime = new LocalDateTime(2016, 3, 5, 0, 0, 0, 0) + val dateTimeResult = executePreparedStatement(conn, "SELECT T FROM TEST WHERE T = ?", Array(dateTime)) + dateTimeResult.rows.get.size === 1 } } From d42a303874b6d3ad840027fbe4aec4d0fb3c8396 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 5 Mar 2016 17:04:42 -0500 Subject: [PATCH 329/357] Include null test for PostgreSQL dates --- .../db/postgresql/PreparedStatementSpec.scala | 34 +++++++++++++++++++ project/plugins.sbt | 2 ++ 2 files changed, 36 insertions(+) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala index 6fd7d9a6..660c1411 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PreparedStatementSpec.scala @@ -40,6 +40,7 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { val messagesInsertReverted = s"INSERT INTO messages $filler (moment,content) VALUES (?,?) RETURNING id" val messagesUpdate = "UPDATE messages SET content = ?, moment = ? WHERE id = ?" val messagesSelectOne = "SELECT id, content, moment FROM messages WHERE id = ?" + val messagesSelectByMoment = "SELECT id, content, moment FROM messages WHERE moment = ?" val messagesSelectAll = "SELECT id, content, moment FROM messages" val messagesSelectEscaped = "SELECT id, content, moment FROM messages WHERE content LIKE '%??%' AND id > ?" @@ -163,7 +164,40 @@ class PreparedStatementSpec extends Specification with DatabaseTestHelper { rows(1)("id") === 2 rows(1)("content") === secondContent rows(1)("moment") === date + } + } + + "supports sending null first and then an actual value for the fields" in { + withHandler { + handler => + + val firstContent = "Some Moment" + val secondContent = "Some Other Moment" + val date = LocalDate.now() + + executeDdl(handler, this.messagesCreate) + executePreparedStatement(handler, this.messagesInsert, Array(firstContent, null)) + executePreparedStatement(handler, this.messagesInsert, Array(secondContent, date)) + + val rows = executePreparedStatement(handler, this.messagesSelectByMoment, Array(null)).rows.get + rows.size === 0 + + /* + PostgreSQL does not know how to handle NULL parameters for a query in a prepared statement, + you have to use IS NULL if you want to make use of it. + + rows.length === 1 + + rows(0)("id") === 1 + rows(0)("content") === firstContent + rows(0)("moment") === null + */ + val rowsWithoutNull = executePreparedStatement(handler, this.messagesSelectByMoment, Array(date)).rows.get + rowsWithoutNull.size === 1 + rowsWithoutNull(0)("id") === 2 + rowsWithoutNull(0)("content") === secondContent + rowsWithoutNull(0)("moment") === date } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 1e87e1c8..4528f2d6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,3 +3,5 @@ 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") + +resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" From 3408711d24b590c08e863e9dd70f54e96e24083a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 5 Mar 2016 17:13:46 -0500 Subject: [PATCH 330/357] Change specs2 dependency to specs2 core only --- project/Build.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index aad5b051..ed2024bb 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -51,8 +51,9 @@ object Configuration { val commonVersion = "0.2.19-SNAPSHOT" val projectScalaVersion = "2.11.7" + val specs2Version = "2.5" - val specs2Dependency = "org.specs2" %% "specs2" % "2.5" % "test" + val specs2Dependency = "org.specs2" %% "specs2-core" % specs2Version % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.1.6" % "test" val commonDependencies = Seq( From c3747b5ab8a98de2ecdd45fae1704117ce081173 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 5 Mar 2016 17:38:52 -0500 Subject: [PATCH 331/357] Include BitSpec from @narigo --- .../mauricio/async/db/mysql/BitSpec.scala | 83 +++++++++++++++++++ .../async/db/postgresql/BitSpec.scala | 83 +++++++++++++++++++ project/Build.scala | 2 + 3 files changed, 168 insertions(+) create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BitSpec.scala create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/BitSpec.scala diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BitSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BitSpec.scala new file mode 100644 index 00000000..ade3e6ce --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/BitSpec.scala @@ -0,0 +1,83 @@ +/* + * 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.mysql + +import org.specs2.mutable.Specification + +class BitSpec extends Specification with ConnectionHelper { + + "when processing bit columns" should { + + "result in binary data" in { + + withConnection { + connection => + val create = """CREATE TEMPORARY TABLE binary_test + ( + id INT NOT NULL AUTO_INCREMENT, + some_bit BIT(1) NOT NULL, + PRIMARY KEY (id) + )""" + + executeQuery(connection, create) + executePreparedStatement(connection, + "INSERT INTO binary_test (some_bit) VALUES (B'0'),(B'1')") + + val rows = executePreparedStatement(connection, "select * from binary_test").rows.get + + val bit0 = rows(0)("some_bit") + val bit1 = rows(1)("some_bit") + + bit0 === Array(0) + bit1 === Array(1) + } + + } + + "result in binary data in BIT(2) column" in { + + withConnection { + connection => + val create = """CREATE TEMPORARY TABLE binary_test + ( + id INT NOT NULL AUTO_INCREMENT, + some_bit BIT(2) NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id) + )""" + + executeQuery(connection, create) + executePreparedStatement(connection, + "INSERT INTO binary_test (some_bit) VALUES (B'00'),(B'01'),(B'10'),(B'11')") + + val rows = executePreparedStatement(connection, "select * from binary_test").rows.get + + val bit0 = rows(0)("some_bit") + val bit1 = rows(1)("some_bit") + val bit2 = rows(2)("some_bit") + val bit3 = rows(3)("some_bit") + + bit0 === Array(0) + bit1 === Array(1) + bit2 === Array(2) + bit3 === Array(3) + } + + } + + } + +} \ No newline at end of file diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/BitSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/BitSpec.scala new file mode 100644 index 00000000..8c17f9af --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/BitSpec.scala @@ -0,0 +1,83 @@ +/* + * 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 + +import org.specs2.mutable.Specification + +class BitSpec extends Specification with DatabaseTestHelper { + + "when processing bit columns" should { + + "result in binary data" in { + + withHandler { + handler => + val create = """CREATE TEMP TABLE binary_test + ( + id bigserial NOT NULL, + some_bit BYTEA NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id) + )""" + + executeDdl(handler, create) + executePreparedStatement(handler, + "INSERT INTO binary_test (some_bit) VALUES (E'\\\\000'),(E'\\\\001')") + + val rows = executePreparedStatement(handler, "select * from binary_test").rows.get + + val bit0 = rows(0)("some_bit") + val bit1 = rows(1)("some_bit") + + bit0 === Array(0) + bit1 === Array(1) + } + + } + + "result in binary data in BIT(2) column" in { + + withHandler { + handler => + val create = """CREATE TEMP TABLE binary_test + ( + id bigserial NOT NULL, + some_bit BYTEA NOT NULL, + CONSTRAINT bigserial_column_pkey PRIMARY KEY (id) + )""" + + executeDdl(handler, create) + executePreparedStatement(handler, + "INSERT INTO binary_test (some_bit) VALUES (E'\\\\000'),(E'\\\\001'),(E'\\\\002'),(E'\\\\003')") + + val rows = executePreparedStatement(handler, "select * from binary_test").rows.get + + val bit0 = rows(0)("some_bit") + val bit1 = rows(1)("some_bit") + val bit2 = rows(2)("some_bit") + val bit3 = rows(3)("some_bit") + + bit0 === Array(0) + bit1 === Array(1) + bit2 === Array(2) + bit3 === Array(3) + } + + } + + } + +} \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index ed2024bb..a820fb76 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -54,6 +54,7 @@ object Configuration { val specs2Version = "2.5" val specs2Dependency = "org.specs2" %% "specs2-core" % specs2Version % "test" + val specs2JunitDependency = "org.specs2" %% "specs2-junit" % specs2Version % "test" val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.1.6" % "test" val commonDependencies = Seq( @@ -63,6 +64,7 @@ object Configuration { "io.netty" % "netty-all" % "4.0.34.Final", "org.javassist" % "javassist" % "3.20.0-GA", specs2Dependency, + specs2JunitDependency, logbackDependency ) From 0f9a587ec3a9cd372f0b37f069848201236d2acf Mon Sep 17 00:00:00 2001 From: Alex Dupre Date: Mon, 7 Mar 2016 09:13:09 +0100 Subject: [PATCH 332/357] Add SSL support.. SSL is disabled by default to avoid POLA violations. It is possible to enable and control SSL behavior via url parameters: - `sslmode=` enable ssl (prefer/require/verify-ca/verify-full [recommended]) - `sslrootcert=` specifies trusted certificates (JDK cacert if missing) Client certificate authentication is not implemented, due to lack of time and interest, but it should be easy to add. --- .../mauricio/async/db/Configuration.scala | 2 + .../mauricio/async/db/SSLConfiguration.scala | 31 ++++++++ .../db/postgresql/codec/MessageDecoder.scala | 12 ++- .../db/postgresql/codec/MessageEncoder.scala | 3 +- .../codec/PostgreSQLConnectionHandler.scala | 59 ++++++++++++++- .../encoders/SSLMessageEncoder.scala | 16 ++++ .../encoders/StartupMessageEncoder.scala | 6 +- .../messages/backend/SSLResponseMessage.scala | 3 + .../messages/backend/ServerMessage.scala | 1 - .../frontend/InitialClientMessage.scala | 3 + .../messages/frontend/SSLRequestMessage.scala | 5 ++ .../messages/frontend/StartupMessage.scala | 4 +- .../async/db/postgresql/util/ParserURL.scala | 25 +++++-- .../async/db/postgresql/util/URLParser.scala | 10 +-- .../db/postgresql/DatabaseTestHelper.scala | 14 +++- .../db/postgresql/MessageDecoderSpec.scala | 2 +- .../PostgreSQLSSLConnectionSpec.scala | 51 +++++++++++++ .../db/postgresql/util/URLParserSpec.scala | 20 ++++- script/prepare_build.sh | 43 +++++++---- script/server.crt | 75 +++++++++++++++++++ script/server.key | 27 +++++++ 21 files changed, 364 insertions(+), 48 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/SSLConfiguration.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/SSLMessageEncoder.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/SSLResponseMessage.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/InitialClientMessage.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/SSLRequestMessage.scala create mode 100644 postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLSSLConnectionSpec.scala create mode 100644 script/server.crt create mode 100644 script/server.key 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 841999e1..b032ac02 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 @@ -37,6 +37,7 @@ object Configuration { * @param port database port, defaults to 5432 * @param password password, defaults to no password * @param database database name, defaults to no database + * @param ssl ssl configuration * @param charset charset for the connection, defaults to UTF-8, make sure you know what you are doing if you * change this * @param maximumMessageSize the maximum size a message from the server could possibly have, this limits possible @@ -55,6 +56,7 @@ case class Configuration(username: String, port: Int = 5432, password: Option[String] = None, database: Option[String] = None, + ssl: SSLConfiguration = SSLConfiguration(), charset: Charset = Configuration.DefaultCharset, maximumMessageSize: Int = 16777216, allocator: ByteBufAllocator = PooledByteBufAllocator.DEFAULT, diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/SSLConfiguration.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/SSLConfiguration.scala new file mode 100644 index 00000000..9ae657fe --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/SSLConfiguration.scala @@ -0,0 +1,31 @@ +package com.github.mauricio.async.db + +import java.io.File + +import SSLConfiguration.Mode + +/** + * + * Contains the SSL configuration necessary to connect to a database. + * + * @param mode whether and with what priority a SSL connection will be negotiated, default disabled + * @param rootCert path to PEM encoded trusted root certificates, None to use internal JDK cacerts, defaults to None + * + */ +case class SSLConfiguration(mode: Mode.Value = Mode.Disable, rootCert: Option[java.io.File] = None) + +object SSLConfiguration { + + object Mode extends Enumeration { + val Disable = Value("disable") // only try a non-SSL connection + val Prefer = Value("prefer") // first try an SSL connection; if that fails, try a non-SSL connection + val Require = Value("require") // only try an SSL connection, but don't verify Certificate Authority + val VerifyCA = Value("verify-ca") // only try an SSL connection, and verify that the server certificate is issued by a trusted certificate authority (CA) + val VerifyFull = Value("verify-full") // only try an SSL connection, verify that the server certificate is issued by a trusted CA and that the server host name matches that in the certificate + } + + def apply(properties: Map[String, String]): SSLConfiguration = SSLConfiguration( + mode = Mode.withName(properties.get("sslmode").getOrElse("disable")), + rootCert = properties.get("sslrootcert").map(new File(_)) + ) +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala index 8a3d9fa5..5f210f72 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageDecoder.scala @@ -17,7 +17,7 @@ package com.github.mauricio.async.db.postgresql.codec import com.github.mauricio.async.db.postgresql.exceptions.{MessageTooLongException} -import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage +import com.github.mauricio.async.db.postgresql.messages.backend.{ServerMessage, SSLResponseMessage} import com.github.mauricio.async.db.postgresql.parsers.{AuthenticationStartupParser, MessageParsersRegistry} import com.github.mauricio.async.db.util.{BufferDumper, Log} import java.nio.charset.Charset @@ -31,15 +31,21 @@ object MessageDecoder { val DefaultMaximumSize = 16777216 } -class MessageDecoder(charset: Charset, maximumMessageSize : Int = MessageDecoder.DefaultMaximumSize) extends ByteToMessageDecoder { +class MessageDecoder(sslEnabled: Boolean, charset: Charset, maximumMessageSize : Int = MessageDecoder.DefaultMaximumSize) extends ByteToMessageDecoder { import MessageDecoder.log private val parser = new MessageParsersRegistry(charset) + private var sslChecked = false + override def decode(ctx: ChannelHandlerContext, b: ByteBuf, out: java.util.List[Object]): Unit = { - if (b.readableBytes() >= 5) { + if (sslEnabled & !sslChecked) { + val code = b.readByte() + sslChecked = true + out.add(new SSLResponseMessage(code == 'S')) + } else if (b.readableBytes() >= 5) { b.markReaderIndex() diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala index 5cf5d480..30195a11 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/MessageEncoder.scala @@ -44,12 +44,13 @@ class MessageEncoder(charset: Charset, encoderRegistry: ColumnEncoderRegistry) e override def encode(ctx: ChannelHandlerContext, msg: AnyRef, out: java.util.List[Object]) = { val buffer = msg match { + case SSLRequestMessage => SSLMessageEncoder.encode() + case message: StartupMessage => startupEncoder.encode(message) case message: ClientMessage => { val encoder = (message.kind: @switch) match { case ServerMessage.Close => CloseMessageEncoder case ServerMessage.Execute => this.executeEncoder case ServerMessage.Parse => this.openEncoder - case ServerMessage.Startup => this.startupEncoder case ServerMessage.Query => this.queryEncoder case ServerMessage.PasswordMessage => this.credentialEncoder case _ => throw new EncoderNotAvailableException(message) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala index b53821ee..733cc5d1 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/codec/PostgreSQLConnectionHandler.scala @@ -17,6 +17,7 @@ package com.github.mauricio.async.db.postgresql.codec import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.SSLConfiguration.Mode import com.github.mauricio.async.db.column.{ColumnDecoderRegistry, ColumnEncoderRegistry} import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.postgresql.messages.backend._ @@ -38,6 +39,12 @@ import com.github.mauricio.async.db.postgresql.messages.backend.RowDescriptionMe import com.github.mauricio.async.db.postgresql.messages.backend.ParameterStatusMessage import io.netty.channel.socket.nio.NioSocketChannel import io.netty.handler.codec.CodecException +import io.netty.handler.ssl.{SslContextBuilder, SslHandler} +import io.netty.handler.ssl.util.InsecureTrustManagerFactory +import io.netty.util.concurrent.FutureListener +import javax.net.ssl.{SSLParameters, TrustManagerFactory} +import java.security.KeyStore +import java.io.FileInputStream object PostgreSQLConnectionHandler { final val log = Log.get[PostgreSQLConnectionHandler] @@ -79,7 +86,7 @@ class PostgreSQLConnectionHandler override def initChannel(ch: channel.Channel): Unit = { ch.pipeline.addLast( - new MessageDecoder(configuration.charset, configuration.maximumMessageSize), + new MessageDecoder(configuration.ssl.mode != Mode.Disable, configuration.charset, configuration.maximumMessageSize), new MessageEncoder(configuration.charset, encoderRegistry), PostgreSQLConnectionHandler.this) } @@ -120,13 +127,61 @@ class PostgreSQLConnectionHandler } override def channelActive(ctx: ChannelHandlerContext): Unit = { - ctx.writeAndFlush(new StartupMessage(this.properties)) + if (configuration.ssl.mode == Mode.Disable) + ctx.writeAndFlush(new StartupMessage(this.properties)) + else + ctx.writeAndFlush(SSLRequestMessage) } override def channelRead0(ctx: ChannelHandlerContext, msg: Object): Unit = { msg match { + case SSLResponseMessage(supported) => + if (supported) { + val ctxBuilder = SslContextBuilder.forClient() + if (configuration.ssl.mode >= Mode.VerifyCA) { + configuration.ssl.rootCert.fold { + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + val ks = KeyStore.getInstance(KeyStore.getDefaultType()) + val cacerts = new FileInputStream(System.getProperty("java.home") + "/lib/security/cacerts") + try { + ks.load(cacerts, "changeit".toCharArray) + } finally { + cacerts.close() + } + tmf.init(ks) + ctxBuilder.trustManager(tmf) + } { path => + ctxBuilder.trustManager(path) + } + } else { + ctxBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE) + } + val sslContext = ctxBuilder.build() + val sslEngine = sslContext.newEngine(ctx.alloc(), configuration.host, configuration.port) + if (configuration.ssl.mode >= Mode.VerifyFull) { + val sslParams = sslEngine.getSSLParameters() + sslParams.setEndpointIdentificationAlgorithm("HTTPS") + sslEngine.setSSLParameters(sslParams) + } + val handler = new SslHandler(sslEngine) + ctx.pipeline().addFirst(handler) + handler.handshakeFuture.addListener(new FutureListener[channel.Channel]() { + def operationComplete(future: io.netty.util.concurrent.Future[channel.Channel]) { + if (future.isSuccess()) { + ctx.writeAndFlush(new StartupMessage(properties)) + } else { + connectionDelegate.onError(future.cause()) + } + } + }) + } else if (configuration.ssl.mode < Mode.Require) { + ctx.writeAndFlush(new StartupMessage(properties)) + } else { + connectionDelegate.onError(new IllegalArgumentException("SSL is not supported on server")) + } + case m: ServerMessage => { (m.kind : @switch) match { diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/SSLMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/SSLMessageEncoder.scala new file mode 100644 index 00000000..aeec7435 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/SSLMessageEncoder.scala @@ -0,0 +1,16 @@ +package com.github.mauricio.async.db.postgresql.encoders + +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled + +object SSLMessageEncoder { + + def encode(): ByteBuf = { + val buffer = Unpooled.buffer() + buffer.writeInt(8) + buffer.writeShort(1234) + buffer.writeShort(5679) + buffer + } + +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala index b8c97843..206fd2d3 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/encoders/StartupMessageEncoder.scala @@ -21,13 +21,11 @@ import com.github.mauricio.async.db.util.ByteBufferUtils import java.nio.charset.Charset import io.netty.buffer.{Unpooled, ByteBuf} -class StartupMessageEncoder(charset: Charset) extends Encoder { +class StartupMessageEncoder(charset: Charset) { //private val log = Log.getByName("StartupMessageEncoder") - override def encode(message: ClientMessage): ByteBuf = { - - val startup = message.asInstanceOf[StartupMessage] + def encode(startup: StartupMessage): ByteBuf = { val buffer = Unpooled.buffer() buffer.writeInt(0) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/SSLResponseMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/SSLResponseMessage.scala new file mode 100644 index 00000000..905ab688 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/SSLResponseMessage.scala @@ -0,0 +1,3 @@ +package com.github.mauricio.async.db.postgresql.messages.backend + +case class SSLResponseMessage(supported: Boolean) diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala index c413ef4e..1fa5b9a2 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/backend/ServerMessage.scala @@ -43,7 +43,6 @@ object ServerMessage { final val Query = 'Q' final val RowDescription = 'T' final val ReadyForQuery = 'Z' - final val Startup = '0' final val Sync = 'S' } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/InitialClientMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/InitialClientMessage.scala new file mode 100644 index 00000000..228c5e65 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/InitialClientMessage.scala @@ -0,0 +1,3 @@ +package com.github.mauricio.async.db.postgresql.messages.frontend + +trait InitialClientMessage diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/SSLRequestMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/SSLRequestMessage.scala new file mode 100644 index 00000000..c3bf84ff --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/SSLRequestMessage.scala @@ -0,0 +1,5 @@ +package com.github.mauricio.async.db.postgresql.messages.frontend + +import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage + +object SSLRequestMessage extends InitialClientMessage diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala index e4bb34c4..bb53390f 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/messages/frontend/StartupMessage.scala @@ -16,6 +16,4 @@ package com.github.mauricio.async.db.postgresql.messages.frontend -import com.github.mauricio.async.db.postgresql.messages.backend.ServerMessage - -class StartupMessage(val parameters: List[(String, Any)]) extends ClientMessage(ServerMessage.Startup) \ No newline at end of file +class StartupMessage(val parameters: List[(String, Any)]) extends InitialClientMessage 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 index ce5fa180..8172877e 100644 --- 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 @@ -16,28 +16,37 @@ object ParserURL { val PGPORT = "port" val PGDBNAME = "database" val PGHOST = "host" - val PGUSERNAME = "username" + val PGUSERNAME = "user" val PGPASSWORD = "password" val DEFAULT_PORT = "5432" - private val pgurl1 = """(jdbc:postgresql):(?://([^/:]*|\[.+\])(?::(\d+))?)?(?:/([^/?]*))?(?:\?user=(.*)&password=(.*))?""".r - private val pgurl2 = """(postgres|postgresql)://(.*):(.*)@(.*):(\d+)/(.*)""".r + 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, username, password) => { + 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(username != null) result = (result + (PGUSERNAME -> username) + (PGPASSWORD -> password)) + if (port != null) result += (PGPORT -> port) + if (params != null) result ++= parseOptions(params) result } - case pgurl2(protocol, username, password, server, port, dbname) => { - properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) + 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.") 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 f39f24ac..debcb6d9 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 @@ -16,14 +16,11 @@ package com.github.mauricio.async.db.postgresql.util -import com.github.mauricio.async.db.Configuration +import com.github.mauricio.async.db.{Configuration, SSLConfiguration} import java.nio.charset.Charset object URLParser { - private val Username = "username" - private val Password = "password" - import Configuration.Default def parse(url: String, @@ -35,11 +32,12 @@ object URLParser { val port = properties.get(ParserURL.PGPORT).getOrElse(ParserURL.DEFAULT_PORT).toInt new Configuration( - username = properties.get(Username).getOrElse(Default.username), - password = properties.get(Password), + 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 ) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala index 40b35549..2659d372 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/DatabaseTestHelper.scala @@ -18,10 +18,12 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.util.Log import com.github.mauricio.async.db.{Connection, Configuration} +import java.io.File import java.util.concurrent.{TimeoutException, TimeUnit} -import scala.Some import scala.concurrent.duration._ import scala.concurrent.{Future, Await} +import com.github.mauricio.async.db.SSLConfiguration +import com.github.mauricio.async.db.SSLConfiguration.Mode object DatabaseTestHelper { val log = Log.get[DatabaseTestHelper] @@ -54,6 +56,16 @@ trait DatabaseTestHelper { withHandler(this.timeTestConfiguration, fn) } + def withSSLHandler[T](mode: SSLConfiguration.Mode.Value, host: String = "localhost", rootCert: Option[File] = Some(new File("script/server.crt")))(fn: (PostgreSQLConnection) => T): T = { + val config = new Configuration( + host = host, + port = databasePort, + username = "postgres", + database = databaseName, + ssl = SSLConfiguration(mode = mode, rootCert = rootCert)) + withHandler(config, fn) + } + def withHandler[T](configuration: Configuration, fn: (PostgreSQLConnection) => T): T = { val handler = new PostgreSQLConnection(configuration) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala index 14f0bed2..a033e3ee 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/MessageDecoderSpec.scala @@ -27,7 +27,7 @@ import java.util class MessageDecoderSpec extends Specification { - val decoder = new MessageDecoder(CharsetUtil.UTF_8) + val decoder = new MessageDecoder(false, CharsetUtil.UTF_8) "message decoder" should { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLSSLConnectionSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLSSLConnectionSpec.scala new file mode 100644 index 00000000..2e38adbb --- /dev/null +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLSSLConnectionSpec.scala @@ -0,0 +1,51 @@ +package com.github.mauricio.async.db.postgresql + +import org.specs2.mutable.Specification +import com.github.mauricio.async.db.SSLConfiguration.Mode +import javax.net.ssl.SSLHandshakeException + +class PostgreSQLSSLConnectionSpec extends Specification with DatabaseTestHelper { + + "ssl handler" should { + + "connect to the database in ssl without verifying CA" in { + + withSSLHandler(Mode.Require, "127.0.0.1", None) { handler => + handler.isReadyForQuery must beTrue + } + + } + + "connect to the database in ssl verifying CA" in { + + withSSLHandler(Mode.VerifyCA, "127.0.0.1") { handler => + handler.isReadyForQuery must beTrue + } + + } + + "connect to the database in ssl verifying CA and hostname" in { + + withSSLHandler(Mode.VerifyFull) { handler => + handler.isReadyForQuery must beTrue + } + + } + + "throws exception when CA verification fails" in { + + withSSLHandler(Mode.VerifyCA, rootCert = None) { handler => + } must throwA[SSLHandshakeException] + + } + + "throws exception when hostname verification fails" in { + + withSSLHandler(Mode.VerifyFull, "127.0.0.1") { handler => + } must throwA[SSLHandshakeException] + + } + + } + +} 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 1e542f52..d0df6eaa 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 @@ -18,6 +18,8 @@ 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 class URLParserSpec extends Specification { @@ -68,8 +70,20 @@ class URLParserSpec extends Specification { configuration.port === 9987 } - "create a connection from a heroku like URL using 'postgres' protocol" in { - val connectionUri = "postgres://john:doe@128.567.54.90:9987/my_database" + "create a connection with SSL enabled" in { + val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database?sslmode=verify-full" + + val configuration = URLParser.parse(connectionUri) + configuration.username === Configuration.Default.username + configuration.password === None + configuration.database === Some("my_database") + configuration.host === "128.567.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 configuration = URLParser.parse(connectionUri) configuration.username === "john" @@ -77,6 +91,8 @@ class URLParserSpec extends Specification { configuration.database === Some("my_database") configuration.host === "128.567.54.90" configuration.port === 9987 + configuration.ssl.mode === Mode.VerifyCA + configuration.ssl.rootCert.map(_.getPath) === Some("server.crt") } "create a connection with the available fields and named server" in { diff --git a/script/prepare_build.sh b/script/prepare_build.sh index 96aa8345..068ab389 100755 --- a/script/prepare_build.sh +++ b/script/prepare_build.sh @@ -1,5 +1,7 @@ #!/usr/bin/env sh +SCRIPTDIR=`dirname $0` + echo "Preparing MySQL configs" mysql -u root -e 'create database mysql_async_tests;' mysql -u root -e "create table mysql_async_tests.transaction_test (id varchar(255) not null, primary key (id))" @@ -10,26 +12,35 @@ mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'mysql_async_nopw'@'localhost' echo "preparing postgresql configs" -psql -c 'create database netty_driver_test;' -U postgres -psql -c 'create database netty_driver_time_test;' -U postgres -psql -c "alter database netty_driver_time_test set timezone to 'GMT'" -U postgres -psql -c "create table transaction_test ( id varchar(255) not null, constraint id_unique primary key (id))" -U postgres netty_driver_test -psql -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U postgres -psql -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U postgres -psql -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U postgres -psql -d "netty_driver_test" -c "CREATE TYPE example_mood AS ENUM ('sad', 'ok', 'happy');" -U postgres +PGUSER=postgres +PGCONF=/etc/postgresql/9.1/main +PGDATA=/var/ramfs/postgresql/9.1/main + +psql -d "postgres" -c 'create database netty_driver_test;' -U $PGUSER +psql -d "postgres" -c 'create database netty_driver_time_test;' -U $PGUSER +psql -d "postgres" -c "alter database netty_driver_time_test set timezone to 'GMT'" -U $PGUSER +psql -d "netty_driver_test" -c "create table transaction_test ( id varchar(255) not null, constraint id_unique primary key (id))" -U $PGUSER +psql -d "postgres" -c "CREATE USER postgres_md5 WITH PASSWORD 'postgres_md5'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_md5;" -U $PGUSER +psql -d "postgres" -c "CREATE USER postgres_cleartext WITH PASSWORD 'postgres_cleartext'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_cleartext;" -U $PGUSER +psql -d "postgres" -c "CREATE USER postgres_kerberos WITH PASSWORD 'postgres_kerberos'; GRANT ALL PRIVILEGES ON DATABASE netty_driver_test to postgres_kerberos;" -U $PGUSER +psql -d "netty_driver_test" -c "CREATE TYPE example_mood AS ENUM ('sad', 'ok', 'happy');" -U $PGUSER -sudo chmod 777 /etc/postgresql/9.1/main/pg_hba.conf +sudo chmod 666 $PGCONF/pg_hba.conf echo "pg_hba.conf goes as follows" -cat "/etc/postgresql/9.1/main/pg_hba.conf" +cat "$PGCONF/pg_hba.conf" -sudo echo "host all postgres 127.0.0.1/32 trust" > /etc/postgresql/9.1/main/pg_hba.conf -sudo echo "host all postgres_md5 127.0.0.1/32 md5" >> /etc/postgresql/9.1/main/pg_hba.conf -sudo echo "host all postgres_cleartext 127.0.0.1/32 password" >> /etc/postgresql/9.1/main/pg_hba.conf -sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" >> /etc/postgresql/9.1/main/pg_hba.conf +sudo echo "local all all trust" > $PGCONF/pg_hba.conf +sudo echo "host all postgres 127.0.0.1/32 trust" >> $PGCONF/pg_hba.conf +sudo echo "host all postgres_md5 127.0.0.1/32 md5" >> $PGCONF/pg_hba.conf +sudo echo "host all postgres_cleartext 127.0.0.1/32 password" >> $PGCONF/pg_hba.conf +sudo echo "host all postgres_kerberos 127.0.0.1/32 krb5" >> $PGCONF/pg_hba.conf echo "pg_hba.conf is now like" -cat "/etc/postgresql/9.1/main/pg_hba.conf" +cat "$PGCONF/pg_hba.conf" + +sudo chmod 600 $PGCONF/pg_hba.conf + +sudo cp -f $SCRIPTDIR/server.crt $SCRIPTDIR/server.key $PGDATA -sudo /etc/init.d/postgresql restart \ No newline at end of file +sudo /etc/init.d/postgresql restart diff --git a/script/server.crt b/script/server.crt new file mode 100644 index 00000000..aeef86f2 --- /dev/null +++ b/script/server.crt @@ -0,0 +1,75 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 9913731310682600948 (0x8994a61a13e775f4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: CN=localhost + Validity + Not Before: Mar 6 08:12:28 2016 GMT + Not After : Apr 5 08:12:28 2016 GMT + Subject: CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:ce:26:60:f9:0d:0f:f1:d6:ed:3e:79:91:55:6a: + 18:63:23:96:f2:60:50:3d:e3:dd:72:e8:c2:54:17: + 50:be:f0:9c:32:95:39:75:b1:04:a7:bb:f5:10:a4: + eb:d0:10:e2:17:45:d3:f9:35:8e:b4:8f:14:97:8f: + 27:93:d7:20:05:e2:dc:68:64:bc:fd:f2:19:17:94: + e8:2f:a6:b2:54:3f:df:3e:e7:8f:f1:52:15:7a:30: + 81:4d:bb:6f:22:8c:ca:e1:cb:6a:72:6d:fa:89:50: + e7:ee:07:d1:84:8a:71:07:dc:3f:6f:1f:db:10:e9: + 93:ad:01:c5:2b:51:ce:58:ef:12:95:00:16:e8:d4: + 46:07:35:ee:10:47:c4:f7:ff:47:17:52:a5:bb:5c: + cb:3c:f6:6b:c8:e7:d9:7c:18:39:a1:8f:e0:45:82: + 88:b5:27:f3:58:cb:ba:30:c0:8a:77:5b:00:bf:09: + 10:b1:ad:aa:f4:1b:2c:a1:f9:a5:59:57:c8:ef:de: + 54:ad:35:af:67:7e:29:bc:9a:2a:d2:f0:b1:9c:34: + 3c:bc:64:c9:4c:93:2c:7d:29:f4:1a:ac:f3:44:42: + a4:c9:06:1e:a4:73:e6:aa:67:d0:e4:02:02:ba:51: + 1e:97:44:b8:4b:4e:55:cd:e6:24:49:08:ac:9b:09: + 19:31 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 2E:20:4D:E1:12:2A:B0:6F:52:7F:62:90:D4:78:7B:E3:7D:D5:60:10 + X509v3 Authority Key Identifier: + keyid:2E:20:4D:E1:12:2A:B0:6F:52:7F:62:90:D4:78:7B:E3:7D:D5:60:10 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 9b:e8:50:8b:86:0f:bf:22:c6:b4:ef:3e:c9:a2:55:fb:69:fc: + ae:93:7b:5e:6a:b6:ed:5b:27:c2:9e:36:d6:f1:f1:0f:67:65: + 87:de:05:21:6e:0e:f4:df:ac:72:61:47:f8:fd:16:9b:3d:54: + ef:21:cf:b7:31:ba:bf:c9:1b:2c:a0:f9:f1:6b:45:5a:98:25: + b9:01:99:cf:e1:79:c5:6a:20:ce:ca:ca:3f:6d:56:f3:65:51: + 31:98:01:b9:96:99:04:9c:ab:ae:fb:3f:f8:ad:60:66:77:54: + b2:81:e3:7c:6b:c4:36:ae:ae:5c:c6:1a:09:5c:d6:13:da:2b: + ba:ef:3f:3e:b2:13:f2:51:15:c5:1b:9c:22:be:b4:55:9b:15: + 70:60:3d:98:6e:ef:53:4c:c7:20:60:3f:17:f3:cc:76:47:96: + 27:05:84:0e:db:21:e1:76:b7:9c:38:35:19:ef:52:d4:fc:bd: + ec:95:2e:eb:4b:5b:0b:c8:86:d7:23:c2:76:14:f3:93:6f:c0: + a9:b6:ca:f8:47:3e:9d:af:11:5d:73:79:68:70:26:f9:fd:39: + 60:c1:c3:c7:a9:fc:48:b5:c0:e6:b4:2e:07:de:6a:ca:ed:04: + 67:31:b8:0b:d0:48:fd:3b:4c:12:8a:34:5c:18:3f:38:85:f2: + 1c:96:39:50 +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJAImUphoT53X0MA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xNjAzMDYwODEyMjhaFw0xNjA0MDUwODEyMjhaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAM4mYPkND/HW7T55kVVqGGMjlvJgUD3j3XLowlQXUL7wnDKVOXWxBKe79RCk +69AQ4hdF0/k1jrSPFJePJ5PXIAXi3GhkvP3yGReU6C+mslQ/3z7nj/FSFXowgU27 +byKMyuHLanJt+olQ5+4H0YSKcQfcP28f2xDpk60BxStRzljvEpUAFujURgc17hBH +xPf/RxdSpbtcyzz2a8jn2XwYOaGP4EWCiLUn81jLujDAindbAL8JELGtqvQbLKH5 +pVlXyO/eVK01r2d+KbyaKtLwsZw0PLxkyUyTLH0p9Bqs80RCpMkGHqRz5qpn0OQC +ArpRHpdEuEtOVc3mJEkIrJsJGTECAwEAAaNQME4wHQYDVR0OBBYEFC4gTeESKrBv +Un9ikNR4e+N91WAQMB8GA1UdIwQYMBaAFC4gTeESKrBvUn9ikNR4e+N91WAQMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJvoUIuGD78ixrTvPsmiVftp +/K6Te15qtu1bJ8KeNtbx8Q9nZYfeBSFuDvTfrHJhR/j9Fps9VO8hz7cxur/JGyyg ++fFrRVqYJbkBmc/hecVqIM7Kyj9tVvNlUTGYAbmWmQScq677P/itYGZ3VLKB43xr +xDaurlzGGglc1hPaK7rvPz6yE/JRFcUbnCK+tFWbFXBgPZhu71NMxyBgPxfzzHZH +licFhA7bIeF2t5w4NRnvUtT8veyVLutLWwvIhtcjwnYU85NvwKm2yvhHPp2vEV1z +eWhwJvn9OWDBw8ep/Ei1wOa0LgfeasrtBGcxuAvQSP07TBKKNFwYPziF8hyWOVA= +-----END CERTIFICATE----- diff --git a/script/server.key b/script/server.key new file mode 100644 index 00000000..0e226429 --- /dev/null +++ b/script/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAziZg+Q0P8dbtPnmRVWoYYyOW8mBQPePdcujCVBdQvvCcMpU5 +dbEEp7v1EKTr0BDiF0XT+TWOtI8Ul48nk9cgBeLcaGS8/fIZF5ToL6ayVD/fPueP +8VIVejCBTbtvIozK4ctqcm36iVDn7gfRhIpxB9w/bx/bEOmTrQHFK1HOWO8SlQAW +6NRGBzXuEEfE9/9HF1Klu1zLPPZryOfZfBg5oY/gRYKItSfzWMu6MMCKd1sAvwkQ +sa2q9BssofmlWVfI795UrTWvZ34pvJoq0vCxnDQ8vGTJTJMsfSn0GqzzREKkyQYe +pHPmqmfQ5AICulEel0S4S05VzeYkSQismwkZMQIDAQABAoIBAH80v3Hu1X/tl8eN +TFjgdtv2Ahbdx6XpDaTya7doC7NG1ZuA6UvuR2kZWkdC/SAOyvSBaiPFIKHaCGLd +OxbHEEORkV/5iYVJ9qHOiNeejTvfjepLCU9nz0ju1VsZ5aH0LtzVoIGry4UgH32J +5YdbxhOLnLj9dzggabe/9+KbQDEveGTzkIvSJ1nbts7c8IRp6t/1nBz54BhawUjJ +IbaEbCH/mEmiCOUP914SCAUEfmgbMhdx8dc4V9nyxK+bulF3WIEpVZU1zj5Rpyni +P8gQ1geI64Erd8oa4DJ5C77eLuKKk0JBCkgh5x3hiAxuvN0zxHxW2Q75c6x9uDr5 +DXi20GECgYEA+NRW6heYBJw7Lt7+cQCRG5/WFOX9TmmK9EAidVPULWO4NN4wLZxa +exW/epg8w1Y+u+BHOzFq9idJaHsoLZCmoNWMkZsP+AzeEkklee6wgur3/Zs1HqHZ +1VA3EmvOecz++3o69zcjd0nzgk9ADhjA2dAahKTnn5RESD1dFBWU2+sCgYEA1Bcv +PiQe6ce86FlSPr0TBFvIJl2dfjrQijL3dhZMo+1Y5VTShGBoAQKfBhJITSbsmaEz +UQ/4rBMyTN9bwvSwsDpQZw/Y0YKiSQIOr4J0jyotY5RN2AH3AlCX8CrhoOmBaLUd +n2SGx5keodnXn1/GPkuGPIa7xnGib/gdL2AaZFMCgYBV5AX0XByPStZrAXJW01lD +bdLZ9+GOFYRvd0vtr/gHiupk5WU/+T6KSiGEUdR3oOeatnogBpjjSwBd3lUqFUpP +LieNgzbp6pclPLaA9lFbf3wGwHJ/lmK47S11YF0vUgGaEMEV4KSPYql5i52SwByh +kuH0c2+4d9dyECx26FQv7QKBgQDBtX83oWP+n6hhCpu8o5IH7BAtQlmDHhKz9oLf +/tP28OO9abBwqWC0c4Fs2SviE4gLdRjak9zKxSmu3l3//N6XxlsDFo0wJcE1L0Tc +dikhTSNxjNVgUcMaASQUfgXfowXH7YvltboH+UjqCH4QmTgGU5KCG4jLYaQ74gA9 +8eeI8wKBgDfclcMsJnY6FpFoR0Ub9VOrdbKtD9nXSxhTSFKjrp4JM7SBN3u6NPJK +FgKZyQxd1bX/RBioN1prrZ3rbg+9awc65KhyfwtNxiurCBZhYObhKJv7lZyjNgsT +EALMKvB+fdpMtPZOVtUl0MbHEBblrJ+oy4TPT/kvMuCudF/5arcZ +-----END RSA PRIVATE KEY----- From efeb246ae1eefa4e020e993e02a058cf6edcacb2 Mon Sep 17 00:00:00 2001 From: Guilherme Campos Date: Tue, 15 Mar 2016 14:20:14 -0300 Subject: [PATCH 333/357] Rename LICENCE.txt to LICENSE.txt fixing filename typo --- LICENCE.txt => LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename LICENCE.txt => LICENSE.txt (99%) diff --git a/LICENCE.txt b/LICENSE.txt similarity index 99% rename from LICENCE.txt rename to LICENSE.txt index 61ca0ac4..fc389d02 100644 --- a/LICENCE.txt +++ b/LICENSE.txt @@ -199,4 +199,4 @@ 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. \ No newline at end of file + limitations under the License. From a43118ffcd8196055d1cb1a17d696e09b1a516e7 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 17 Mar 2016 22:08:13 -0400 Subject: [PATCH 334/357] Wrapping up 0.2.19 --- CHANGELOG.md | 5 +++++ README.markdown | 8 ++++---- README.md | 3 --- project/Build.scala | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) delete mode 100644 README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab0d079..5baf54f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ # Changelog +## 0.2.19 - 2016-03-17 + +* Always use `NUMERIC` when handling numbers in prepared statements in PostgreSQL; +* SSL support for PostgreSQL - @alexdupre - #85; + ## 0.2.18 - 2015-08-08 * Timeouts implemented queries for MySQL and PostgreSQL - @lifey - #147 diff --git a/README.markdown b/README.markdown index 73302b6c..b5fb56c8 100644 --- a/README.markdown +++ b/README.markdown @@ -55,7 +55,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.18" +"com.github.mauricio" %% "postgresql-async" % "0.2.19" ``` Or Maven: @@ -64,14 +64,14 @@ Or Maven: com.github.mauricio postgresql-async_2.11 - 0.2.18 + 0.2.19 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.18" +"com.github.mauricio" %% "mysql-async" % "0.2.19" ``` Or Maven: @@ -80,7 +80,7 @@ Or Maven: com.github.mauricio mysql-async_2.11 - 0.2.18 + 0.2.19 ``` diff --git a/README.md b/README.md deleted file mode 100644 index 4fedd098..00000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# postgresql-async - -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mauricio/postgresql-async?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index a820fb76..386c4f5e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -49,7 +49,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.19-SNAPSHOT" + val commonVersion = "0.2.19" val projectScalaVersion = "2.11.7" val specs2Version = "2.5" From 11e23e0eab0b50df61f3e693564fbf8deff1ef6a Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 17 Mar 2016 22:10:15 -0400 Subject: [PATCH 335/357] Updated readme and changelog --- CHANGELOG.md | 5 +++-- README.markdown | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5baf54f7..bafc831d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ -**Table of Contents** - [Changelog](#changelog) - - [0.2.17 - in progresss](#0217---in-progresss) + - [0.2.19 - 2016-03-17](#0219---2016-03-17) + - [0.2.18 - 2015-08-08](#0218---2015-08-08) + - [0.2.17 - 2015-07-13](#0217---2015-07-13) - [0.2.16 - 2015-01-04](#0216---2015-01-04) - [0.2.15 - 2014-09-12](#0215---2014-09-12) - [0.2.14 - 2014-08-30](#0214---2014-08-30) diff --git a/README.markdown b/README.markdown index b5fb56c8..b87583a2 100644 --- a/README.markdown +++ b/README.markdown @@ -1,8 +1,7 @@ -**Table of Contents** -- postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala - 2.10 - 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 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) - [Abstractions and integrations](#abstractions-and-integrations) - [Include them as dependencies](#include-them-as-dependencies) - [Database connections and encodings](#database-connections-and-encodings) From 5e24cb0902860972a952c4b4d164e73568fe1d93 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Thu, 17 Mar 2016 22:11:54 -0400 Subject: [PATCH 336/357] Kicking off next cycle --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 386c4f5e..13c8df4b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -49,7 +49,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.19" + val commonVersion = "0.2.20-SNAPSHOT" val projectScalaVersion = "2.11.7" val specs2Version = "2.5" From d6180b518b9c399c7392c2cac96d14bc5d3d8c7f Mon Sep 17 00:00:00 2001 From: Stephen Couchman Date: Mon, 29 Feb 2016 05:48:17 -0500 Subject: [PATCH 337/357] Added check to SingleThreadedAsyncObjectPool to ensure returned objects came from that pool. Fixed not destroying invalidated objects during test cycle. Fixed exception on multiple close attempts in SingleThreadedAsyncObjectPool to make consistent with simultaneous request execution path. Added generic spec for testing an AsyncObjectPool implementation, and applied it to SingleThreadedAsyncObjectPool to guard against the above problems reappearing. Added mock capabilities back for specs2. --- .../pool/SingleThreadedAsyncObjectPool.scala | 72 ++++-- .../db/pool/AbstractAsyncObjectPoolSpec.scala | 228 ++++++++++++++++++ project/Build.scala | 2 + 3 files changed, 277 insertions(+), 25 deletions(-) create mode 100644 db-async-common/src/test/scala/com/github/mauricio/async/db/pool/AbstractAsyncObjectPoolSpec.scala 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 2b2e28d9..49f60593 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 @@ -16,11 +16,14 @@ package com.github.mauricio.async.db.pool +import java.util.concurrent.RejectedExecutionException + import com.github.mauricio.async.db.util.{Log, Worker} import java.util.concurrent.atomic.AtomicLong -import java.util.{TimerTask, Timer} +import java.util.{Timer, TimerTask} + import scala.collection.mutable.{ArrayBuffer, Queue, Stack} -import scala.concurrent.{Promise, Future} +import scala.concurrent.{Future, Promise} import scala.util.{Failure, Success} object SingleThreadedAsyncObjectPool { @@ -93,15 +96,30 @@ class SingleThreadedAsyncObjectPool[T]( def giveBack(item: T): Future[AsyncObjectPool[T]] = { val promise = Promise[AsyncObjectPool[T]]() this.mainPool.action { - this.checkouts -= item - this.factory.validate(item) match { - case Success(item) => { - this.addBack(item, promise) + // Ensure it came from this pool + val idx = this.checkouts.indexOf(item) + if(idx >= 0) { + this.checkouts.remove(idx) + this.factory.validate(item) match { + case Success(item) => { + this.addBack(item, promise) + } + case Failure(e) => { + this.factory.destroy(item) + promise.failure(e) + } } - case Failure(e) => { - this.checkouts -= item - this.factory.destroy(item) - promise.failure(e) + } else { + // It's already a failure but lets doublecheck why + val isFromOurPool = (item match { + case x: AnyRef => this.poolables.find(holder => x eq holder.item.asInstanceOf[AnyRef]) + case _ => this.poolables.find(holder => item == holder.item) + }).isDefined + + if(isFromOurPool) { + promise.failure(new IllegalStateException("This item has already been returned")) + } else { + promise.failure(new IllegalArgumentException("The returned item did not come from this pool.")) } } } @@ -112,25 +130,28 @@ class SingleThreadedAsyncObjectPool[T]( def isFull: Boolean = this.poolables.isEmpty && this.checkouts.size == configuration.maxObjects def close: Future[AsyncObjectPool[T]] = { - val promise = Promise[AsyncObjectPool[T]]() - - this.mainPool.action { - if (!this.closed) { - try { - this.timer.cancel() - this.mainPool.shutdown - this.closed = true - (this.poolables.map(i => i.item) ++ this.checkouts).foreach(item => factory.destroy(item)) + try { + val promise = Promise[AsyncObjectPool[T]]() + this.mainPool.action { + if (!this.closed) { + try { + this.timer.cancel() + this.mainPool.shutdown + this.closed = true + (this.poolables.map(i => i.item) ++ this.checkouts).foreach(item => factory.destroy(item)) + promise.success(this) + } catch { + case e: Exception => promise.failure(e) + } + } else { promise.success(this) - } catch { - case e: Exception => promise.failure(e) } - } else { - promise.success(this) } + promise.future + } catch { + case e: RejectedExecutionException if this.closed => + Future.successful(this) } - - promise.future } def availables: Traversable[T] = this.poolables.map(item => item.item) @@ -238,6 +259,7 @@ class SingleThreadedAsyncObjectPool[T]( case Failure(e) => { log.error("Failed to validate object", e) removals += poolable + factory.destroy(poolable.item) } } } 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 new file mode 100644 index 00000000..34ca0662 --- /dev/null +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/AbstractAsyncObjectPoolSpec.scala @@ -0,0 +1,228 @@ +package com.github.mauricio.async.db.pool + +import com.github.mauricio.async.db.pool.AbstractAsyncObjectPoolSpec.Widget +import org.mockito.Mockito.reset +import org.specs2.mock.Mockito +import org.specs2.mutable.Specification + +import scala.concurrent.{Await, Future} +import scala.util.Failure + +import scala.reflect.runtime.universe.TypeTag +import scala.util.Try +import scala.concurrent.duration.{Duration, SECONDS} + +/** + * This spec is designed abstract to allow testing of any implementation of AsyncObjectPool, against the common + * requirements the interface expects. + * + * @tparam T the AsyncObjectPool being tested. + */ +abstract class AbstractAsyncObjectPoolSpec[T <: AsyncObjectPool[Widget]](implicit tag: TypeTag[T]) + extends Specification + with Mockito { + + import AbstractAsyncObjectPoolSpec._ + + protected def pool(factory: ObjectFactory[Widget] = new TestWidgetFactory, conf: PoolConfiguration = PoolConfiguration.Default): T + + // Evaluates to the type of AsyncObjectPool + s"the ${tag.tpe.erasure} variant of AsyncObjectPool" should { + + "successfully retrieve and return a Widget" in { + val p = pool() + val widget = Await.result(p.take, Duration.Inf) + + widget must not beNull + + val thePool = Await.result(p.giveBack(widget), Duration.Inf) + thePool must be(p) + } + + "reject Widgets that did not come from it" in { + val p = pool() + + Await.result(p.giveBack(Widget(null)), Duration.Inf) must throwAn[IllegalArgumentException] + } + + "scale contents" >> { + sequential + + val factory = spy(new TestWidgetFactory) + + val p = pool( + factory = factory, + conf = PoolConfiguration( + maxObjects = 5, + maxIdle = 2, + maxQueueSize = 5, + validationInterval = 2000 + )) + + + + var taken = Seq.empty[Widget] + "can take up to maxObjects" in { + taken = Await.result(Future.sequence(for (i <- 1 to 5) yield p.take), Duration.Inf) + + taken must have size 5 + taken.head must not beNull; + taken(1) must not beNull; + taken(2) must not beNull; + taken(3) must not beNull; + taken(4) must not beNull + } + + "does not attempt to expire taken items" in { + // Wait 3 seconds to ensure idle check has run at least once + there was after(3.seconds).no(factory).destroy(any[Widget]) + } + + reset(factory) // Considered bad form, but necessary as we depend on previous state in these tests + "takes maxObjects back" in { + val returns = Await.result(Future.sequence(for (widget <- taken) yield p.giveBack(widget)), Duration.Inf) + + returns must have size 5 + + returns.head must be(p) + returns(1) must be(p) + returns(2) must be(p) + returns(3) must be(p) + returns(4) must be(p) + } + + "protest returning an item that was already returned" in { + val resultFuture = p.giveBack(taken.head) + + Await.result(resultFuture, Duration.Inf) must throwAn[IllegalStateException] + } + + "destroy down to maxIdle widgets" in { + Thread.sleep(3000) + there were 5.times(factory).destroy(any[Widget]) + } + } + + "queue requests after running out" in { + val p = pool(conf = PoolConfiguration.Default.copy(maxObjects = 2, maxQueueSize = 1)) + + val widgets = Await.result(Future.sequence(for (i <- 1 to 2) yield p.take), Duration.Inf) + + val future = p.take + + // Wait five seconds + Thread.sleep(5000) + + val failedFuture = p.take + + // Cannot be done, would exceed maxObjects + future.isCompleted must beFalse + + Await.result(failedFuture, Duration.Inf) must throwA[PoolExhaustedException] + + Await.result(p.giveBack(widgets.head), Duration.Inf) must be(p) + + Await.result(future, Duration(5, SECONDS)) must be(widgets.head) + } + + "refuse to allow take after being closed" in { + val p = pool() + + Await.result(p.close, Duration.Inf) must be(p) + + Await.result(p.take, Duration.Inf) must throwA[PoolAlreadyTerminatedException] + } + + "allow being closed more than once" in { + val p = pool() + + Await.result(p.close, Duration.Inf) must be(p) + + Await.result(p.close, Duration.Inf) must be(p) + } + + + "destroy a failed widget" in { + val factory = spy(new TestWidgetFactory) + val p = pool(factory = factory) + + val widget = Await.result(p.take, Duration.Inf) + + widget must not beNull + + factory.validate(widget) returns Failure(new RuntimeException("This is a bad widget!")) + + Await.result(p.giveBack(widget), Duration.Inf) must throwA[RuntimeException](message = "This is a bad widget!") + + there was atLeastOne(factory).destroy(widget) + } + + "clean up widgets that die in the pool" in { + val factory = spy(new TestWidgetFactory) + // Deliberately make it impossible to expire (nearly) + val p = pool(factory = factory, conf = PoolConfiguration.Default.copy(maxIdle = Long.MaxValue, validationInterval = 2000)) + + val widget = Await.result(p.take, Duration.Inf) + + widget must not beNull + + Await.result(p.giveBack(widget), Duration.Inf) must be(p) + + there was atLeastOne(factory).validate(widget) + there were no(factory).destroy(widget) + + there was after(3.seconds).atLeastTwo(factory).validate(widget) + + factory.validate(widget) returns Failure(new RuntimeException("Test Exception, Not an Error")) + + there was after(3.seconds).one(factory).destroy(widget) + + Await.ready(p.take, Duration.Inf) + + there was two(factory).create + } + + } + +} + +object AbstractAsyncObjectPoolSpec { + + case class Widget(factory: TestWidgetFactory) + + class TestWidgetFactory extends ObjectFactory[Widget] { + + override def create: Widget = Widget(this) + + override def destroy(item: Widget) = {} + + override def validate(item: Widget): Try[Widget] = Try { + if (item.factory eq this) + item + else + throw new IllegalArgumentException("Not our item") + } + } + +} + + +class SingleThreadedAsyncObjectPoolSpec extends AbstractAsyncObjectPoolSpec[SingleThreadedAsyncObjectPool[Widget]] { + + import AbstractAsyncObjectPoolSpec._ + + override protected def pool(factory: ObjectFactory[Widget], conf: PoolConfiguration) = + new SingleThreadedAsyncObjectPool(factory, conf) + + "SingleThreadedAsyncObjectPool" should { + "successfully record a closed state" in { + val p = pool() + + Await.result(p.close, Duration.Inf) must be(p) + + p.isClosed must beTrue + } + + } + +} diff --git a/project/Build.scala b/project/Build.scala index 13c8df4b..84cd916f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -55,6 +55,7 @@ object Configuration { 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 commonDependencies = Seq( @@ -65,6 +66,7 @@ object Configuration { "org.javassist" % "javassist" % "3.20.0-GA", specs2Dependency, specs2JunitDependency, + specs2MockDependency, logbackDependency ) From a2a11ac3ff3c8fcbdbaf18589cacaa7994e8269f Mon Sep 17 00:00:00 2001 From: varkockova Date: Mon, 18 Apr 2016 17:59:21 +0200 Subject: [PATCH 338/357] Time unit as a part of javadoc I wanted to know the time unit being used for idle and I had to look into the iplementation to be sure. I think it might be useful to have this in the javadoc directly. --- .../com/github/mauricio/async/db/pool/PoolConfiguration.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala index a245de5c..0ac567f2 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/PoolConfiguration.scala @@ -25,7 +25,7 @@ object PoolConfiguration { * Defines specific pieces of a pool's behavior. * * @param maxObjects how many objects this pool will hold - * @param maxIdle how long are objects going to be kept as idle (not in use by clients of the pool) + * @param maxIdle number of milliseconds for which the objects are going to be kept as idle (not in use by clients of the pool) * @param maxQueueSize when there are no more objects, the pool can queue up requests to serve later then there * are objects available, this is the maximum number of enqueued requests * @param validationInterval pools will use this value as the timer period to validate idle objects. From 226ed09a6c5550e57f9b856ad2840e9a38134a2f Mon Sep 17 00:00:00 2001 From: Stephen Couchman Date: Mon, 25 Apr 2016 18:45:25 -0400 Subject: [PATCH 339/357] 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 8fb137a6fec7feb92e171e8b8060256d7799e18a Mon Sep 17 00:00:00 2001 From: Mansheng Yang Date: Thu, 12 May 2016 15:35:40 +0800 Subject: [PATCH 340/357] Fixed ByteBuf leaks PostgreSQLConnection.onDataRow should release the raw ByteBufs after decoding the data --- .../async/db/postgresql/PostgreSQLConnection.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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..1c2c08fe 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 @@ -191,11 +191,16 @@ class PostgreSQLConnection var x = 0 while ( x < m.values.size ) { - items(x) = if ( m.values(x) == null ) { + val buf = m.values(x) + items(x) = if ( buf == null ) { null } else { - val columnType = this.currentQuery.get.columnTypes(x) - this.decoderRegistry.decode(columnType, m.values(x), configuration.charset) + try { + val columnType = this.currentQuery.get.columnTypes(x) + this.decoderRegistry.decode(columnType, buf, configuration.charset) + } finally { + buf.release() + } } x += 1 } From 07dadc804041c2564ce386e535047da9e8b0a8ae Mon Sep 17 00:00:00 2001 From: Julien Viet Date: Tue, 7 Jun 2016 17:04:09 +0200 Subject: [PATCH 341/357] Upgrade to Netty 4.1.0 --- .../async/db/mysql/codec/LittleEndianByteBufAllocator.scala | 2 ++ project/Build.scala | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala index 40b51f24..0fdc790a 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/codec/LittleEndianByteBufAllocator.scala @@ -66,6 +66,8 @@ class LittleEndianByteBufAllocator extends ByteBufAllocator { def compositeDirectBuffer(maxNumComponents: Int): CompositeByteBuf = allocator.compositeDirectBuffer(maxNumComponents) + def calculateNewCapacity(minNewCapacity: Int, maxCapacity: Int): Int = allocator.calculateNewCapacity(minNewCapacity, maxCapacity) + private def littleEndian(b: ByteBuf) = b.order(ByteOrder.LITTLE_ENDIAN) } diff --git a/project/Build.scala b/project/Build.scala index 84cd916f..cf8b6861 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -62,7 +62,7 @@ object Configuration { "org.slf4j" % "slf4j-api" % "1.7.18", "joda-time" % "joda-time" % "2.9.2", "org.joda" % "joda-convert" % "1.8.1", - "io.netty" % "netty-all" % "4.0.34.Final", + "io.netty" % "netty-all" % "4.1.0.Final", "org.javassist" % "javassist" % "3.20.0-GA", specs2Dependency, specs2JunitDependency, From eed80b673a02e6efd5bd0d2d7db2de724f4b3894 Mon Sep 17 00:00:00 2001 From: Julien Viet Date: Wed, 8 Jun 2016 21:43:29 +0200 Subject: [PATCH 342/357] Update to 4.1.1.Final --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index cf8b6861..e02f3b3e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -62,7 +62,7 @@ object Configuration { "org.slf4j" % "slf4j-api" % "1.7.18", "joda-time" % "joda-time" % "2.9.2", "org.joda" % "joda-convert" % "1.8.1", - "io.netty" % "netty-all" % "4.1.0.Final", + "io.netty" % "netty-all" % "4.1.1.Final", "org.javassist" % "javassist" % "3.20.0-GA", specs2Dependency, specs2JunitDependency, From d51a85b9ffa96b91f79372c22353ad4073282830 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 Jun 2016 00:13:13 -0400 Subject: [PATCH 343/357] 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 344/357] 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 345/357] 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 346/357] 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 347/357] 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 348/357] 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 349/357] 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 350/357] 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 351/357] 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 352/357] 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 353/357] 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 354/357] 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 355/357] 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 356/357] 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 357/357] 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