Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
features/sql: read result sets with plain JDBC instead of rxjava3-jdbc by cportele · Pull Request #625 · ldproxy/xtraplatform-spatial · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' features/sql: read result sets with plain JDBC instead of rxjava3-jdbc by cportele · Pull Request #625 · ldproxy/xtraplatform-spatial · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' features/sql: read result sets with plain JDBC instead of rxjava3-jdbc by cportele · Pull Request #625 · ldproxy/xtraplatform-spatial · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' features/sql: read result sets with plain JDBC instead of rxjava3-jdbc by cportele · Pull Request #625 · ldproxy/xtraplatform-spatial · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' features/sql: read result sets with plain JDBC instead of rxjava3-jdbc by cportele · Pull Request #625 · ldproxy/xtraplatform-spatial · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' features/sql: read result sets with plain JDBC instead of rxjava3-jdbc by cportele · Pull Request #625 · ldproxy/xtraplatform-spatial · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); features/sql: read result sets with plain JDBC instead of rxjava3-jdbc by cportele · Pull Request #625 · ldproxy/xtraplatform-spatial · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ antlr = '4.13.2'
xsom = '20140925'
aalto = '1.4.0'
staxmate = '2.4.2'
rxjava-jdbc = '0.1.4-ii.1'
hikaricp = '7.1.0'
postgresql = '42.7.13'
sqlite = '3.53.4.0'
Expand All@@ -28,7 +27,6 @@ aalto = { module = "com.fasterxml:aalto-xml", version.ref = "aalto" }
staxmate = { module = "com.fasterxml.staxmate:staxmate", version.ref = "staxmate" }

# sql
rxjava-jdbc = { module = "com.github.davidmoten:rxjava3-jdbc", version.ref = "rxjava-jdbc" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -251,11 +250,6 @@ public List<String> getDefaultSchemas() {
return List.of();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.OTHER;
}

@Override
public List<String> getSystemSchemas() {
return List.of("public");
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,6 @@
import java.util.Optional;
import java.util.Set;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand DownExpand Up@@ -128,11 +127,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.empty();
}

@Override
public DatabaseType getRxType() {
return DatabaseType.ORACLE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of("public");
Expand Down
7 changes: 0 additions & 7 deletions xtraplatform-features-sql/build.gradle
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,13 +23,6 @@ dependencies {
provided project(':xtraplatform-geometries')
provided project(':xtraplatform-strings')

embedded(libs.rxjava.jdbc) {
exclude module: 'rxjava'
exclude module: 'reactive-streams'
exclude module: 'commons-io'
exclude module: 'slf4j-api'
exclude group: 'com.google.code.findbugs'
}
//use reactive-streams + rxjava exported from this
embeddedImport 'de.interactive_instruments:xtraplatform-streams'

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,6 @@
import java.util.Optional;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;

@AutoMultiBind
Expand All@@ -32,8 +31,6 @@ public interface SqlDbmsAdapter {

List<String> getDefaultSchemas();

DatabaseType getRxType();

List<String> getSystemSchemas();

List<String> getSystemTables();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.Collator;
Expand All@@ -34,7 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.Database;
import org.postgresql.PGConnection;
import org.postgresql.PGNotification;
import org.slf4j.Logger;
Expand All@@ -50,21 +50,16 @@ public class SqlClientRx implements SqlClient {
// it exists to end.
private static final long READ_STALL_TIMEOUT_MINUTES = 10;

// rxjava3-jdbc is used for streamed reads only; everything that needs a connection of its own
// (sessions, statements without a result) leases it from the pool directly
private final Database session;
private final DataSource dataSource;
private final SqlDbmsAdapter dbmsAdapter;
private final SqlDialect dialect;
private final Collator collator;

public SqlClientRx(
Database session,
DataSource dataSource,
SqlDbmsAdapter dbmsAdapter,
SqlDialect dialect,
Optional<String> defaultCollation) {
this.session = session;
this.dataSource = dataSource;
this.dbmsAdapter = dbmsAdapter;
this.dialect = dialect;
Expand DownExpand Up@@ -92,11 +87,19 @@ public CompletableFuture<Collection<SqlRow>> run(String query, SqlQueryOptions o
return result;
}

session
.select(query)
.get(resultSet -> new SqlRowVals(collator).read(resultSet, options))
.toList()
.subscribe(result::complete, result::completeExceptionally);
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query)) {
List<SqlRow> rows = new ArrayList<>();

while (resultSet.next()) {
rows.add(new SqlRowVals(collator).read(resultSet, options));
}

result.complete(rows);
} catch (SQLException | RuntimeException e) {
result.completeExceptionally(e);
}

return result;
}
Expand All@@ -108,31 +111,35 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
}
List<SqlRow> logBuffer = new ArrayList<>(5);

org.davidmoten.rxjava3.jdbc.ResultSetMapper<SqlRow> mapper =
resultSet -> {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

return row;
};

// A positive fetch size requires a transaction so the database driver uses a server-side cursor
// and streams rows instead of buffering the whole result set in memory (PostgreSQL ignores the
// fetch size with autoCommit=true).
// TODO encapsulating the query in a transaction is also a workaround for what appears to be a
// bug in rxjava3-jdbc, see https://github.com/interactive-instruments/ldproxy/issues/1293
boolean streamed = options.getFetchSize() > 0;

// The connection is leased when the stream is subscribed and returned to the pool when it
// terminates, whether the rows were exhausted, the read failed or the consumer cancelled.
Flowable<SqlRow> flowable =
options.getFetchSize() > 0
? session
.select(query)
.transacted()
.fetchSize(options.getFetchSize())
.valuesOnly()
.get(mapper)
: session.select(query).get(mapper);
Flowable.using(
() -> lease(streamed),
connection ->
Flowable.generate(
() -> execute(connection, query, options.getFetchSize()),
(resultSet, emitter) -> {
if (resultSet.next()) {
SqlRow row = new SqlRowVals(collator).read(resultSet, options);

if (LOGGER.isDebugEnabled(MARKER.SQL_RESULT) && logBuffer.size() < 10) {
logBuffer.add(row);
}

emitter.onNext(row);
} else {
emitter.onComplete();
}
},
SqlClientRx::close),
connection -> release(connection, streamed),
true);

// TODO: prettify, see
// https://github.com/slick/slick/blob/main/slick/src/main/scala/slick/jdbc/StatementInvoker.scala
Expand DownExpand Up@@ -169,20 +176,17 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
});
}

// The blocking connection provider runs connect+execute+read on the subscribing thread, so
// without this the whole stream is single-threaded. Subscribing on a worker thread lets several
// parallel-flagged queries (e.g. the concurrent single-shot value phase) run at once, each on
// its
// own connection.
// Lease, execute and read run on the subscribing thread, so without this the whole stream is
// single-threaded. Subscribing on a worker thread lets several parallel-flagged queries (e.g.
// the concurrent single-shot value phase) run at once, each on its own connection.
if (options.isParallel()) {
flowable = flowable.subscribeOn(Schedulers.io());
}

// Safety net for a read that neither completes nor fails. A database error raised while the
// rows are being streamed is not delivered by the underlying library (see the issue linked
// above), so the stream can stall forever: no error is logged, no response is sent, and the
// connections the sub-query holds stay held until the client gives up. A connection lost
// mid-stream — a failover in a replicated cluster, for instance — looks exactly the same.
// Safety net for a read that neither completes nor fails: a connection lost mid-stream — a
// failover in a replicated cluster, for instance — can leave the driver waiting for the next
// row forever, so no error is logged, no response is sent, and the connections the sub-query
// holds stay held until the client gives up.
// The timeout is per element, not per stream, so a slow but progressing read is unaffected
// however long it runs in total; only a gap longer than the window ends the stream, with an
// error that does propagate. A database-side statement_timeout is no substitute: its error
Expand All@@ -203,6 +207,80 @@ public Reactive.Source<SqlRow> getSourceStream(String query, SqlQueryOptions opt
return Reactive.Source.publisher(flowable);
}

private Connection lease(boolean transaction) throws SQLException {
Connection connection = dataSource.getConnection();

if (transaction) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
close(connection);
throw e;
}
}

return connection;
}

private static ResultSet execute(Connection connection, String query, int fetchSize)
throws SQLException {
Statement statement = connection.createStatement();

try {
if (fetchSize > 0) {
statement.setFetchSize(fetchSize);
}

return statement.executeQuery(query);
} catch (SQLException | RuntimeException e) {
close(statement);
throw e;
}
}

/** Ends a read-only transaction, if any, and returns the connection to the pool. */
private static void release(Connection connection, boolean transaction) {
if (transaction) {
try {
// nothing to commit, and a rollback is the cheapest way to close the server-side cursor
connection.rollback();
} catch (SQLException e) {
LOGGER.debug("Ending the read transaction failed: {}", e.getMessage());
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.debug("Resetting autocommit failed: {}", e.getMessage());
}
}

close(connection);
}

/** Closes the result set and the statement it belongs to. */
private static void close(ResultSet resultSet) {
Statement statement = null;

try {
statement = resultSet.getStatement();
} catch (SQLException e) {
// the result set is closed below regardless
}

close((AutoCloseable) resultSet);
close(statement);
}

private static void close(AutoCloseable closeable) {
if (Objects.nonNull(closeable)) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.debug("Closing {} failed: {}", closeable.getClass().getSimpleName(), e.getMessage());
}
}
}

@Override
public Connection getConnection() {
return leaseConnection();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,6 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.davidmoten.rxjava3.jdbc.Database;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
Expand DownExpand Up@@ -80,7 +79,6 @@ public class SqlConnectorRx extends AbstractVolatilePolling implements SqlConnec
private final AtomicInteger refCounter;
private final boolean asyncStartup;

private Database session;
private HikariDataSource dataSource;
private SqlClient sqlClient;
private Throwable connectionError;
Expand DownExpand Up@@ -166,10 +164,8 @@ public void start() {
try {
HikariConfig hikariConfig = createHikariConfig();
this.dataSource = new HikariDataSource(hikariConfig);
this.session = createSession(dataSource);
this.sqlClient =
new SqlClientRx(
session,
dataSource,
dbmsAdapters.get(connectionInfo.getDialect()),
dbmsAdapters.getDialect(connectionInfo.getDialect()),
Expand All@@ -192,13 +188,6 @@ public void stop() {
// ignore
}
}
if (Objects.nonNull(session)) {
try {
session.close();
} catch (Throwable e) {
// ignore
}
}
if (Objects.nonNull(dataSource)) {
try {
dataSource.close();
Expand DownExpand Up@@ -350,10 +339,6 @@ private HikariConfig createHikariConfig() {
return config;
}

private Database createSession(HikariDataSource dataSource) {
return Database.fromBlocking(dataSource);
}

private static long getInitFailTimeout(ConnectionInfoSql connectionInfo) {
return parseMs(Objects.requireNonNullElse(connectionInfo.getPool().getInitFailTimeout(), "1"));
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,6 @@
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import org.davidmoten.rxjava3.jdbc.pool.DatabaseType;
import org.immutables.value.Value;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteDataSource;
Expand DownExpand Up@@ -140,11 +139,6 @@ public Optional<String> getInitSql(ConnectionInfoSql connectionInfo) {
return Optional.of("SELECT CASE CheckGeoPackageMetaData() WHEN 1 THEN EnableGpkgMode() END;");
}

@Override
public DatabaseType getRxType() {
return DatabaseType.SQLITE;
}

@Override
public List<String> getDefaultSchemas() {
return List.of();
Expand Down
Loading
Loading