Skip to content

Commit 7509994

Browse files
committed
Use extended result codes
1 parent 4484854 commit 7509994

File tree

7 files changed

+251
-15
lines changed

7 files changed

+251
-15
lines changed

src/main/java/org/sqlite/SQLiteErrorCode.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
//--------------------------------------
2525
package org.sqlite;
2626

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

68116
public final int code;
69117
public final String message;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*--------------------------------------------------------------------------
2+
* Copyright 2016 Magnus Reftel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*--------------------------------------------------------------------------*/
16+
//--------------------------------------
17+
// sqlite-jdbc Project
18+
//
19+
// SQLiteException.java
20+
// Since: Jun 28, 2016
21+
//
22+
// $URL$
23+
// $Author$
24+
//--------------------------------------
25+
package org.sqlite;
26+
27+
import java.sql.SQLException;
28+
import org.sqlite.SQLiteErrorCode;
29+
30+
public class SQLiteException extends SQLException {
31+
private SQLiteErrorCode resultCode;
32+
33+
public SQLiteException(String message, SQLiteErrorCode resultCode) {
34+
super(message, null, resultCode.code & 0xff);
35+
this.resultCode = resultCode;
36+
}
37+
38+
public SQLiteErrorCode getResultCode() {
39+
return resultCode;
40+
}
41+
}

src/main/java/org/sqlite/core/CoreStatement.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ protected void internalClose() throws SQLException {
123123
int resp = db.finalize(this);
124124

125125
if (resp != SQLITE_OK && resp != SQLITE_MISUSE)
126-
db.throwex();
126+
db.throwex(resp);
127127
}
128128

129129
public abstract ResultSet executeQuery(String sql, boolean closeStmt) throws SQLException;

src/main/java/org/sqlite/core/DB.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.sqlite.Function;
2525
import org.sqlite.SQLiteConnection;
2626
import org.sqlite.SQLiteErrorCode;
27+
import org.sqlite.SQLiteException;
2728

2829
/*
2930
* This class is the interface to SQLite. It provides some helper functions
@@ -132,14 +133,15 @@ public final synchronized void exec(String sql) throws SQLException {
132133
long pointer = 0;
133134
try {
134135
pointer = prepare(sql);
135-
switch (step(pointer)) {
136+
int rc = step(pointer);
137+
switch (rc) {
136138
case SQLITE_DONE:
137139
ensureAutoCommit();
138140
return;
139141
case SQLITE_ROW:
140142
return;
141143
default:
142-
throwex();
144+
throwex(rc);
143145
}
144146
}
145147
finally {
@@ -749,8 +751,9 @@ final synchronized int[] executeBatch(long stmt, int count, Object[] vals) throw
749751
for (int i = 0; i < count; i++) {
750752
reset(stmt);
751753
for (int j = 0; j < params; j++) {
752-
if (sqlbind(stmt, j, vals[(i * params) + j]) != SQLITE_OK) {
753-
throwex();
754+
rc = sqlbind(stmt, j, vals[(i * params) + j]);
755+
if (rc != SQLITE_OK) {
756+
throwex(rc);
754757
}
755758
}
756759

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

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

792795
for (int i = 0; i < params; i++) {
793-
if (sqlbind(stmt.pointer, i, vals[i]) != SQLITE_OK) {
794-
throwex();
796+
int rc = sqlbind(stmt.pointer, i, vals[i]);
797+
if (rc != SQLITE_OK) {
798+
throwex(rc);
795799
}
796800
}
797801
}
@@ -880,7 +884,7 @@ public final void throwex(int errorCode) throws SQLException {
880884
* @param errorMessage Error message to be passed.
881885
* @throws SQLException
882886
*/
883-
final void throwex(int errorCode, String errorMessage) throws SQLException {
887+
static final void throwex(int errorCode, String errorMessage) throws SQLiteException {
884888
throw newSQLException(errorCode, errorMessage);
885889
}
886890

@@ -891,9 +895,11 @@ final void throwex(int errorCode, String errorMessage) throws SQLException {
891895
* @return Formated SQLException with error code and message.
892896
* @throws SQLException
893897
*/
894-
public static SQLException newSQLException(int errorCode, String errorMessage) throws SQLException {
898+
public static SQLiteException newSQLException(int errorCode, String errorMessage) {
895899
SQLiteErrorCode code = SQLiteErrorCode.getErrorCode(errorCode);
896-
SQLException e = new SQLException(String.format("%s (%s)", code, errorMessage), null, code.code);
900+
SQLiteException e = new SQLiteException(
901+
String.format("%s (%s)", code, errorMessage), code
902+
);
897903
return e;
898904
}
899905

@@ -903,7 +909,7 @@ public static SQLException newSQLException(int errorCode, String errorMessage) t
903909
* @return SQLException with error code and message.
904910
* @throws SQLException
905911
*/
906-
private SQLException newSQLException(int errorCode) throws SQLException {
912+
private SQLiteException newSQLException(int errorCode) throws SQLException {
907913
return newSQLException(errorCode, errmsg());
908914
}
909915

@@ -956,9 +962,10 @@ final void ensureAutoCommit() throws SQLException {
956962
{
957963
return; // assume we are in a transaction
958964
}
959-
if (step(commit) != SQLITE_DONE) {
965+
int rc = step(commit);
966+
if (rc != SQLITE_DONE) {
960967
reset(commit);
961-
throwex();
968+
throwex(rc);
962969
}
963970
//throw new SQLException("unable to auto-commit");
964971
}

src/main/java/org/sqlite/core/NativeDB.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,9 @@ JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB__1open(
354354
return;
355355
}
356356

357+
// Ignore failures, as we can tolerate regular result codes.
358+
(void) sqlite3_extended_result_codes(db, 1);
359+
357360
sethandle(env, this, db);
358361
}
359362

src/test/java/org/sqlite/AllTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
BackupTest.class,
1010
ConnectionTest.class,
1111
DBMetaDataTest.class,
12+
ErrorMessageTest.class,
1213
ExtendedCommandTest.class,
1314
ExtensionTest.class,
1415
FetchSizeTest.class,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package org.sqlite;
2+
3+
import static org.junit.Assume.assumeTrue;
4+
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.sql.Connection;
8+
import java.sql.DriverManager;
9+
import java.sql.SQLException;
10+
import java.sql.Statement;
11+
import java.util.Properties;
12+
13+
import org.hamcrest.BaseMatcher;
14+
import org.hamcrest.Description;
15+
import org.junit.Rule;
16+
import org.junit.Test;
17+
import org.junit.matchers.JUnitMatchers;
18+
import org.junit.rules.ExpectedException;
19+
20+
public class ErrorMessageTest {
21+
@Rule
22+
public ExpectedException thrown = ExpectedException.none();
23+
24+
static class VendorCodeMatcher extends BaseMatcher<Object> {
25+
final SQLiteErrorCode expected;
26+
27+
VendorCodeMatcher(SQLiteErrorCode expected) {this.expected = expected;}
28+
29+
public boolean matches(Object o) {
30+
if (!(o instanceof SQLException)) {
31+
return false;
32+
}
33+
SQLException e = (SQLException)o;
34+
SQLiteErrorCode ec = SQLiteErrorCode.getErrorCode(e.getErrorCode());
35+
return ec == expected;
36+
}
37+
38+
public void describeTo(Description description) {
39+
description
40+
.appendText("SQLException with error code ")
41+
.appendText(expected.name())
42+
.appendText(" (")
43+
.appendValue(expected.code)
44+
.appendText(")");
45+
}
46+
}
47+
48+
static class ResultCodeMatcher extends BaseMatcher<Object> {
49+
final SQLiteErrorCode expected;
50+
51+
ResultCodeMatcher(SQLiteErrorCode expected) {this.expected = expected;}
52+
53+
public boolean matches(Object o) {
54+
if (!(o instanceof SQLiteException)) {
55+
return false;
56+
}
57+
SQLiteException e = (SQLiteException)o;
58+
return e.getResultCode() == expected;
59+
}
60+
61+
public void describeTo(Description description) {
62+
description
63+
.appendText("SQLiteException with error code ")
64+
.appendText(expected.name())
65+
.appendText(" (")
66+
.appendValue(expected.code)
67+
.appendText(")");
68+
}
69+
}
70+
71+
@Test
72+
public void moved() throws SQLException, IOException {
73+
File from = File.createTempFile("error-message-test-moved-from", ".sqlite");
74+
from.deleteOnExit();
75+
76+
Connection conn = DriverManager.getConnection("jdbc:sqlite:" + from.getAbsolutePath());
77+
Statement stmt = conn.createStatement();
78+
stmt.executeUpdate("create table sample(id, name)");
79+
stmt.executeUpdate("insert into sample values(1, \"foo\")");
80+
81+
File to = File.createTempFile("error-message-test-moved-from", ".sqlite");
82+
assumeTrue(to.delete());
83+
assumeTrue(from.renameTo(to));
84+
85+
thrown.expectMessage(JUnitMatchers.containsString("[SQLITE_READONLY_DBMOVED]"));
86+
stmt.executeUpdate("insert into sample values(2, \"bar\")");
87+
88+
stmt.close();
89+
conn.close();
90+
}
91+
92+
@Test
93+
public void writeProtected() throws SQLException, IOException {
94+
File file = File.createTempFile("error-message-test-write-protected", ".sqlite");
95+
file.deleteOnExit();
96+
97+
Connection conn = DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath());
98+
Statement stmt = conn.createStatement();
99+
stmt.executeUpdate("create table sample(id, name)");
100+
stmt.executeUpdate("insert into sample values(1, \"foo\")");
101+
stmt.close();
102+
conn.close();
103+
104+
assumeTrue(file.setReadOnly());
105+
106+
conn = DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath());
107+
stmt = conn.createStatement();
108+
thrown.expectMessage(JUnitMatchers.containsString("[SQLITE_READONLY]"));
109+
stmt.executeUpdate("insert into sample values(2, \"bar\")");
110+
stmt.close();
111+
conn.close();
112+
}
113+
114+
@Test
115+
public void shouldUsePlainErrorCodeAsVendorCodeAndExtendedAsResultCode() throws SQLException, IOException {
116+
File from = File.createTempFile("error-message-test-plain-1", ".sqlite");
117+
from.deleteOnExit();
118+
119+
Connection conn = DriverManager.getConnection("jdbc:sqlite:" + from.getAbsolutePath());
120+
Statement stmt = conn.createStatement();
121+
stmt.executeUpdate("create table sample(id, name)");
122+
stmt.executeUpdate("insert into sample values(1, \"foo\")");
123+
124+
File to = File.createTempFile("error-message-test-plain-2", ".sqlite");
125+
assumeTrue(to.delete());
126+
assumeTrue(from.renameTo(to));
127+
128+
thrown.expectMessage(JUnitMatchers.containsString("[SQLITE_READONLY_DBMOVED]"));
129+
thrown.expect(new VendorCodeMatcher(SQLiteErrorCode.SQLITE_READONLY));
130+
thrown.expect(new ResultCodeMatcher(SQLiteErrorCode.SQLITE_READONLY_DBMOVED));
131+
stmt.executeUpdate("insert into sample values(2, \"bar\")");
132+
133+
stmt.close();
134+
conn.close();
135+
}
136+
}

0 commit comments

Comments
 (0)