Skip to content
Merged
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
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Javadocs](http://www.javadoc.io/badge/com.qwazr/jdbc-cache-driver.svg)](http://www.javadoc.io/doc/com.qwazr/jdbc-cache-driver)
[![Coverage Status](https://coveralls.io/repos/github/qwazr/jdbc-cache-driver/badge.svg?branch=master)](https://coveralls.io/github/qwazr/jdbc-cache-driver?branch=master)

JDBC-Driver-Cache is JDBC cache which store the result of a SQL query (ResultSet) in files.
JDBC-Driver-Cache is JDBC cache which store the result of a SQL query (ResultSet) in files or in memory.
The same query requested again will be read from the file, the database is no more requested again.

You may use it to easily mock ResultSets from a database.
Expand Down Expand Up @@ -42,17 +42,29 @@ Class.forName("com.qwazr.jdbc.cache.Driver");
Properties info = new Properties();
info.setProperty("cache.driver.url", "jdbc:derby:memory:myDB;create=true");
info.setProperty("cache.driver.class", "org.apache.derby.jdbc.EmbeddedDriver");
```

Use the file cache implementation:

```java
// Get your JDBC connection
Connection cnx = DriverManager.getConnection("jdbc:cache:file:/var/jdbc/cache", info);
```

Or use the in memory cache implementation:

```java
// Get your JDBC connection
Connection cnx = DriverManager.getConnection("jdbc:cache:mem:my-memory-cache", info);
```

To build a connection you have to provide the URL and some properties.
The URL tells the driver where to store the cached ResultSet.

The syntax of the URL is:
The syntax of the URL can be:

*jdbc:cache:file:{path-to-the-cache-directory}*
* *jdbc:cache:file:{path-to-the-cache-directory}* for on disk cache
* *jdbc:cache:mem:{name-of-the-cache}* for in memory cache

Two possible properties:
- **cache.driver.url** contains the typical JDBC URL of the backend driver.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ class CachedCallableStatement extends CachedPreparedStatement<CallableStatement>

private final SortedMap<String, Object> namedParameters;

CachedCallableStatement(final CachedConnection connection, final ResultSetCacheImpl resultSetCache,
CachedCallableStatement(final CachedConnection connection, final ResultSetCache resultSetCache,
final CallableStatement backendStatement, final String sql, final int resultSetConcurrency,
final int resultSetType, final int resultSetHoldability) {
super(connection, resultSetCache, backendStatement, sql, resultSetConcurrency, resultSetType,
resultSetHoldability);
this.namedParameters = new TreeMap<>();
}

CachedCallableStatement(final CachedConnection connection, final ResultSetCacheImpl resultSetCache,
CachedCallableStatement(final CachedConnection connection, final ResultSetCache resultSetCache,
final CallableStatement backendStatement, final String sql) {
this(connection, resultSetCache, backendStatement, sql, 0, 0, 0);
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/qwazr/jdbc/cache/CachedConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class CachedConnection implements Connection {
private volatile String schema;

private final Connection connection;
private final ResultSetCacheImpl resultSetCache;
private final ResultSetCache resultSetCache;

CachedConnection(final Connection backendConnection, final ResultSetCacheImpl resultSetCache) throws SQLException {
CachedConnection(final Connection backendConnection, final ResultSetCache resultSetCache) throws SQLException {
this.connection = backendConnection;
this.resultSetCache = resultSetCache;
this.autocommit = false;
Expand All @@ -49,7 +49,7 @@ class CachedConnection implements Connection {
this.schema = null;
}

ResultSetCacheImpl getResultSetCache() {
ResultSetCache getResultSetCache() {
return resultSetCache;
}

Expand Down
33 changes: 33 additions & 0 deletions src/main/java/com/qwazr/jdbc/cache/CachedInMemoryResultSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2016-2017 Emmanuel Keller / QWAZR
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.qwazr.jdbc.cache;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.sql.SQLException;

/**
* Uses ByteArrayInputStream/ByteArrayOutputStream as the storage implementation.
* Everything is hold in memory.
* Warning: this is a super naive implementation. Not designed to run in production
* as lot of memory is going to be used by converting to byte[].
*/
class CachedInMemoryResultSet extends CachedResultSet {
CachedInMemoryResultSet(final CachedStatement statement, ByteArrayOutputStream outputStream) throws SQLException {
super(statement, new DataInputStream(new ByteArrayInputStream(outputStream.toByteArray())));
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/qwazr/jdbc/cache/CachedOnDiskResultSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2016-2017 Emmanuel Keller / QWAZR
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.qwazr.jdbc.cache;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.zip.GZIPInputStream;

/**
* Uses disk persistence for caching
*/
class CachedOnDiskResultSet extends CachedResultSet {
CachedOnDiskResultSet(final CachedStatement statement, final Path resultSetPath) throws SQLException, IOException {
super(statement, new DataInputStream(new GZIPInputStream(new FileInputStream(resultSetPath.toFile()))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ class CachedPreparedStatement<T extends PreparedStatement> extends CachedStateme

final SortedMap<Integer, Object> parameters;

CachedPreparedStatement(final CachedConnection connection, final ResultSetCacheImpl resultSetCache,
CachedPreparedStatement(final CachedConnection connection, final ResultSetCache resultSetCache,
final T backendStatement, final String sql, final int resultSetConcurrency, final int resultSetType,
final int resultSetHoldability) {
super(connection, resultSetCache, backendStatement, resultSetConcurrency, resultSetType, resultSetHoldability);
this.parameters = new TreeMap<>();
this.executedSql = sql;
}

CachedPreparedStatement(final CachedConnection connection, final ResultSetCacheImpl resultSetCache,
CachedPreparedStatement(final CachedConnection connection, final ResultSetCache resultSetCache,
final T backendStatement, final String sql) {
this(connection, resultSetCache, backendStatement, sql, 0, 0, 0);
}
Expand All @@ -55,7 +55,7 @@ protected void generateKey() throws SQLException {
@Override
public ResultSet executeQuery() throws SQLException {
generateKey();
return resultSetCache.get(this, generatedKey, backendStatement != null ? backendStatement::executeQuery : null);
return resultSetCache.get(this, generatedKey, backendStatement != null ? () -> backendStatement.executeQuery() : null);
}

@Override
Expand Down
40 changes: 28 additions & 12 deletions src/main/java/com/qwazr/jdbc/cache/CachedResultSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,40 @@
*/
package com.qwazr.jdbc.cache;

import java.io.*;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.sql.*;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;

class CachedResultSet implements ResultSet {
/**
* Cached ResultSet
*/
abstract class CachedResultSet implements ResultSet {

private final CachedStatement statement;
private final DataInputStream input;
Expand All @@ -41,17 +61,13 @@ class CachedResultSet implements ResultSet {
private volatile int nextPos;
private volatile boolean closed;

CachedResultSet(final CachedStatement statement, final Path resultSetPath) throws SQLException {
CachedResultSet(final CachedStatement statement, DataInputStream input) throws SQLException {
this.statement = statement;
this.wasNull = false;
this.currentPos = 0;
this.nextPos = 0;
this.closed = false;
try {
this.input = new DataInputStream(new GZIPInputStream(new FileInputStream(resultSetPath.toFile())));
} catch (IOException e) {
throw new SQLException("Error while opening the file " + resultSetPath, e);
}
this.input = input;
try {
this.metaData = new CachedResultSetMetaData(ResultSetWriter.readColumns(input));
this.currentRow = new Object[metaData.columns.length];
Expand All @@ -67,7 +83,7 @@ class CachedResultSet implements ResultSet {
} catch (Exception ex) {
//Close quietly
}
throw new SQLException("Cannot read the cache: " + resultSetPath, e);
throw new SQLException("Cannot read the cache for statement " + statement, e);
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/qwazr/jdbc/cache/CachedStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.qwazr.jdbc.cache;

import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
Expand All @@ -28,7 +29,7 @@
class CachedStatement<T extends Statement> implements Statement {

private final CachedConnection connection;
final ResultSetCacheImpl resultSetCache;
final ResultSetCache resultSetCache;
final T backendStatement;

private final int resultSetConcurrency;
Expand All @@ -47,7 +48,7 @@ class CachedStatement<T extends Statement> implements Statement {
volatile String executedSql;
volatile String generatedKey;

CachedStatement(final CachedConnection connection, final ResultSetCacheImpl resultSetCache,
CachedStatement(final CachedConnection connection, final ResultSetCache resultSetCache,
final T backendStatement, final int resultSetConcurrency, final int resultSetType,
final int resultSetHoldability) {
this.connection = connection;
Expand All @@ -67,7 +68,7 @@ class CachedStatement<T extends Statement> implements Statement {
this.executedSql = null;
}

CachedStatement(final CachedConnection connection, final ResultSetCacheImpl resultSetCache,
CachedStatement(final CachedConnection connection, final ResultSetCache resultSetCache,
final T backendStatement) {
this(connection, resultSetCache, backendStatement, 0, 0, 0);
}
Expand Down
40 changes: 24 additions & 16 deletions src/main/java/com/qwazr/jdbc/cache/Driver.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public class Driver implements java.sql.Driver {

final static Logger LOGGER = Logger.getLogger(Driver.class.getPackage().getName());

public final static String URL_PREFIX = "jdbc:cache:file:";
public final static String URL_FILE_PREFIX = "jdbc:cache:file:";
public final static String URL_MEM_PREFIX = "jdbc:cache:mem:";
public final static String CACHE_DRIVER_URL = "cache.driver.url";
public final static String CACHE_DRIVER_CLASS = "cache.driver.class";
public final static String CACHE_DRIVER_ACTIVE = "cache.driver.active";
Expand All @@ -44,7 +45,7 @@ public class Driver implements java.sql.Driver {
}
}

private final ConcurrentHashMap<Path, ResultSetCacheImpl> resultSetCacheMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, ResultSetCache> resultSetCacheMap = new ConcurrentHashMap<>();

public Connection connect(String url, Properties info) throws SQLException {

Expand All @@ -68,28 +69,35 @@ public Connection connect(String url, Properties info) throws SQLException {
null :
DriverManager.getConnection(cacheDriverUrl, info);

if (url.length() <= URL_PREFIX.length())
throw new SQLException("The path is empty: " + url);

if (!active)
if (!active) {
return new CachedConnection(backendConnection, null);
}

// Check the cache directory
final Path cacheDirectory = FileSystems.getDefault().getPath(url.substring(URL_PREFIX.length()));

final ResultSetCacheImpl resultSetCache;

try {
resultSetCache = resultSetCacheMap.computeIfAbsent(cacheDirectory, ResultSetCacheImpl::new);
} catch (CacheException e) {
throw e.getSQLException();
final ResultSetCache resultSetCache;
if (url.startsWith(URL_FILE_PREFIX)) {
if (url.length() <= URL_FILE_PREFIX.length()) {
throw new SQLException("The path is empty: " + url);
}
// Check the cache directory
final String cacheName = url.substring(URL_FILE_PREFIX.length());
final Path cacheDirectory = FileSystems.getDefault().getPath(cacheName);
resultSetCache = resultSetCacheMap.computeIfAbsent(cacheName, (foo) -> new ResultSetOnDiskCacheImpl(cacheDirectory));
} else if (url.startsWith(URL_MEM_PREFIX)) {
if (url.length() <= URL_MEM_PREFIX.length()) {
throw new SQLException("The name is empty: " + url);
}
// Check the cache directory
final String cacheName = url.substring(URL_MEM_PREFIX.length());
resultSetCache = resultSetCacheMap.computeIfAbsent(cacheName, (foo) -> new ResultSetInMemoryCacheImpl());
} else {
throw new IllegalArgumentException("Can not find cache implementation for " + url);
}

return new CachedConnection(backendConnection, resultSetCache);
}

public boolean acceptsURL(String url) throws SQLException {
return url != null && url.startsWith(URL_PREFIX);
return url != null && (url.startsWith(URL_FILE_PREFIX) || url.startsWith(URL_MEM_PREFIX));
}

public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) throws SQLException {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/qwazr/jdbc/cache/ResultSetCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.qwazr.jdbc.cache;

import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

Expand Down Expand Up @@ -58,4 +60,12 @@ public interface ResultSetCache {
* @return true if a cache entry is currently build for the given statement
*/
boolean active(Statement stmt) throws SQLException;

<T extends Statement> ResultSet get(CachedStatement statement, String key, Provider s) throws SQLException;

boolean checkIfExists(String key);

interface Provider {
ResultSet provide() throws SQLException, IOException;
}
}
Loading