Skip to content

Commit fc322d4

Browse files
authored
Partitioned Postgis - Expose pgjdbc prepareThreshold as a connection param (#10787)
* Adds `prepare_threshold` datastore param and uses it as a pgjdbc driver connection property Closes #10786
1 parent a4dff38 commit fc322d4

4 files changed

Lines changed: 87 additions & 8 deletions

File tree

docs/user/postgis/usage.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Parameter Type Description
1818
for more information. Setting this timeout may help prevent abandoned queries from slowing down database operations.
1919
``read_access_roles`` String A comma-separated list of roles that should be granted read-only access to any new schemas. These roles must already exist in the
2020
database.
21+
``prepare_threshold`` Integer The pgjdbc ``prepareThreshold`` connection property. By default (``5``), the JDBC driver only requests the binary result wire
22+
format after a statement has been server-prepared, so one-shot queries are always decoded as the slower text format. Set to
23+
``-1`` to force the binary format on the first execution. Leave unset to use the pgjdbc default. See the
24+
`pgjdbc documentation <https://jdbc.postgresql.org/documentation/use/>`__ for more information.
2125
``geomesa.metrics.registry`` String Specify the type of registry used to publish metrics. Must be one of ``none``,
2226
``prometheus``, or ``cloudwatch``. See :ref:`geomesa_metrics` for registry details.
2327
``geomesa.metrics.registry.config`` String Override the default registry config. See :ref:`geomesa_metrics` for configuration details.

geomesa-gt/geomesa-gt-partitioning/src/main/scala/org/locationtech/geomesa/gt/partition/postgis/PartitionedPostgisDataStoreFactory.scala

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import scala.util.control.NonFatal
3131
class PartitionedPostgisDataStoreFactory extends PostgisNGDataStoreFactory with LazyLogging {
3232

3333
import JDBCDataStoreFactory.{DATABASE, USER}
34-
import PartitionedPostgisDataStoreParams.{DbType, IdleInTransactionTimeout, PreparedStatements, ReadAccessRoles}
34+
import PartitionedPostgisDataStoreParams.{DbType, IdleInTransactionTimeout, PrepareThreshold, PreparedStatements, ReadAccessRoles}
3535
import org.locationtech.geomesa.index.geotools.GeoMesaDataStoreFactory.{MetricsRegistryConfigParam, MetricsRegistryParam}
3636

3737
import scala.collection.JavaConverters._
@@ -44,7 +44,7 @@ class PartitionedPostgisDataStoreFactory extends PostgisNGDataStoreFactory with
4444

4545
override protected def setupParameters(parameters: java.util.Map[String, AnyRef]): Unit = {
4646
super.setupParameters(parameters)
47-
Seq(DbType, IdleInTransactionTimeout, PreparedStatements, ReadAccessRoles, MetricsRegistryParam, MetricsRegistryConfigParam)
47+
Seq(DbType, IdleInTransactionTimeout, PreparedStatements, ReadAccessRoles, PrepareThreshold, MetricsRegistryParam, MetricsRegistryConfigParam)
4848
.foreach(p => parameters.put(p.key, p))
4949
}
5050

@@ -180,18 +180,25 @@ class PartitionedPostgisDataStoreFactory extends PostgisNGDataStoreFactory with
180180
override protected def createSQLDialect(dataStore: JDBCDataStore, params: java.util.Map[String, _]): SQLDialect =
181181
new PostGISDialect(dataStore)
182182

183-
private def createConnectionOptions(params: java.util.Map[String, _]): Map[String, String] = {
183+
private[postgis] def createConnectionOptions(params: java.util.Map[String, _]): Map[String, String] = {
184184
val options =
185185
Seq(IdleInTransactionTimeout)
186186
.flatMap(p => p.opt(params).map(t => s"-c ${p.key}=${t.millis}"))
187187

188188
logger.debug(s"Connection options: ${options.mkString(" ")}")
189189

190-
if (options.isEmpty) {
191-
Map.empty
192-
} else {
193-
Map("options" -> options.mkString(" "))
194-
}
190+
val serverOptions =
191+
if (options.isEmpty) { Map.empty[String, String] } else { Map("options" -> options.mkString(" ")) }
192+
193+
// prepareThreshold is a pgjdbc driver connection property (not a server GUC), so it is added
194+
// directly rather than through the '-c <guc>' options string. -1 forces binary result wire
195+
// format on the first execution, which one-shot queries never reach with the default of 5.
196+
val driverOptions =
197+
Option(PrepareThreshold.lookUp(params).asInstanceOf[Integer])
198+
.map(v => "prepareThreshold" -> v.toString)
199+
.toMap
200+
201+
serverOptions ++ driverOptions
195202
}
196203
}
197204

geomesa-gt/geomesa-gt-partitioning/src/main/scala/org/locationtech/geomesa/gt/partition/postgis/PartitionedPostgisDataStoreParams.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ object PartitionedPostgisDataStoreParams {
5252
false
5353
)
5454

55+
val PrepareThreshold =
56+
new Param(
57+
"prepare_threshold",
58+
classOf[Integer],
59+
"pgjdbc prepareThreshold connection property. -1 forces binary result wire format on the first " +
60+
"execution, which one-shot queries never reach with the pgjdbc default of 5. Leave unset to use " +
61+
"the pgjdbc default. See https://jdbc.postgresql.org/documentation/use/",
62+
false
63+
)
64+
5565
// note: need a default string constructor so geotools can create it from the param
5666
class Timeout(repr: String) {
5767
private val duration = Duration(repr)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/***********************************************************************
2+
* Copyright (c) 2013-2025 General Atomics Integrated Intelligence, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Apache License, Version 2.0
5+
* which accompanies this distribution and is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
***********************************************************************/
8+
9+
package org.locationtech.geomesa.gt.partition.postgis
10+
11+
import org.junit.runner.RunWith
12+
import org.specs2.mutable.Specification
13+
import org.specs2.runner.JUnitRunner
14+
15+
@RunWith(classOf[JUnitRunner])
16+
class PartitionedPostgisDataStoreFactoryTest extends Specification {
17+
18+
import scala.collection.JavaConverters._
19+
20+
"PartitionedPostgisDataStoreFactory" should {
21+
22+
"advertise the prepare_threshold parameter" in {
23+
// guards the setupParameters registration: if the param is dropped from that Seq,
24+
// GeoTools silently stops exposing it and this fails
25+
val factory = new PartitionedPostgisDataStoreFactory()
26+
val keys = factory.getParametersInfo.map(_.key).toSeq
27+
keys must contain(PartitionedPostgisDataStoreParams.PrepareThreshold.key)
28+
PartitionedPostgisDataStoreParams.PrepareThreshold.key mustEqual "prepare_threshold"
29+
}
30+
31+
"emit prepare_threshold as the pgjdbc driver property prepareThreshold" in {
32+
val factory = new PartitionedPostgisDataStoreFactory()
33+
val params = Map[String, AnyRef](PartitionedPostgisDataStoreParams.PrepareThreshold.key -> Int.box(-1))
34+
val options = factory.createConnectionOptions(params.asJava)
35+
// the datastore key is snake_case but the emitted pgjdbc connection property keeps its own spelling
36+
options.get("prepareThreshold") must beSome("-1")
37+
// it's a driver property, not a server GUC, so it must not leak into the '-c' options string
38+
options.get("options") must beNone
39+
}
40+
41+
"not emit prepareThreshold when the parameter is unset" in {
42+
val factory = new PartitionedPostgisDataStoreFactory()
43+
val options = factory.createConnectionOptions(Map.empty[String, AnyRef].asJava)
44+
options.get("prepareThreshold") must beNone
45+
}
46+
47+
"route idle_in_transaction_session_timeout to a server GUC, not a driver property" in {
48+
// sibling param: proves the driver-property vs. '-c <guc>' split is behaving as intended
49+
val factory = new PartitionedPostgisDataStoreFactory()
50+
val params =
51+
Map[String, AnyRef](PartitionedPostgisDataStoreParams.IdleInTransactionTimeout.key -> "2 minutes")
52+
val options = factory.createConnectionOptions(params.asJava)
53+
options.get("options") must beSome
54+
options("options") must contain("-c idle_in_transaction_session_timeout=120000")
55+
options.get("prepareThreshold") must beNone
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)