Skip to content

Commit e9c93af

Browse files
committed
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.
1 parent df248da commit e9c93af

File tree

2 files changed

+46
-51
lines changed

2 files changed

+46
-51
lines changed

postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class PostgreSQLConnection
7474
private var recentError = false
7575
private val queryPromiseReference = new AtomicReference[Option[Promise[QueryResult]]](None)
7676
private var currentQuery: Option[MutableResultSet[PostgreSQLColumnData]] = None
77-
private var currentPreparedStatement: Option[String] = None
77+
private var currentPreparedStatement: Option[PreparedStatementHolder] = None
7878
private var version = Version(0,0,0)
7979

8080
private var queryResult: Option[QueryResult] = None
@@ -106,61 +106,31 @@ class PostgreSQLConnection
106106
promise.future
107107
}
108108

109-
private def prepareQueryParams(query : String) : (String, Int) = {
110-
val result = new StringBuilder(query.length+16)
111-
var offset = 0
112-
var params = 0
113-
while (offset < query.length) {
114-
val next = query.indexOf('?', offset)
115-
if (next == -1) {
116-
result ++= query.substring(offset)
117-
offset = query.length
118-
} else {
119-
result ++= query.substring(offset, next)
120-
offset = next + 1
121-
if (offset < query.length && query(offset) == '?') {
122-
result += '?'
123-
offset += 1
124-
} else {
125-
result += '$'
126-
params += 1
127-
result ++= params.toString
128-
}
129-
}
130-
}
131-
(result.toString, params)
132-
}
133-
134109
override def sendPreparedStatement(query: String, values: Seq[Any] = List()): Future[QueryResult] = {
135110
validateQuery(query)
136111

137-
val (realQuery, paramsCount) = prepareQueryParams(query)
138-
139-
if (paramsCount != values.length) {
140-
throw new InsufficientParametersException(paramsCount, values)
141-
}
142-
143112
val promise = Promise[QueryResult]()
144113
this.setQueryPromise(promise)
145-
this.currentPreparedStatement = Some(query)
146114

147-
this.isParsed(query) match {
148-
case Some(holder) => {
149-
this.currentQuery = Some(new MutableResultSet(holder.columnDatas))
150-
write(new PreparedStatementExecuteMessage(holder.statementId, realQuery, values, this.encoderRegistry))
151-
}
152-
case None => {
153-
val statementId = this.preparedStatementsCounter.incrementAndGet()
154-
this.parsedStatements.put( query, new PreparedStatementHolder( statementId ) )
155-
write(new PreparedStatementOpeningMessage(statementId, realQuery, values, this.encoderRegistry))
156-
}
115+
val holder = this.parsedStatements.getOrElseUpdate(query,
116+
new PreparedStatementHolder( query, preparedStatementsCounter.incrementAndGet ))
117+
118+
if (holder.paramsCount != values.length) {
119+
this.clearQueryPromise
120+
throw new InsufficientParametersException(holder.paramsCount, values)
157121
}
158122

159-
promise.future
160-
}
123+
this.currentPreparedStatement = Some(holder)
124+
this.currentQuery = Some(new MutableResultSet(holder.columnDatas))
125+
write(
126+
if (holder.prepared)
127+
new PreparedStatementExecuteMessage(holder.statementId, holder.realQuery, values, this.encoderRegistry)
128+
else {
129+
holder.prepared = true
130+
new PreparedStatementOpeningMessage(holder.statementId, holder.realQuery, values, this.encoderRegistry)
131+
})
161132

162-
private def isParsed(query: String): Option[PreparedStatementHolder] = {
163-
this.parsedStatements.get(query)
133+
promise.future
164134
}
165135

166136
override def onError( exception : Throwable ) {
@@ -234,8 +204,7 @@ class PostgreSQLConnection
234204
}
235205

236206
private def setColumnDatas( columnDatas : Array[PostgreSQLColumnData] ) {
237-
if (this.currentPreparedStatement.isDefined) {
238-
val holder = this.parsedStatements(this.currentPreparedStatement.get)
207+
this.currentPreparedStatement.foreach { holder =>
239208
holder.columnDatas = columnDatas
240209
}
241210
}

postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PreparedStatementHolder.scala

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,34 @@ package com.github.mauricio.async.db.postgresql
1818

1919
import com.github.mauricio.async.db.postgresql.messages.backend.PostgreSQLColumnData
2020

21-
class PreparedStatementHolder( val statementId : Int ) {
21+
class PreparedStatementHolder( query : String, val statementId : Int ) {
2222

23+
val (realQuery, paramsCount) = {
24+
val result = new StringBuilder(query.length+16)
25+
var offset = 0
26+
var params = 0
27+
while (offset < query.length) {
28+
val next = query.indexOf('?', offset)
29+
if (next == -1) {
30+
result ++= query.substring(offset)
31+
offset = query.length
32+
} else {
33+
result ++= query.substring(offset, next)
34+
offset = next + 1
35+
if (offset < query.length && query(offset) == '?') {
36+
result += '?'
37+
offset += 1
38+
} else {
39+
result += '$'
40+
params += 1
41+
result ++= params.toString
42+
}
43+
}
44+
}
45+
(result.toString, params)
46+
}
47+
48+
var prepared : Boolean = false
2349
var columnDatas : Array[PostgreSQLColumnData] = Array.empty
2450

25-
}
51+
}

0 commit comments

Comments
 (0)