Skip to content

Use extended error codes #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 1, 2016
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
50 changes: 49 additions & 1 deletion src/main/java/org/sqlite/SQLiteErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
//--------------------------------------
package org.sqlite;

import java.sql.SQLException;

/**
* SQLite3 error code
*
Expand Down Expand Up @@ -63,7 +65,53 @@ public enum SQLiteErrorCode {
SQLITE_RANGE(25, " 2nd parameter to sqlite3_bind out of range"),
SQLITE_NOTADB(26, " File opened that is not a database file"),
SQLITE_ROW(100, " sqlite3_step() has another row ready"),
SQLITE_DONE(101, " sqlite3_step() has finished executing");
SQLITE_DONE(101, " sqlite3_step() has finished executing"),
/* Beginning of extended error codes */
SQLITE_BUSY_RECOVERY(261, " Another process is busy recovering a WAL mode database file following a crash"),
SQLITE_LOCKED_SHAREDCACHE(262, " Contention with a different database connection that shares the cache"),
SQLITE_READONLY_RECOVERY(264, " The database file needs to be recovered"),
SQLITE_IOERR_READ(266, " I/O error in the VFS layer while trying to read from a file on disk"),
SQLITE_CORRUPT_VTAB(267, " Content in the virtual table is corrupt"),
SQLITE_CONSTRAINT_CHECK(275, " A CHECK constraint failed"),
SQLITE_ABORT_ROLLBACK(516, " The transaction that was active when the SQL statement first started was rolled back"),
SQLITE_BUSY_SNAPSHOT(517, " Another database connection has already written to the database"),
SQLITE_READONLY_CANTLOCK(520, " The shared-memory file associated with that database is read-only"),
SQLITE_IOERR_SHORT_READ(522, " The VFS layer was unable to obtain as many bytes as was requested"),
SQLITE_CANTOPEN_ISDIR(526, " The file is really a directory"),
SQLITE_CONSTRAINT_COMMITHOOK(531, " A commit hook callback returned non-zero"),
SQLITE_READONLY_ROLLBACK(776, " Hot journal needs to be rolled back"),
SQLITE_IOERR_WRITE(778, " I/O error in the VFS layer while trying to write to a file on disk"),
SQLITE_CANTOPEN_FULLPATH(782, " The operating system was unable to convert the filename into a full pathname"),
SQLITE_CONSTRAINT_FOREIGNKEY(787, " A foreign key constraint failed"),
SQLITE_READONLY_DBMOVED(1032, " The database file has been moved since it was opened"),
SQLITE_IOERR_FSYNC(1034, " I/O error in the VFS layer while trying to flush previously written content"),
SQLITE_CANTOPEN_CONVPATH(1038, " cygwin_conv_path() system call failed while trying to open a file"),
SQLITE_CONSTRAINT_FUNCTION(1043, " Error reported by extension function"),
SQLITE_IOERR_DIR_FSYNC(1290, " I/O error in the VFS layer while trying to invoke fsync() on a directory"),
SQLITE_CONSTRAINT_NOTNULL(1299, " A NOT NULL constraint failed"),
SQLITE_IOERR_TRUNCATE(1546, " I/O error in the VFS layer while trying to truncate a file to a smaller size"),
SQLITE_CONSTRAINT_PRIMARYKEY(1555, " A PRIMARY KEY constraint failed"),
SQLITE_IOERR_FSTAT(1802, " I/O error in the VFS layer while trying to invoke fstat()"),
SQLITE_CONSTRAINT_TRIGGER(1811, " A RAISE function within a trigger fired, causing the SQL statement to abort"),
SQLITE_IOERR_UNLOCK(2058, " I/O error within xUnlock"),
SQLITE_CONSTRAINT_UNIQUE(2067, " A UNIQUE constraint failed"),
SQLITE_IOERR_RDLOCK(2314, " I/O error within xLock"),
SQLITE_CONSTRAINT_VTAB(2323, " Error reported by application-defined virtual table"),
SQLITE_IOERR_DELETE(2570, " I/O error within xDelete"),
SQLITE_CONSTRAINT_ROWID(2579, " rowid is not unique"),
SQLITE_IOERR_NOMEM(3082, " Unable to allocate sufficient memory"),
SQLITE_IOERR_ACCESS(3338, " I/O error within the xAccess"),
SQLITE_IOERR_CHECKRESERVEDLOCK(3594, " I/O error within xCheckReservedLock"),
SQLITE_IOERR_LOCK(3850, " I/O error in the advisory file locking logic"),
SQLITE_IOERR_CLOSE(4106, " I/O error within xClose"),
SQLITE_IOERR_SHMOPEN(4618, " I/O error within xShmMap while trying to open a new shared memory segment"),
SQLITE_IOERR_SHMSIZE(4874, " I/O error within xShmMap while trying to resize an existing shared memory segment"),
SQLITE_IOERR_SHMMAP(5386, " I/O error within xShmMap while trying to map a shared memory segment"),
SQLITE_IOERR_SEEK(5642, " I/O error while trying to seek a file descriptor"),
SQLITE_IOERR_DELETE_NOENT(5898, " The file being deleted does not exist"),
SQLITE_IOERR_MMAP(6154, " I/O error while trying to map or unmap part of the database file"),
SQLITE_IOERR_GETTEMPPATH(6410, " Unable to determine a suitable directory in which to place temporary files"),
SQLITE_IOERR_CONVPATH(6666, " cygwin_conv_path() system call failed");

public final int code;
public final String message;
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/org/sqlite/SQLiteException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*--------------------------------------------------------------------------
* Copyright 2016 Magnus Reftel
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*--------------------------------------------------------------------------*/
//--------------------------------------
// sqlite-jdbc Project
//
// SQLiteException.java
// Since: Jun 28, 2016
//
// $URL$
// $Author$
//--------------------------------------
package org.sqlite;

import java.sql.SQLException;
import org.sqlite.SQLiteErrorCode;

public class SQLiteException extends SQLException {
private SQLiteErrorCode resultCode;

public SQLiteException(String message, SQLiteErrorCode resultCode) {
super(message, null, resultCode.code & 0xff);
this.resultCode = resultCode;
}

public SQLiteErrorCode getResultCode() {
return resultCode;
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/sqlite/core/CoreStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ protected void internalClose() throws SQLException {
int resp = db.finalize(this);

if (resp != SQLITE_OK && resp != SQLITE_MISUSE)
db.throwex();
db.throwex(resp);
}

public abstract ResultSet executeQuery(String sql, boolean closeStmt) throws SQLException;
Expand Down
33 changes: 20 additions & 13 deletions src/main/java/org/sqlite/core/DB.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.sqlite.Function;
import org.sqlite.SQLiteConnection;
import org.sqlite.SQLiteErrorCode;
import org.sqlite.SQLiteException;

/*
* This class is the interface to SQLite. It provides some helper functions
Expand Down Expand Up @@ -132,14 +133,15 @@ public final synchronized void exec(String sql) throws SQLException {
long pointer = 0;
try {
pointer = prepare(sql);
switch (step(pointer)) {
int rc = step(pointer);
switch (rc) {
case SQLITE_DONE:
ensureAutoCommit();
return;
case SQLITE_ROW:
return;
default:
throwex();
throwex(rc);
}
}
finally {
Expand Down Expand Up @@ -749,8 +751,9 @@ final synchronized int[] executeBatch(long stmt, int count, Object[] vals) throw
for (int i = 0; i < count; i++) {
reset(stmt);
for (int j = 0; j < params; j++) {
if (sqlbind(stmt, j, vals[(i * params) + j]) != SQLITE_OK) {
throwex();
rc = sqlbind(stmt, j, vals[(i * params) + j]);
if (rc != SQLITE_OK) {
throwex(rc);
}
}

Expand All @@ -760,7 +763,7 @@ final synchronized int[] executeBatch(long stmt, int count, Object[] vals) throw
if (rc == SQLITE_ROW) {
throw new BatchUpdateException("batch entry " + i + ": query returns results", changes);
}
throwex();
throwex(rc);
}

changes[i] = changes();
Expand Down Expand Up @@ -790,8 +793,9 @@ public final synchronized boolean execute(CoreStatement stmt, Object[] vals) thr
}

for (int i = 0; i < params; i++) {
if (sqlbind(stmt.pointer, i, vals[i]) != SQLITE_OK) {
throwex();
int rc = sqlbind(stmt.pointer, i, vals[i]);
if (rc != SQLITE_OK) {
throwex(rc);
}
}
}
Expand Down Expand Up @@ -880,7 +884,7 @@ public final void throwex(int errorCode) throws SQLException {
* @param errorMessage Error message to be passed.
* @throws SQLException
*/
final void throwex(int errorCode, String errorMessage) throws SQLException {
static final void throwex(int errorCode, String errorMessage) throws SQLiteException {
throw newSQLException(errorCode, errorMessage);
}

Expand All @@ -891,9 +895,11 @@ final void throwex(int errorCode, String errorMessage) throws SQLException {
* @return Formated SQLException with error code and message.
* @throws SQLException
*/
public static SQLException newSQLException(int errorCode, String errorMessage) throws SQLException {
public static SQLiteException newSQLException(int errorCode, String errorMessage) {
SQLiteErrorCode code = SQLiteErrorCode.getErrorCode(errorCode);
SQLException e = new SQLException(String.format("%s (%s)", code, errorMessage), null, code.code);
SQLiteException e = new SQLiteException(
String.format("%s (%s)", code, errorMessage), code
);
return e;
}

Expand All @@ -903,7 +909,7 @@ public static SQLException newSQLException(int errorCode, String errorMessage) t
* @return SQLException with error code and message.
* @throws SQLException
*/
private SQLException newSQLException(int errorCode) throws SQLException {
private SQLiteException newSQLException(int errorCode) throws SQLException {
return newSQLException(errorCode, errmsg());
}

Expand Down Expand Up @@ -956,9 +962,10 @@ final void ensureAutoCommit() throws SQLException {
{
return; // assume we are in a transaction
}
if (step(commit) != SQLITE_DONE) {
int rc = step(commit);
if (rc != SQLITE_DONE) {
reset(commit);
throwex();
throwex(rc);
}
//throw new SQLException("unable to auto-commit");
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/sqlite/core/NativeDB.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB__1open(
return;
}

// Ignore failures, as we can tolerate regular result codes.
(void) sqlite3_extended_result_codes(db, 1);

sethandle(env, this, db);
}

Expand Down
1 change: 1 addition & 0 deletions src/test/java/org/sqlite/AllTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
BackupTest.class,
ConnectionTest.class,
DBMetaDataTest.class,
ErrorMessageTest.class,
ExtendedCommandTest.class,
ExtensionTest.class,
FetchSizeTest.class,
Expand Down
136 changes: 136 additions & 0 deletions src/test/java/org/sqlite/ErrorMessageTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package org.sqlite;

import static org.junit.Assume.assumeTrue;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Rule;
import org.junit.Test;
import org.junit.matchers.JUnitMatchers;
import org.junit.rules.ExpectedException;

public class ErrorMessageTest {
@Rule
public ExpectedException thrown = ExpectedException.none();

static class VendorCodeMatcher extends BaseMatcher<Object> {
final SQLiteErrorCode expected;

VendorCodeMatcher(SQLiteErrorCode expected) {this.expected = expected;}

public boolean matches(Object o) {
if (!(o instanceof SQLException)) {
return false;
}
SQLException e = (SQLException)o;
SQLiteErrorCode ec = SQLiteErrorCode.getErrorCode(e.getErrorCode());
return ec == expected;
}

public void describeTo(Description description) {
description
.appendText("SQLException with error code ")
.appendText(expected.name())
.appendText(" (")
.appendValue(expected.code)
.appendText(")");
}
}

static class ResultCodeMatcher extends BaseMatcher<Object> {
final SQLiteErrorCode expected;

ResultCodeMatcher(SQLiteErrorCode expected) {this.expected = expected;}

public boolean matches(Object o) {
if (!(o instanceof SQLiteException)) {
return false;
}
SQLiteException e = (SQLiteException)o;
return e.getResultCode() == expected;
}

public void describeTo(Description description) {
description
.appendText("SQLiteException with error code ")
.appendText(expected.name())
.appendText(" (")
.appendValue(expected.code)
.appendText(")");
}
}

@Test
public void moved() throws SQLException, IOException {
File from = File.createTempFile("error-message-test-moved-from", ".sqlite");
from.deleteOnExit();

Connection conn = DriverManager.getConnection("jdbc:sqlite:" + from.getAbsolutePath());
Statement stmt = conn.createStatement();
stmt.executeUpdate("create table sample(id, name)");
stmt.executeUpdate("insert into sample values(1, \"foo\")");

File to = File.createTempFile("error-message-test-moved-from", ".sqlite");
assumeTrue(to.delete());
assumeTrue(from.renameTo(to));

thrown.expectMessage(JUnitMatchers.containsString("[SQLITE_READONLY_DBMOVED]"));
stmt.executeUpdate("insert into sample values(2, \"bar\")");

stmt.close();
conn.close();
}

@Test
public void writeProtected() throws SQLException, IOException {
File file = File.createTempFile("error-message-test-write-protected", ".sqlite");
file.deleteOnExit();

Connection conn = DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath());
Statement stmt = conn.createStatement();
stmt.executeUpdate("create table sample(id, name)");
stmt.executeUpdate("insert into sample values(1, \"foo\")");
stmt.close();
conn.close();

assumeTrue(file.setReadOnly());

conn = DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath());
stmt = conn.createStatement();
thrown.expectMessage(JUnitMatchers.containsString("[SQLITE_READONLY]"));
stmt.executeUpdate("insert into sample values(2, \"bar\")");
stmt.close();
conn.close();
}

@Test
public void shouldUsePlainErrorCodeAsVendorCodeAndExtendedAsResultCode() throws SQLException, IOException {
File from = File.createTempFile("error-message-test-plain-1", ".sqlite");
from.deleteOnExit();

Connection conn = DriverManager.getConnection("jdbc:sqlite:" + from.getAbsolutePath());
Statement stmt = conn.createStatement();
stmt.executeUpdate("create table sample(id, name)");
stmt.executeUpdate("insert into sample values(1, \"foo\")");

File to = File.createTempFile("error-message-test-plain-2", ".sqlite");
assumeTrue(to.delete());
assumeTrue(from.renameTo(to));

thrown.expectMessage(JUnitMatchers.containsString("[SQLITE_READONLY_DBMOVED]"));
thrown.expect(new VendorCodeMatcher(SQLiteErrorCode.SQLITE_READONLY));
thrown.expect(new ResultCodeMatcher(SQLiteErrorCode.SQLITE_READONLY_DBMOVED));
stmt.executeUpdate("insert into sample values(2, \"bar\")");

stmt.close();
conn.close();
}
}