All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sqlite.driver.Conn Maven / Gradle / Ivy

The newest version!
/*
 * The author disclaims copyright to this source code.  In place of
 * a legal notice, here is a blessing:
 *
 *    May you do good and not evil.
 *    May you find forgiveness for yourself and forgive others.
 *    May you share freely, never taking more than you give.
 */
package org.sqlite.driver;

import java.nio.charset.Charset;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

import org.sqlite.ConnException;
import org.sqlite.ErrCodes;
import org.sqlite.parser.ast.Release;
import org.sqlite.parser.ast.Rollback;

class Conn implements Connection {
	private org.sqlite.Conn c;
	final String[] dateTimeConfig;

	private DbMeta meta;
	PreparedStatement getGeneratedKeys;

	private Properties clientInfo;
	private int savepointId;
	private SQLWarning warnings;
	private int transactionIsolation = TRANSACTION_SERIALIZABLE;

	Conn(org.sqlite.Conn c, String[] dateTimeConfig, SQLWarning warnings) {
		this.c = c;
		this.dateTimeConfig = dateTimeConfig;
		this.warnings = warnings;
	}

	org.sqlite.Conn getConn() throws SQLException {
		checkOpen();
		return c;
	}

	private void checkOpen() throws SQLException {
		if (c == null) {
			throw new SQLException("Connection closed");
		} else {
			c.checkOpen();
		}
	}

	ResultSet getGeneratedKeys() throws SQLException {
		if (getGeneratedKeys == null) {
			getGeneratedKeys = prepareStatement("select last_insert_rowid()");
		}
		return getGeneratedKeys.executeQuery();
	}

	@Override
	public Statement createStatement() throws SQLException {
		return createStatement(ResultSet.TYPE_FORWARD_ONLY,
				ResultSet.CONCUR_READ_ONLY,
				ResultSet.CLOSE_CURSORS_AT_COMMIT);
	}

	@Override
	public PreparedStatement prepareStatement(String sql) throws SQLException {
		return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
				ResultSet.CONCUR_READ_ONLY);
	}

	@Override
	public CallableStatement prepareCall(String sql) throws SQLException {
		return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY,
				ResultSet.CONCUR_READ_ONLY,
				ResultSet.CLOSE_CURSORS_AT_COMMIT);
	}

	@Override
	public String nativeSQL(String sql) {
		Util.trace("Connection.nativeSQL");
		return sql;
	}

	// FIXME By default, new connections should be in auto-commit mode.
	@Override
	public void setAutoCommit(boolean autoCommit) throws SQLException {
		final org.sqlite.Conn c = getConn();
		if (c.getAutoCommit() == autoCommit) return;
		c.fastExec(autoCommit ? "COMMIT" : "BEGIN");
	}

	@Override
	public boolean getAutoCommit() throws SQLException {
		return getConn().getAutoCommit();
	}

	@Override
	public void commit() throws SQLException {
		final org.sqlite.Conn c = getConn();
		if (c.getAutoCommit()) throw new ConnException(c, "database in auto-commit mode", ErrCodes.WRAPPER_SPECIFIC);
		c.fastExec("COMMIT; BEGIN");
	}

	@Override
	public void rollback() throws SQLException {
		final org.sqlite.Conn c = getConn();
		if (getAutoCommit()) throw new ConnException(c, "database in auto-commit mode", ErrCodes.WRAPPER_SPECIFIC);
		c.fastExec("ROLLBACK; BEGIN");
	}

	@Override
	public void close() throws SQLException {
		if (c != null) {
			if (getGeneratedKeys != null) getGeneratedKeys.close();
			c.close();
			if (clientInfo != null) clientInfo.clear();
			c = null;
		}
	}

	@Override
	public boolean isClosed() {
		return c == null;
	}

	@Override
	public DatabaseMetaData getMetaData() throws SQLException {
		checkOpen();
		if (meta == null) meta = new DbMeta(this);
		return meta;
	}

	@Override
	public void setReadOnly(boolean readOnly) throws SQLException {
		checkOpen();
		final boolean ro = c.isReadOnly(null);
		final boolean qo = c.isQueryOnly(null);
		if (ro == readOnly && qo == readOnly) {
			return;
		}
		if (ro && !readOnly) {
			addWarning(new SQLWarning("readOnly mode cannot be reset"));
		} else {
			if (!c.getAutoCommit()) {
				throw new ConnException(c, "setReadOnly is called during a transaction", ErrCodes.WRAPPER_SPECIFIC);
			}
			c.setQueryOnly(null, readOnly);
		}
	}

	@Override
	public boolean isReadOnly() throws SQLException {
		final org.sqlite.Conn c = getConn();
		return c.isReadOnly(null) || c.isQueryOnly(null);
	}

	@Override
	public void setCatalog(String catalog) throws SQLException {
		checkOpen();
		addWarning(new SQLWarning("catalog cannot be set"));
	}

	@Override
	public String getCatalog() throws SQLException {
		checkOpen();
		return null; // "main" is not the default catalog ("temp" catalog is searched first)
	}

	@Override
	public void setTransactionIsolation(int level) throws SQLException {
		checkOpen();
		if (level == transactionIsolation) {
			return;
		}
		if (level == TRANSACTION_SERIALIZABLE) {
			c.setReadUncommitted(null, false);
		} else if (level == TRANSACTION_READ_UNCOMMITTED) {
			if (!c.isSharedCacheMode()) {
				addWarning(new SQLWarning("read_uncommitted is effective only in shared-cache mode."));
			}
			c.setReadUncommitted(null, true);
		} else {
			throw Util.error("SQLite supports only TRANSACTION_SERIALIZABLE or TRANSACTION_READ_UNCOMMITTED.");
		}
		transactionIsolation = level;
	}

	@Override
	public int getTransactionIsolation() throws SQLException {
		checkOpen();
		return transactionIsolation;
	}

	public void addWarning(SQLWarning warn) {
		if (warnings != null) {
			warnings.setNextWarning(warn);
		} else {
			warnings = warn;
		}
	}

	@Override
	public SQLWarning getWarnings() throws SQLException { // SQLITE_CONFIG_LOG
		checkOpen();
		return warnings;
	}

	@Override
	public void clearWarnings() throws SQLException {
		checkOpen();
		warnings = null;
	}

	@Override
	public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
		return createStatement(resultSetType, resultSetConcurrency,
				ResultSet.CLOSE_CURSORS_AT_COMMIT);
	}

	@Override
	public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
		return prepareStatement(sql, resultSetType, resultSetConcurrency,
				ResultSet.CLOSE_CURSORS_AT_COMMIT);
	}

	@Override
	public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
		return prepareCall(sql, resultSetType, resultSetConcurrency,
				ResultSet.CLOSE_CURSORS_AT_COMMIT);
	}

	@Override
	public Map> getTypeMap() throws SQLException {
		checkOpen();
		throw Util.unsupported("Connection.getTypeMap");
	}

	@Override
	public void setTypeMap(Map> map) throws SQLException {
		checkOpen();
		throw Util.unsupported("Connection.setTypeMap");
	}

	@Override
	public void setHoldability(int holdability) throws SQLException {
		checkOpen();
		if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT)
			throw Util.caseUnsupported("SQLite only supports CLOSE_CURSORS_AT_COMMIT");
	}

	@Override
	public int getHoldability() throws SQLException {
		checkOpen();
		return ResultSet.CLOSE_CURSORS_AT_COMMIT;
	}

	// A SAVEPOINT can be started either within or outside of a BEGIN...COMMIT.
	// When a SAVEPOINT is the outer-most savepoint and it is not within a BEGIN...COMMIT
	// then the behavior is the same as BEGIN DEFERRED TRANSACTION.
	@Override
	public Savepoint setSavepoint() throws SQLException {
		final int id = savepointId++;
		final Savepoint savepoint = new Savepoint() {
			@Override
			public int getSavepointId() {
				return id;
			}

			@Override
			public String getSavepointName() throws SQLException {
				throw new ConnException(c, "un-named savepoint", ErrCodes.WRAPPER_SPECIFIC);
			}

			@Override
			public String toString() {
				return String.valueOf(id);
			}
		};
		getConn().fastExec("SAVEPOINT \"" + id + '"'); // SAVEPOINT 1; fails
		return savepoint;
	}

	@Override
	public Savepoint setSavepoint(final String name) throws SQLException {
		final Savepoint savepoint = new Savepoint() {
			@Override
			public int getSavepointId() throws SQLException {
				throw new ConnException(c, "named savepoint", ErrCodes.WRAPPER_SPECIFIC);
			}

			@Override
			public String getSavepointName() {
				return name;
			}

			@Override
			public String toString() {
				return name;
			}
		};
		org.sqlite.parser.ast.Savepoint sp = new org.sqlite.parser.ast.Savepoint(name);
		getConn().fastExec(sp.toSql());
		return savepoint;
	}

	@Override
	public void rollback(Savepoint savepoint) throws SQLException {
		Rollback rollback = new Rollback(null, savepoint.toString());
		getConn().fastExec(rollback.toSql());
	}

	@Override
	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
		Release release = new Release(savepoint.toString());
		getConn().fastExec(release.toSql());
	}

	@Override
	public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
		checkOpen();
		checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
		return new Stmt(this);
	}

	@Override
	public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
		final org.sqlite.Conn c = getConn();
		checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
		return new PrepStmt(this, c.prepare(sql, true));
	}

	@Override
	public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
		checkOpen();
		throw Util.unsupported("Connection.prepareCall");
	}

	@Override
	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
		checkAutoGeneratedKeys(autoGeneratedKeys);
		return prepareStatement(sql);
	}

	@Override
	public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
		checkOpen();
		if (columnIndexes == null || columnIndexes.length == 0) {
			return prepareStatement(sql);
		}
		throw Util.unsupported("Connection.prepareStatement(String,int[])");
	}

	@Override
	public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
		checkOpen();
		if (columnNames == null || columnNames.length == 0) {
			return prepareStatement(sql);
		}
		throw Util.unsupported("Connection.prepareStatement(String,String[])");
	}

	@Override
	public Clob createClob() throws SQLException {
		checkOpen();
		throw Util.unsupported("Connection.createClob");
	}

	@Override
	public Blob createBlob() throws SQLException { // FIXME ...
		checkOpen();
		throw Util.unsupported("Connection.createBlob");
	}

	@Override
	public NClob createNClob() throws SQLException {
		checkOpen();
		throw Util.unsupported("Connection.createNClob");
	}

	@Override
	public SQLXML createSQLXML() throws SQLException {
		return new SQLXMLImpl(Charset.forName(getConn().encoding(null)));
	}

	@Override
	public boolean isValid(int seconds) throws SQLException {
		Util.trace("Connection.isValid");
		if (seconds < 0) throw Util.error("timeout must be >= 0");
		return !isClosed();
	}

	@Override
	public void setClientInfo(String name, String value) {
		Util.trace("Connection.setClientInfo(String,String)");
		//checkOpen();
		if (clientInfo == null) return;
		clientInfo.setProperty(name, value);
	}

	@Override
	public void setClientInfo(Properties properties) {
		Util.trace("Connection.setClientInfo(Properties)");
		//checkOpen();
		clientInfo = new Properties(properties);
	}

	@Override
	public String getClientInfo(String name) throws SQLException {
		Util.trace("Connection.getClientInfo(String)");
		checkOpen();
		if (clientInfo == null) return null;
		return clientInfo.getProperty(name);
	}

	@Override
	public Properties getClientInfo() throws SQLException {
		Util.trace("Connection.getClientInfo()");
		checkOpen();
		if (clientInfo == null) {
			clientInfo = new Properties();
		}
		return clientInfo;
	}

	@Override
	public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
		checkOpen();
		throw Util.unsupported("Connection.createArrayOf");
	}

	@Override
	public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
		checkOpen();
		throw Util.unsupported("Connection.createStruct");
	}

	//JCP! if mvn.project.property.jdbc.specification.version >= "4.1"
	@Override
	public void setSchema(String schema) throws SQLException {
		checkOpen();
	}

	@Override
	public String getSchema() throws SQLException {
		checkOpen();
		return null;
	}

	@Override
	public void abort(Executor executor) throws SQLException {
		Util.trace("Connection.abort");
		// TODO
	}

	@Override
	public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
		throw Util.unsupported("Connection.setNetworkTimeout");
	}

	@Override
	public int getNetworkTimeout() throws SQLException {
		throw Util.unsupported("Connection.getNetworkTimeout");
	}
	//JCP! endif

	@Override
	public  T unwrap(Class iface) throws SQLException {
		if (iface.isAssignableFrom(getClass())) {
			return iface.cast(this);
		}
		throw new SQLException("Cannot unwrap to " + iface.getName());
	}

	@Override
	public boolean isWrapperFor(Class iface) {
		return iface.isAssignableFrom(getClass());
	}

	private static void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
			throws SQLFeatureNotSupportedException {
		if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) throw Util.caseUnsupported(
				"SQLite only supports TYPE_FORWARD_ONLY cursors");
		if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) throw Util.caseUnsupported(
				"SQLite only supports CONCUR_READ_ONLY cursors");
		if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) throw Util.caseUnsupported(
				"SQLite only supports closing cursors at commit");
	}

	static void checkAutoGeneratedKeys(int autoGeneratedKeys) throws SQLException {
		if (Statement.NO_GENERATED_KEYS != autoGeneratedKeys && Statement.RETURN_GENERATED_KEYS != autoGeneratedKeys) {
			throw new SQLException(String.format("unsupported autoGeneratedKeys value: %d", autoGeneratedKeys));
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy