├── .boring ├── LICENSE ├── Makefile ├── Makefile.common ├── Makefile.nested ├── Makefile.release ├── VERSION ├── lib ├── jdbc-1.4.jar └── junit-4.1.jar └── src ├── org └── sqlite │ ├── Codes.java │ ├── Conn.java │ ├── DB.java │ ├── Function.java │ ├── JDBC.java │ ├── MetaData.java │ ├── NativeDB.c │ ├── NativeDB.java │ ├── NestedDB.c │ ├── NestedDB.java │ ├── PrepStmt.java │ ├── RS.java │ ├── Stmt.java │ └── Unused.java └── test ├── ConnectionTest.java ├── DBMetaDataTest.java ├── PrepStmtTest.java ├── RSMetaDataTest.java ├── StatementTest.java ├── TransactionTest.java └── UDFTest.java /.boring: -------------------------------------------------------------------------------- 1 | # ignore build directories 2 | (^|/)build($|/) 3 | (^|/)dist($|/) 4 | (^|/)work($|/) 5 | # Boring file regexps: 6 | \.hi$ 7 | \.o$ 8 | \.o\.cmd$ 9 | # *.ko files aren't boring by default because they might 10 | # be Korean translations rather than kernel modules. 11 | # \.ko$ 12 | \.ko\.cmd$ 13 | \.mod\.c$ 14 | (^|/)\.tmp_versions($|/) 15 | (^|/)CVS($|/) 16 | (^|/)RCS($|/) 17 | ~$ 18 | #(^|/)\.[^/] 19 | (^|/)_darcs($|/) 20 | \.bak$ 21 | \.BAK$ 22 | \.orig$ 23 | (^|/)vssver\.scc$ 24 | \.swp$ 25 | (^|/)MT($|/) 26 | (^|/)\{arch\}($|/) 27 | (^|/).arch-ids($|/) 28 | (^|/), 29 | \.class$ 30 | \.prof$ 31 | (^|/)\.DS_Store$ 32 | (^|/)BitKeeper($|/) 33 | (^|/)ChangeSet($|/) 34 | (^|/)\.svn($|/) 35 | \.py[co]$ 36 | \# 37 | \.cvsignore$ 38 | (^|/)Thumbs\.db$ 39 | (^|/)autom4te\.cache($|/) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 David Crawshaw 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for the native SQLite JDBC Driver 2 | # 3 | # No auto-goop. Just try typing 'make'. You should get two interesting files: 4 | # build/TARGET_OS/LIBNAME 5 | # build/sqlitejdbc-vXXX-native.jar 6 | # 7 | # To combine these, type: 8 | # cd build 9 | # mv LIBNAME linux-x86.lib (or win-x86.lib, freebsd-ppc.lib, mac.lib, etc) 10 | # java uf sqlitejdbc-vXXX-native.jar linux-x86.lib 11 | # 12 | # The first is the native library, the second is the java support files. 13 | # Generating the more complete sqlitejdbc-vXXX.jar requires building the 14 | # NestedVM source, which requires running a Linux machine, and looking at 15 | # the other make files. 16 | # 17 | 18 | include Makefile.common 19 | 20 | default: test 21 | 22 | test: native $(test_classes) 23 | $(JAVA) -Djava.library.path=build/$(target) \ 24 | -cp "build/$(sqlitejdbc)-native.jar$(sep)build$(sep)$(libjunit)" \ 25 | org.junit.runner.JUnitCore $(tests) 26 | 27 | native: build/$(sqlitejdbc)-native.jar build/$(target)/$(LIBNAME) 28 | 29 | build/$(sqlitejdbc)-native.jar: $(native_classes) 30 | cd build && jar cf $(sqlitejdbc)-native.jar $(java_classlist) 31 | 32 | build/$(target)/$(LIBNAME): build/$(sqlite)-$(target)/sqlite3.o build/org/sqlite/NativeDB.class 33 | @mkdir -p build/$(target) 34 | $(JAVAH) -classpath build -jni -o build/NativeDB.h org.sqlite.NativeDB 35 | $(CC) $(CFLAGS) -c -o build/$(target)/NativeDB.o \ 36 | src/org/sqlite/NativeDB.c 37 | $(CC) $(CFLAGS) $(LINKFLAGS) -o build/$(target)/$(LIBNAME) \ 38 | build/$(target)/NativeDB.o build/$(sqlite)-$(target)/*.o 39 | $(STRIP) build/$(target)/$(LIBNAME) 40 | 41 | build/$(sqlite)-%/sqlite3.o: dl/$(sqlite)-amal.zip 42 | @mkdir -p build/$(sqlite)-$* 43 | unzip -qo dl/$(sqlite)-amal.zip -d build/$(sqlite)-$* 44 | perl -pi -e "s/sqlite3_api;/sqlite3_api = 0;/g" \ 45 | build/$(sqlite)-$*/sqlite3ext.h 46 | (cd build/$(sqlite)-$*; $(CC) -o sqlite3.o -c $(CFLAGS) \ 47 | -DSQLITE_ENABLE_COLUMN_METADATA \ 48 | -DSQLITE_ENABLE_FTS3 \ 49 | -DSQLITE_THREADSAFE=1 \ 50 | sqlite3.c) 51 | 52 | build/org/%.class: src/org/%.java 53 | @mkdir -p build 54 | $(JAVAC) -source 1.2 -target 1.2 -sourcepath src -d build $< 55 | 56 | build/test/%.class: src/test/%.java 57 | @mkdir -p build 58 | $(JAVAC) -target 1.5 -classpath "build$(sep)$(libjunit)" \ 59 | -sourcepath src/test -d build $< 60 | 61 | dl/$(sqlite)-amal.zip: 62 | @mkdir -p dl 63 | curl -odl/$(sqlite)-amal.zip \ 64 | http://www.sqlite.org/sqlite-amalgamation-$(subst .,_,$(sqlite_version)).zip 65 | 66 | clean: 67 | rm -rf build dist 68 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | ifndef JAVA_HOME 2 | $(error Set JAVA_HOME environment variable) 3 | endif 4 | 5 | ifeq ($(os),) 6 | ifeq ($(shell uname),Darwin) 7 | os := Darwin 8 | endif 9 | ifeq ($(findstring CYGWIN,$(shell uname)),CYGWIN) 10 | os := Win 11 | endif 12 | ifeq ($(findstring MINGW,$(shell uname)),MINGW) 13 | os := Win 14 | endif 15 | endif 16 | ifeq ($(os),) 17 | os := Default 18 | endif 19 | 20 | # Windows uses different path separators, because they hate me 21 | ifeq ($(os),Win) 22 | sep := ; 23 | else 24 | sep := : 25 | endif 26 | 27 | ifeq ($(arch),) 28 | arch := $(shell uname -m) 29 | endif 30 | 31 | sqlite_version := 3.6.14.2 32 | sqlite := sqlite-$(sqlite_version) 33 | 34 | sqlitejdbc := sqlitejdbc-v$(shell cat VERSION) 35 | 36 | jni_md := $(shell find -L "$(JAVA_HOME)/include" -name jni_md.h) 37 | ifneq ($(jni_md),) 38 | jni_include := $(shell dirname "$(jni_md)") 39 | endif 40 | 41 | libjdbc := $(wildcard lib/jdbc-*.jar) 42 | libjunit := $(wildcard lib/junit-*.jar) 43 | 44 | JAVA := $$JAVA_HOME/bin/java 45 | JAVAC := $$JAVA_HOME/bin/javac -Xbootclasspath/p:$(libjdbc) 46 | JAVAH := $$JAVA_HOME/bin/javah 47 | 48 | java_sources = $(wildcard src/org/sqlite/*.java) 49 | java_classes = $(java_sources:src/%.java=build/%.class) 50 | native_classes = $(filter-out %NestedDB.class,$(java_classes)) 51 | java_classlist = $(subst $$,\$$,$(patsubst build/%, %, $(wildcard $(java_classes:%.class=%*)))) 52 | test_sources = $(wildcard src/test/*.java) 53 | test_classes = $(test_sources:src/%.java=build/%.class) 54 | tests = $(subst /,.,$(patsubst build/%.class,%,$(test_classes))) 55 | 56 | target := $(os)-$(arch) 57 | 58 | # os=Default is meant to be generic unix/linux 59 | Default_CC := gcc 60 | Default_STRIP := strip 61 | Default_CFLAGS := -I$(JAVA_HOME)/include -Os -fPIC 62 | Default_LINKFLAGS := -shared 63 | Default_LIBNAME := libsqlitejdbc.so 64 | 65 | Support10_4 := -isysroot /Developer/SDKs/MacOSX10.4u.sdk 66 | Darwin_CC := gcc -arch $(arch) -mmacosx-version-min=10.4 $(Support10_4) 67 | Darwin_STRIP := strip -x 68 | Darwin_CFLAGS := -I$(JAVA_HOME)/include -Os -fPIC 69 | Darwin_LINKFLAGS := -dynamiclib $(Support10_4) 70 | Darwin_LIBNAME := libsqlitejdbc.jnilib 71 | 72 | Win_CC := $(arch)-mingw32msvc-gcc 73 | Win_STRIP := $(arch)-mingw32msvc-strip 74 | Win_CFLAGS := -D_JNI_IMPLEMENTATION_ -Ilib/inc_win -O 75 | Win_LINKFLAGS := -Wl,--kill-at -shared 76 | Win_LIBNAME := sqlitejdbc.dll 77 | 78 | CC := $($(os)_CC) 79 | STRIP := $($(os)_STRIP) 80 | CFLAGS := $($(os)_CFLAGS) 81 | LINKFLAGS := $($(os)_LINKFLAGS) 82 | LIBNAME := $($(os)_LIBNAME) 83 | 84 | CFLAGS := $(CFLAGS) -Ibuild/$(sqlite)-$(target) -Ibuild 85 | ifneq ($(jni_include),) 86 | CFLAGS := $(CFLAGS) -I$(jni_include) 87 | endif 88 | -------------------------------------------------------------------------------- /Makefile.nested: -------------------------------------------------------------------------------- 1 | include Makefile.common 2 | 3 | nestedvm_version := 2007-06-30 4 | nestedvm := nestedvm-$(nestedvm_version) 5 | 6 | default: test 7 | 8 | test: dist/$(sqlitejdbc)-pure.jar $(test_classes) 9 | $(JAVA) -cp "dist/$(sqlitejdbc)-pure.jar$(sep)build$(sep)$(libjunit)" \ 10 | org.junit.runner.JUnitCore $(tests) 11 | 12 | dist/$(sqlitejdbc)-pure.jar: build/org/sqlite/SQLite.class $(java_classes) 13 | @mkdir -p dist 14 | cd build && jar cf ../$@ \ 15 | org/sqlite/SQLite.class $(java_classlist) \ 16 | -C ../$(nestedvm)/build org/ibex 17 | 18 | build/org/%.class: src/org/%.java 19 | @mkdir -p build 20 | $(JAVAC) -source 1.2 -target 1.2 -classpath "$(nestedvm)/build" \ 21 | -sourcepath src -d build $< 22 | 23 | build/test/%.class: src/test/%.java 24 | @mkdir -p build 25 | $(JAVAC) -target 1.5 -classpath "build$(sep)$(libjunit)" \ 26 | -sourcepath src/test -d build $< 27 | 28 | $(nestedvm)/%: 29 | $(MAKE) -C $(nestedvm) $* 30 | 31 | dl/$(nestedvm).tgz: 32 | @mkdir -p dl 33 | curl -odl/$(nestedvm).tgz http://files.zentus.com/sqlitejdbc/$(nestedvm).tgz 34 | 35 | dl/$(sqlite)-amal.zip: 36 | @mkdir -p dl 37 | curl -odl/$(sqlite)-amal.zip \ 38 | http://www.sqlite.org/sqlite-amalgamation-$(subst .,_,$(sqlite_version)).zip 39 | 40 | $(nestedvm)/Makefile: dl/$(nestedvm).tgz 41 | tar xfz dl/$(nestedvm).tgz 42 | 43 | build/SQLite.mips: $(nestedvm)/Makefile $(nestedvm)/env.sh dl/$(sqlite)-amal.zip 44 | @mkdir -p build 45 | @mkdir -p build/$(sqlite)-nestedvm 46 | unzip -qo dl/$(sqlite)-amal.zip -d build/$(sqlite)-nestedvm 47 | cp src/org/sqlite/Nested*.c build/$(sqlite)-nestedvm 48 | perl -pi -e "s/sqlite3_api;/sqlite3_api = 0;/g" \ 49 | build/$(sqlite)-nestedvm/sqlite3ext.h 50 | 51 | # we need a dummy main 52 | echo 'int main() { return 0; }' >> build/$(sqlite)-nestedvm/sqlite3.c 53 | perl -pi -e "s/utimes\(.*?\);//g" build/$(sqlite)-nestedvm/sqlite3.c 54 | 55 | (. ./$(nestedvm)/env.sh; cd build/$(sqlite)-nestedvm; \ 56 | $$CC -c $$CFLAGS -o sqlite3.o \ 57 | -DSQLITE_THREADSAFE=0 \ 58 | -DSQLITE_ENABLE_COLUMN_METADATA \ 59 | -DSQLITE_CORE \ 60 | -DSQLITE_ENABLE_FTS3 \ 61 | -DSQLITE_OMIT_LOAD_EXTENSION \ 62 | sqlite3.c; \ 63 | $$CC -c $$CFLAGS -o NestedDB.o Nested*.c) 64 | 65 | ./$(nestedvm)/upstream/install/bin/mips-unknown-elf-gcc \ 66 | -march=mips1 --static \ 67 | -o $@ build/$(sqlite)-nestedvm/sqlite3.o \ 68 | build/$(sqlite)-nestedvm/NestedDB.o 69 | 70 | build/org/sqlite/SQLite.class: build/SQLite.mips 71 | java -cp $(nestedvm)/build$(sep)$(nestedvm)/upstream/build/classgen/build \ 72 | org.ibex.nestedvm.Compiler \ 73 | -outformat class -d build -o unixRuntime \ 74 | org.sqlite.SQLite build/SQLite.mips 75 | 76 | clean: 77 | rm -rf build 78 | rm -rf dist 79 | -------------------------------------------------------------------------------- /Makefile.release: -------------------------------------------------------------------------------- 1 | # This file is a nasty piece of work I use for building SQLiteJDBC 2 | # across multiple architectures. Best to ignore it. 3 | 4 | include Makefile.common 5 | 6 | afsdir := /afs/hcoop.net/user/c/cr/crawshaw 7 | repo := $(afsdir)/repo/sqlitejdbc 8 | files := $(afsdir)/files/sqlitejdbc 9 | 10 | default: dist/$(sqlitejdbc).jar dist/$(sqlitejdbc)-src.tgz dist/$(sqlitejdbc)-bin.tgz 11 | 12 | release: dist/$(sqlitejdbc).jar dist/$(sqlitejdbc)-src.tgz dist/$(sqlitejdbc)-bin.tgz 13 | git push hcoop:$(repo) 14 | scp dist/$(sqlitejdbc)* hcoop:$(files)/ 15 | 16 | dist/$(sqlitejdbc).jar: build/mac-universal.lib build/win-x86.lib build/linux-x86.lib build/linux-amd64.lib dist/$(sqlitejdbc)-pure.jar 17 | cp dist/$(sqlitejdbc)-pure.jar build/$(sqlitejdbc).jar 18 | (cd build; jar uf $(sqlitejdbc).jar *.lib) 19 | cp build/$(sqlitejdbc).jar $@ 20 | rm build/$(sqlitejdbc).jar 21 | 22 | dist/$(sqlitejdbc)-src.tgz: 23 | git archive --format=tar --prefix=$(sqlitejdbc)/ HEAD | gzip > $@ 24 | 25 | dist/$(sqlitejdbc)-bin.tgz: dist/$(sqlitejdbc).jar 26 | cp build/mac-universal.lib build/libsqlitejdbc.jnilib 27 | cp build/win-x86.lib build/sqlitejdbc.dll 28 | cp build/linux-x86.lib build/libsqlitejdbc.so 29 | $(MAKE) build/$(sqlitejdbc)-native.jar 30 | cd build; tar cfz ../$@ $(sqlitejdbc)-native.jar \ 31 | libsqlitejdbc.jnilib sqlitejdbc.dll libsqlitejdbc.so \ 32 | 33 | dist/$(sqlitejdbc)-pure.jar: 34 | git push debian:repo/sqlitejdbc 35 | ssh debian "cd repo/sqlitejdbc && make -f Makefile.nested test $@" 36 | scp debian:repo/sqlitejdbc/$@ $@ 37 | 38 | build/mac-universal.lib: 39 | @mkdir -p dist build/Darwin-universal 40 | $(MAKE) os=Darwin arch=ppc native 41 | $(MAKE) os=Darwin arch=i386 native 42 | $(MAKE) os=Darwin arch=x86_64 native 43 | lipo -create build/Darwin-ppc/$(Darwin_LIBNAME) \ 44 | build/Darwin-i386/$(Darwin_LIBNAME) \ 45 | build/Darwin-x86_64/$(Darwin_LIBNAME) \ 46 | -output $@ 47 | 48 | build/win-x86.lib: 49 | git push debian:repo/sqlitejdbc 50 | ssh debian "cd repo/sqlitejdbc && make os=Win arch=i586 native" 51 | scp debian:repo/sqlitejdbc/build/Win-i586/$(Win_LIBNAME) $@ 52 | 53 | build/linux-x86.lib: 54 | git push debian:repo/sqlitejdbc 55 | ssh debian "cd repo/sqlitejdbc && make arch=i386 test" 56 | scp debian:repo/sqlitejdbc/build/Default-i386/$(Default_LIBNAME) $@ 57 | 58 | build/linux-amd64.lib: 59 | git push debian64:repo/sqlitejdbc 60 | ssh debian64 "cd repo/sqlitejdbc && make arch=amd64 test" 61 | scp debian64:repo/sqlitejdbc/build/Default-amd64/$(Default_LIBNAME) $@ 62 | 63 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 056 2 | -------------------------------------------------------------------------------- /lib/jdbc-1.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crawshaw/sqlitejdbc/60b5f5c608dca435f3319158ff3980ad5f5faec1/lib/jdbc-1.4.jar -------------------------------------------------------------------------------- /lib/junit-4.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crawshaw/sqlitejdbc/60b5f5c608dca435f3319158ff3980ad5f5faec1/lib/junit-4.1.jar -------------------------------------------------------------------------------- /src/org/sqlite/Codes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | package org.sqlite; 17 | 18 | interface Codes 19 | { 20 | /** Successful result */ 21 | public static final int SQLITE_OK = 0; 22 | 23 | /** SQL error or missing database */ 24 | public static final int SQLITE_ERROR = 1; 25 | 26 | /** An internal logic error in SQLite */ 27 | public static final int SQLITE_INTERNAL = 2; 28 | 29 | /** Access permission denied */ 30 | public static final int SQLITE_PERM = 3; 31 | 32 | /** Callback routine requested an abort */ 33 | public static final int SQLITE_ABORT = 4; 34 | 35 | /** The database file is locked */ 36 | public static final int SQLITE_BUSY = 5; 37 | 38 | /** A table in the database is locked */ 39 | public static final int SQLITE_LOCKED = 6; 40 | 41 | /** A malloc() failed */ 42 | public static final int SQLITE_NOMEM = 7; 43 | 44 | /** Attempt to write a readonly database */ 45 | public static final int SQLITE_READONLY = 8; 46 | 47 | /** Operation terminated by sqlite_interrupt() */ 48 | public static final int SQLITE_INTERRUPT = 9; 49 | 50 | /** Some kind of disk I/O error occurred */ 51 | public static final int SQLITE_IOERR = 10; 52 | 53 | /** The database disk image is malformed */ 54 | public static final int SQLITE_CORRUPT = 11; 55 | 56 | /** (Internal Only) Table or record not found */ 57 | public static final int SQLITE_NOTFOUND = 12; 58 | 59 | /** Insertion failed because database is full */ 60 | public static final int SQLITE_FULL = 13; 61 | 62 | /** Unable to open the database file */ 63 | public static final int SQLITE_CANTOPEN = 14; 64 | 65 | /** Database lock protocol error */ 66 | public static final int SQLITE_PROTOCOL = 15; 67 | 68 | /** (Internal Only) Database table is empty */ 69 | public static final int SQLITE_EMPTY = 16; 70 | 71 | /** The database schema changed */ 72 | public static final int SQLITE_SCHEMA = 17; 73 | 74 | /** Too much data for one row of a table */ 75 | public static final int SQLITE_TOOBIG = 18; 76 | 77 | /** Abort due to constraint violation */ 78 | public static final int SQLITE_CONSTRAINT = 19; 79 | 80 | /** Data type mismatch */ 81 | public static final int SQLITE_MISMATCH = 20; 82 | 83 | /** Library used incorrectly */ 84 | public static final int SQLITE_MISUSE = 21; 85 | 86 | /** Uses OS features not supported on host */ 87 | public static final int SQLITE_NOLFS = 22; 88 | 89 | /** Authorization denied */ 90 | public static final int SQLITE_AUTH = 23; 91 | 92 | /** sqlite_step() has another row ready */ 93 | public static final int SQLITE_ROW = 100; 94 | 95 | /** sqlite_step() has finished executing */ 96 | public static final int SQLITE_DONE = 101; 97 | 98 | 99 | // types returned by sqlite3_column_type() 100 | 101 | public static final int SQLITE_INTEGER = 1; 102 | public static final int SQLITE_FLOAT = 2; 103 | public static final int SQLITE_TEXT = 3; 104 | public static final int SQLITE_BLOB = 4; 105 | public static final int SQLITE_NULL = 5; 106 | } 107 | -------------------------------------------------------------------------------- /src/org/sqlite/Conn.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | package org.sqlite; 17 | 18 | import java.sql.*; 19 | import java.util.*; 20 | import java.io.File; 21 | import java.lang.ref.WeakReference; 22 | 23 | class Conn implements Connection 24 | { 25 | private final String url; 26 | private final boolean readOnly; 27 | private DB db = null; 28 | private MetaData meta = null; 29 | private boolean autoCommit = true; 30 | private int timeout = 0; 31 | 32 | public Conn(String url, String filename, boolean sharedCache) 33 | throws SQLException { 34 | this(url, filename); 35 | db.shared_cache(sharedCache); 36 | } 37 | public Conn(String url, String filename) throws SQLException { 38 | boolean ro = false; 39 | 40 | // check the path to the file exists 41 | if (!":memory:".equals(filename)) { 42 | File file = new File(filename).getAbsoluteFile(); 43 | File parent = file.getParentFile(); 44 | if (parent != null && !parent.exists()) { 45 | for (File up = parent; up != null && !up.exists();) { 46 | parent = up; 47 | up = up.getParentFile(); 48 | } 49 | throw new SQLException("path to '" + filename + "': '" 50 | + parent + "' does not exist"); 51 | } 52 | 53 | // check write access if file does not exist 54 | try { 55 | // The extra check to exists() is necessary as createNewFile() 56 | // does not follow the JavaDoc when used on read-only shares. 57 | if (!file.exists() && file.createNewFile()) file.delete(); 58 | } catch (Exception e) { 59 | throw new SQLException( 60 | "opening db: '" + filename + "': " +e.getMessage()); 61 | } 62 | filename = file.getAbsolutePath(); 63 | if (file.exists()) 64 | ro = !file.canWrite(); 65 | } 66 | 67 | readOnly = ro; 68 | 69 | // TODO: library variable to explicitly control load type 70 | // attempt to use the Native library first 71 | try { 72 | Class nativedb = Class.forName("org.sqlite.NativeDB"); 73 | if (((Boolean)nativedb.getDeclaredMethod("load", null) 74 | .invoke(null, null)).booleanValue()) 75 | db = (DB)nativedb.newInstance(); 76 | } catch (Exception e) { } // fall through to nested library 77 | 78 | // load nested library 79 | if (db == null) { 80 | try { 81 | db = (DB)Class.forName("org.sqlite.NestedDB").newInstance(); 82 | } catch (Exception e) { 83 | throw new SQLException("no SQLite library found"); 84 | } 85 | } 86 | 87 | this.url = url; 88 | db.open(this, filename); 89 | setTimeout(3000); 90 | } 91 | 92 | int getTimeout() { return timeout; } 93 | void setTimeout(int ms) throws SQLException { 94 | timeout = ms; 95 | db.busy_timeout(ms); 96 | } 97 | String url() { return url; } 98 | String libversion() throws SQLException { return db.libversion(); } 99 | DB db() { return db; } 100 | 101 | private void checkOpen() throws SQLException { 102 | if (db == null) throw new SQLException("database connection closed"); 103 | } 104 | 105 | private void checkCursor(int rst, int rsc, int rsh) throws SQLException { 106 | if (rst != ResultSet.TYPE_FORWARD_ONLY) throw new SQLException( 107 | "SQLite only supports TYPE_FORWARD_ONLY cursors"); 108 | if (rsc != ResultSet.CONCUR_READ_ONLY) throw new SQLException( 109 | "SQLite only supports CONCUR_READ_ONLY cursors"); 110 | if (rsh != ResultSet.CLOSE_CURSORS_AT_COMMIT) throw new SQLException( 111 | "SQLite only supports closing cursors at commit"); 112 | } 113 | 114 | public void finalize() throws SQLException { close(); } 115 | public void close() throws SQLException { 116 | if (db == null) return; 117 | if (meta != null) meta.close(); 118 | 119 | db.close(); 120 | db = null; 121 | } 122 | 123 | public boolean isClosed() throws SQLException { return db == null; } 124 | 125 | public String getCatalog() throws SQLException { checkOpen(); return null; } 126 | public void setCatalog(String catalog) throws SQLException { checkOpen(); } 127 | 128 | public int getHoldability() throws SQLException { 129 | checkOpen(); return ResultSet.CLOSE_CURSORS_AT_COMMIT; } 130 | public void setHoldability(int h) throws SQLException { 131 | checkOpen(); 132 | if (h != ResultSet.CLOSE_CURSORS_AT_COMMIT) throw new SQLException( 133 | "SQLite only supports CLOSE_CURSORS_AT_COMMIT"); 134 | } 135 | 136 | public int getTransactionIsolation() { return TRANSACTION_SERIALIZABLE; } 137 | public void setTransactionIsolation(int level) throws SQLException { 138 | if (level != TRANSACTION_SERIALIZABLE) throw new SQLException( 139 | "SQLite supports only TRANSACTION_SERIALIZABLE"); 140 | } 141 | 142 | public Map getTypeMap() throws SQLException 143 | { throw new SQLException("not yet implemented");} 144 | public void setTypeMap(Map map) throws SQLException 145 | { throw new SQLException("not yet implemented");} 146 | 147 | public boolean isReadOnly() throws SQLException { return readOnly; } 148 | public void setReadOnly(boolean ro) throws SQLException {} 149 | 150 | public DatabaseMetaData getMetaData() { 151 | if (meta == null) meta = new MetaData(this); 152 | return meta; 153 | } 154 | 155 | public String nativeSQL(String sql) { return sql; } 156 | 157 | public void clearWarnings() throws SQLException { } 158 | public SQLWarning getWarnings() throws SQLException { return null; } 159 | 160 | public boolean getAutoCommit() throws SQLException { 161 | checkOpen(); return autoCommit; } 162 | public void setAutoCommit(boolean ac) throws SQLException { 163 | checkOpen(); 164 | if (autoCommit == ac) return; 165 | autoCommit = ac; 166 | db.exec(autoCommit ? "commit;" : "begin;"); 167 | } 168 | 169 | public void commit() throws SQLException { 170 | checkOpen(); 171 | if (autoCommit) throw new SQLException("database in auto-commit mode"); 172 | db.exec("commit;"); 173 | db.exec("begin;"); 174 | } 175 | 176 | public void rollback() throws SQLException { 177 | checkOpen(); 178 | if (autoCommit) throw new SQLException("database in auto-commit mode"); 179 | db.exec("rollback;"); 180 | db.exec("begin;"); 181 | } 182 | 183 | public Statement createStatement() throws SQLException { 184 | return createStatement(ResultSet.TYPE_FORWARD_ONLY, 185 | ResultSet.CONCUR_READ_ONLY, 186 | ResultSet.CLOSE_CURSORS_AT_COMMIT); 187 | } 188 | public Statement createStatement(int rsType, int rsConcurr) 189 | throws SQLException { return createStatement(rsType, rsConcurr, 190 | ResultSet.CLOSE_CURSORS_AT_COMMIT); 191 | } 192 | public Statement createStatement(int rst, int rsc, int rsh) 193 | throws SQLException { 194 | checkCursor(rst, rsc, rsh); 195 | return new Stmt(this); 196 | } 197 | 198 | public CallableStatement prepareCall(String sql) throws SQLException { 199 | return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, 200 | ResultSet.CONCUR_READ_ONLY, 201 | ResultSet.CLOSE_CURSORS_AT_COMMIT); 202 | } 203 | public CallableStatement prepareCall(String sql, int rst, int rsc) 204 | throws SQLException { 205 | return prepareCall(sql, rst, rsc, ResultSet.CLOSE_CURSORS_AT_COMMIT); 206 | } 207 | public CallableStatement prepareCall(String sql, int rst, int rsc, int rsh) 208 | throws SQLException { 209 | throw new SQLException("SQLite does not support Stored Procedures"); 210 | } 211 | 212 | public PreparedStatement prepareStatement(String sql) throws SQLException { 213 | return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, 214 | ResultSet.CONCUR_READ_ONLY); 215 | } 216 | public PreparedStatement prepareStatement(String sql, int autoC) 217 | throws SQLException { throw new SQLException("NYI"); } 218 | public PreparedStatement prepareStatement(String sql, int[] colInds) 219 | throws SQLException { throw new SQLException("NYI"); } 220 | public PreparedStatement prepareStatement(String sql, String[] colNames) 221 | throws SQLException { throw new SQLException("NYI"); } 222 | public PreparedStatement prepareStatement(String sql, int rst, int rsc) 223 | throws SQLException { 224 | return prepareStatement(sql, rst, rsc, 225 | ResultSet.CLOSE_CURSORS_AT_COMMIT); 226 | } 227 | 228 | public PreparedStatement prepareStatement( 229 | String sql, int rst, int rsc, int rsh) throws SQLException { 230 | checkCursor(rst, rsc, rsh); 231 | return new PrepStmt(this, sql); 232 | } 233 | 234 | /** Used to supply DatabaseMetaData.getDriverVersion(). */ 235 | String getDriverVersion() { 236 | if (db != null) { 237 | String dbname = db.getClass().getName(); 238 | if (dbname.indexOf("NestedDB") >= 0) 239 | return "pure"; 240 | if (dbname.indexOf("NativeDB") >= 0) 241 | return "native"; 242 | } 243 | return "unloaded"; 244 | } 245 | 246 | 247 | // UNUSED FUNCTIONS ///////////////////////////////////////////// 248 | 249 | public Savepoint setSavepoint() throws SQLException { 250 | throw new SQLException("unsupported by SQLite: savepoints"); } 251 | public Savepoint setSavepoint(String name) throws SQLException { 252 | throw new SQLException("unsupported by SQLite: savepoints"); } 253 | public void releaseSavepoint(Savepoint savepoint) throws SQLException { 254 | throw new SQLException("unsupported by SQLite: savepoints"); } 255 | public void rollback(Savepoint savepoint) throws SQLException { 256 | throw new SQLException("unsupported by SQLite: savepoints"); } 257 | 258 | public Struct createStruct(String t, Object[] attr) throws SQLException { 259 | throw new SQLException("unsupported by SQLite"); } 260 | } 261 | -------------------------------------------------------------------------------- /src/org/sqlite/DB.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | package org.sqlite; 17 | 18 | import java.lang.ref.*; 19 | import java.io.File; 20 | import java.sql.*; 21 | import java.util.*; 22 | 23 | /* 24 | * This class is the interface to SQLite. It provides some helper functions 25 | * used by other parts of the driver. The goal of the helper functions here 26 | * are not only to provide functionality, but to handle contractual 27 | * differences between the JDBC specification and the SQLite C API. 28 | * 29 | * The process of moving SQLite weirdness into this class is incomplete. 30 | * You'll still find lots of code in Stmt and PrepStmt that are doing 31 | * implicit contract conversions. Sorry. 32 | * 33 | * The two subclasses, NativeDB and NestedDB, provide the actual access to 34 | * SQLite functions. 35 | */ 36 | abstract class DB implements Codes 37 | { 38 | /** The JDBC Connection that 'owns' this database instance. */ 39 | Conn conn = null; 40 | 41 | /** The "begin;"and "commit;" statement handles. */ 42 | long begin = 0; 43 | long commit = 0; 44 | 45 | /** Tracer for statements to avoid unfinalized statements on db close. */ 46 | private Map stmts = new Hashtable(); 47 | 48 | // WRAPPER FUNCTIONS //////////////////////////////////////////// 49 | 50 | abstract void interrupt() throws SQLException; 51 | abstract void busy_timeout(int ms) throws SQLException; 52 | abstract String errmsg() throws SQLException; 53 | abstract String libversion() throws SQLException; 54 | abstract int changes() throws SQLException; 55 | abstract int shared_cache(boolean enable) throws SQLException; 56 | 57 | final synchronized void exec(String sql) throws SQLException { 58 | long pointer = 0; 59 | try { 60 | pointer = prepare(sql); 61 | switch (step(pointer)) { 62 | case SQLITE_DONE: 63 | ensureAutoCommit(); 64 | return; 65 | case SQLITE_ROW: 66 | return; 67 | default: 68 | throwex(); 69 | } 70 | } finally { 71 | finalize(pointer); 72 | } 73 | } 74 | 75 | final synchronized void open(Conn conn, String file) throws SQLException { 76 | this.conn = conn; 77 | _open(file); 78 | } 79 | 80 | final synchronized void close() throws SQLException { 81 | // finalize any remaining statements before closing db 82 | synchronized (stmts) { 83 | Iterator i = stmts.entrySet().iterator(); 84 | while (i.hasNext()) { 85 | Map.Entry entry = (Map.Entry)i.next(); 86 | Stmt stmt = (Stmt)entry.getValue(); 87 | finalize(((Long)entry.getKey()).longValue()); 88 | if (stmt != null) { 89 | stmt.pointer = 0; 90 | } 91 | i.remove(); 92 | } 93 | } 94 | 95 | // remove memory used by user-defined functions 96 | free_functions(); 97 | 98 | // clean up commit object 99 | if (begin != 0) { 100 | finalize(begin); 101 | begin = 0; 102 | } 103 | if (commit != 0) { 104 | finalize(commit); 105 | commit = 0; 106 | } 107 | 108 | _close(); 109 | } 110 | 111 | final synchronized void prepare(Stmt stmt) throws SQLException { 112 | if (stmt.pointer != 0) 113 | finalize(stmt); 114 | stmt.pointer = prepare(stmt.sql); 115 | stmts.put(new Long(stmt.pointer), stmt); 116 | } 117 | 118 | final synchronized int finalize(Stmt stmt) throws SQLException { 119 | if (stmt.pointer == 0) return 0; 120 | int rc = SQLITE_ERROR; 121 | try { 122 | rc = finalize(stmt.pointer); 123 | } finally { 124 | stmts.remove(new Long(stmt.pointer)); 125 | stmt.pointer = 0; 126 | } 127 | return rc; 128 | } 129 | 130 | protected abstract void _open(String filename) throws SQLException; 131 | protected abstract void _close() throws SQLException; 132 | protected abstract long prepare(String sql) throws SQLException; 133 | protected abstract int finalize(long stmt) throws SQLException; 134 | protected abstract int step(long stmt) throws SQLException; 135 | protected abstract int reset(long stmt) throws SQLException; 136 | 137 | abstract int clear_bindings(long stmt) throws SQLException; // TODO remove? 138 | abstract int bind_parameter_count(long stmt) throws SQLException; 139 | 140 | abstract int column_count (long stmt) throws SQLException; 141 | abstract int column_type (long stmt, int col) throws SQLException; 142 | abstract String column_decltype (long stmt, int col) throws SQLException; 143 | abstract String column_table_name (long stmt, int col) throws SQLException; 144 | abstract String column_name (long stmt, int col) throws SQLException; 145 | abstract String column_text (long stmt, int col) throws SQLException; 146 | abstract byte[] column_blob (long stmt, int col) throws SQLException; 147 | abstract double column_double (long stmt, int col) throws SQLException; 148 | abstract long column_long (long stmt, int col) throws SQLException; 149 | abstract int column_int (long stmt, int col) throws SQLException; 150 | 151 | abstract int bind_null (long stmt, int pos) throws SQLException; 152 | abstract int bind_int (long stmt, int pos, int v) throws SQLException; 153 | abstract int bind_long (long stmt, int pos, long v) throws SQLException; 154 | abstract int bind_double(long stmt, int pos, double v) throws SQLException; 155 | abstract int bind_text (long stmt, int pos, String v) throws SQLException; 156 | abstract int bind_blob (long stmt, int pos, byte[] v) throws SQLException; 157 | 158 | abstract void result_null (long context) throws SQLException; 159 | abstract void result_text (long context, String val) throws SQLException; 160 | abstract void result_blob (long context, byte[] val) throws SQLException; 161 | abstract void result_double(long context, double val) throws SQLException; 162 | abstract void result_long (long context, long val) throws SQLException; 163 | abstract void result_int (long context, int val) throws SQLException; 164 | abstract void result_error (long context, String err) throws SQLException; 165 | 166 | abstract int value_bytes (Function f, int arg) throws SQLException; 167 | abstract String value_text (Function f, int arg) throws SQLException; 168 | abstract byte[] value_blob (Function f, int arg) throws SQLException; 169 | abstract double value_double(Function f, int arg) throws SQLException; 170 | abstract long value_long (Function f, int arg) throws SQLException; 171 | abstract int value_int (Function f, int arg) throws SQLException; 172 | abstract int value_type (Function f, int arg) throws SQLException; 173 | 174 | abstract int create_function(String name, Function f) throws SQLException; 175 | abstract int destroy_function(String name) throws SQLException; 176 | abstract void free_functions() throws SQLException; 177 | 178 | /** Provides metadata for the columns of a statement. Returns: 179 | * res[col][0] = true if column constrained NOT NULL 180 | * res[col][1] = true if column is part of the primary key 181 | * res[col][2] = true if column is auto-increment 182 | */ 183 | abstract boolean[][] column_metadata(long stmt) throws SQLException; 184 | 185 | 186 | // COMPOUND FUNCTIONS //////////////////////////////////////////// 187 | 188 | final synchronized String[] column_names(long stmt) throws SQLException { 189 | String[] names = new String[column_count(stmt)]; 190 | for (int i=0; i < names.length; i++) 191 | names[i] = column_name(stmt, i); 192 | return names; 193 | } 194 | 195 | final synchronized int sqlbind(long stmt, int pos, Object v) 196 | throws SQLException { 197 | pos++; 198 | if (v == null) { 199 | return bind_null(stmt, pos); 200 | } else if (v instanceof Integer) { 201 | return bind_int(stmt, pos, ((Integer)v).intValue()); 202 | } else if (v instanceof Long) { 203 | return bind_long(stmt, pos, ((Long)v).longValue()); 204 | } else if (v instanceof Double) { 205 | return bind_double(stmt, pos, ((Double)v).doubleValue()); 206 | } else if (v instanceof String) { 207 | return bind_text(stmt, pos, (String)v); 208 | } else if (v instanceof byte[]) { 209 | return bind_blob(stmt, pos, (byte[])v); 210 | } else { 211 | throw new SQLException("unexpected param type: "+v.getClass()); 212 | } 213 | } 214 | 215 | final synchronized int[] executeBatch(long stmt, int count, Object[] vals) 216 | throws SQLException { 217 | if (count < 1) throw new SQLException("count (" + count + ") < 1"); 218 | 219 | final int params = bind_parameter_count(stmt); 220 | 221 | int rc; 222 | int[] changes = new int[count]; 223 | 224 | try { 225 | for (int i=0; i < count; i++) { 226 | reset(stmt); 227 | for (int j=0; j < params; j++) 228 | if (sqlbind(stmt, j, vals[(i * params) + j]) != SQLITE_OK) 229 | throwex(); 230 | 231 | rc = step(stmt); 232 | if (rc != SQLITE_DONE) { 233 | reset(stmt); 234 | if (rc == SQLITE_ROW) throw new BatchUpdateException( 235 | "batch entry "+i+": query returns results", changes); 236 | throwex(); 237 | } 238 | 239 | changes[i] = changes(); 240 | } 241 | } finally { 242 | ensureAutoCommit(); 243 | } 244 | 245 | reset(stmt); 246 | return changes; 247 | } 248 | 249 | final synchronized boolean execute(Stmt stmt, Object[] vals) 250 | throws SQLException { 251 | if (vals != null) { 252 | final int params = bind_parameter_count(stmt.pointer); 253 | if (params != vals.length) 254 | throw new SQLException("assertion failure: param count (" 255 | + params + ") != value count (" + vals.length + ")"); 256 | 257 | for (int i=0; i < params; i++) 258 | if (sqlbind(stmt.pointer, i, vals[i]) != SQLITE_OK) throwex(); 259 | } 260 | 261 | switch (step(stmt.pointer)) { 262 | case SQLITE_DONE: 263 | reset(stmt.pointer); 264 | ensureAutoCommit(); 265 | return false; 266 | case SQLITE_ROW: 267 | return true; 268 | case SQLITE_BUSY: 269 | case SQLITE_LOCKED: 270 | throw new SQLException("database locked"); 271 | case SQLITE_MISUSE: 272 | throw new SQLException(errmsg()); 273 | default: 274 | finalize(stmt); 275 | throw new SQLException(errmsg()); 276 | } 277 | } 278 | 279 | final synchronized int executeUpdate(Stmt stmt, Object[] vals) 280 | throws SQLException { 281 | if (execute(stmt, vals)) 282 | throw new SQLException("query returns results"); 283 | reset(stmt.pointer); 284 | return changes(); 285 | } 286 | 287 | final void throwex() throws SQLException { 288 | throw new SQLException(errmsg()); 289 | } 290 | 291 | /* 292 | * SQLite and the JDBC API have very different ideas about the meaning 293 | * of auto-commit. Under JDBC, when executeUpdate() returns in 294 | * auto-commit mode (the default), the programmer assumes the data has 295 | * been written to disk. In SQLite however, a call to sqlite3_step() 296 | * with an INSERT statement can return SQLITE_OK, and yet the data is 297 | * still in limbo. 298 | * 299 | * This limbo appears when another statement on the database is active, 300 | * e.g. a SELECT. SQLite auto-commit waits until the final read 301 | * statement finishes, and then writes whatever updates have already 302 | * been OKed. So if a program crashes before the reads are complete, 303 | * data is lost. E.g: 304 | * 305 | * select begins 306 | * insert 307 | * select continues 308 | * select finishes 309 | * 310 | * Works as expected, however 311 | * 312 | * select beings 313 | * insert 314 | * select continues 315 | * crash 316 | * 317 | * Results in the data never being written to disk. 318 | * 319 | * As a solution, we call "commit" after every statement in auto-commit 320 | * mode. 321 | */ 322 | final void ensureAutoCommit() throws SQLException { 323 | /* 324 | if (!conn.getAutoCommit()) 325 | return; 326 | 327 | if (begin == 0) 328 | begin = prepare("begin;"); 329 | if (commit == 0) 330 | commit = prepare("commit;"); 331 | 332 | try { 333 | if (step(begin) != SQLITE_DONE) 334 | return; // assume we are in a transaction 335 | if (step(commit) != SQLITE_DONE) { 336 | reset(commit); 337 | throw new SQLException("unable to auto-commit"); 338 | } 339 | } finally { 340 | reset(begin); 341 | reset(commit); 342 | } 343 | */ 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/org/sqlite/Function.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | package org.sqlite; 17 | 18 | import java.sql.*; 19 | 20 | /** Provides an interface for creating SQLite user-defined functions. 21 | * 22 | *

A subclass of org.sqlite.Function can be registered with 23 | * Function.create() and called by the name it was given. All 24 | * functions must implement xFunc(), which is called when SQLite 25 | * runs the custom function.

26 | * 27 | * Eg. 28 | * 29 | *
 30 |  *      Class.forName("org.sqlite.JDBC");
 31 |  *      Connection conn = DriverManager.getConnection("jdbc:sqlite:");
 32 |  *
 33 |  *      Function.create(conn, "myFunc", new Function() {
 34 |  *          protected void xFunc() {
 35 |  *              System.out.println("myFunc called!");
 36 |  *          }
 37 |  *      });
 38 |  *
 39 |  *      conn.createStatement().execute("select myFunc();");
 40 |  *  
41 | * 42 | *

Arguments passed to a custom function can be accessed using the 43 | * protected functions provided. args() returns 44 | * the number of arguments passed, while 45 | * value_<type>(int) returns the value of the specific 46 | * argument. Similarly a function can return a value using the 47 | * result(<type>) function.

48 | * 49 | *

Aggregate functions are not yet supported, but coming soon.

50 | * 51 | */ 52 | public abstract class Function 53 | { 54 | private Conn conn; 55 | private DB db; 56 | 57 | long context = 0; // pointer sqlite3_context* 58 | long value = 0; // pointer sqlite3_value** 59 | int args = 0; 60 | 61 | /** Registers the given function with the Connection using the 62 | * provided name. */ 63 | public static final void create(Connection conn, String name, Function f) 64 | throws SQLException { 65 | if (conn == null || !(conn instanceof Conn)) 66 | throw new SQLException("connection must be to an SQLite db"); 67 | if (conn.isClosed()) 68 | throw new SQLException("connection closed"); 69 | 70 | f.conn = (Conn)conn; 71 | f.db = f.conn.db(); 72 | 73 | if (name == null || name.length() > 255) 74 | throw new SQLException("invalid function name: '"+name+"'"); 75 | 76 | if (f.db.create_function(name, f) != Codes.SQLITE_OK) 77 | throw new SQLException("error creating function"); 78 | } 79 | 80 | /** Removes the named function form the Connection. */ 81 | public static final void destroy(Connection conn, String name) 82 | throws SQLException { 83 | if (conn == null || !(conn instanceof Conn)) 84 | throw new SQLException("connection must be to an SQLite db"); 85 | ((Conn)conn).db().destroy_function(name); 86 | } 87 | 88 | 89 | /** Called by SQLite as a custom function. Should access arguments 90 | * through value_*(int), return results with 91 | * result(*) and throw errors with error(String). */ 92 | protected abstract void xFunc() throws SQLException; 93 | 94 | 95 | /** Returns the number of arguments passed to the function. 96 | * Can only be called from xFunc(). */ 97 | protected synchronized final int args() 98 | throws SQLException { checkContext(); return args; } 99 | 100 | /** Called by xFunc to return a value. */ 101 | protected synchronized final void result(byte[] value) 102 | throws SQLException { checkContext(); db.result_blob(context, value); } 103 | 104 | /** Called by xFunc to return a value. */ 105 | protected synchronized final void result(double value) 106 | throws SQLException { checkContext(); db.result_double(context,value);} 107 | 108 | /** Called by xFunc to return a value. */ 109 | protected synchronized final void result(int value) 110 | throws SQLException { checkContext(); db.result_int(context, value); } 111 | 112 | /** Called by xFunc to return a value. */ 113 | protected synchronized final void result(long value) 114 | throws SQLException { checkContext(); db.result_long(context, value); } 115 | 116 | /** Called by xFunc to return a value. */ 117 | protected synchronized final void result() 118 | throws SQLException { checkContext(); db.result_null(context); } 119 | 120 | /** Called by xFunc to return a value. */ 121 | protected synchronized final void result(String value) 122 | throws SQLException { checkContext(); db.result_text(context, value); } 123 | 124 | /** Called by xFunc to throw an error. */ 125 | protected synchronized final void error(String err) 126 | throws SQLException { checkContext(); db.result_error(context, err); } 127 | 128 | /** Called by xFunc to access the value of an argument. */ 129 | protected synchronized final int value_bytes(int arg) 130 | throws SQLException {checkValue(arg); return db.value_bytes(this,arg);} 131 | 132 | /** Called by xFunc to access the value of an argument. */ 133 | protected synchronized final String value_text(int arg) 134 | throws SQLException {checkValue(arg); return db.value_text(this,arg);} 135 | 136 | /** Called by xFunc to access the value of an argument. */ 137 | protected synchronized final byte[] value_blob(int arg) 138 | throws SQLException {checkValue(arg); return db.value_blob(this,arg); } 139 | 140 | /** Called by xFunc to access the value of an argument. */ 141 | protected synchronized final double value_double(int arg) 142 | throws SQLException {checkValue(arg); return db.value_double(this,arg);} 143 | 144 | /** Called by xFunc to access the value of an argument. */ 145 | protected synchronized final int value_int(int arg) 146 | throws SQLException {checkValue(arg); return db.value_int(this, arg); } 147 | 148 | /** Called by xFunc to access the value of an argument. */ 149 | protected synchronized final long value_long(int arg) 150 | throws SQLException { checkValue(arg); return db.value_long(this,arg); } 151 | 152 | /** Called by xFunc to access the value of an argument. */ 153 | protected synchronized final int value_type(int arg) 154 | throws SQLException {checkValue(arg); return db.value_type(this,arg); } 155 | 156 | 157 | private void checkContext() throws SQLException { 158 | if (conn == null || conn.db() == null || context == 0) 159 | throw new SQLException("no context, not allowed to read value"); 160 | } 161 | 162 | private void checkValue(int arg) throws SQLException { 163 | if (conn == null || conn.db() == null || value == 0) 164 | throw new SQLException("not in value access state"); 165 | if (arg >= args) 166 | throw new SQLException("arg "+arg+" out bounds [0,"+args+")"); 167 | } 168 | 169 | 170 | public static abstract class Aggregate 171 | extends Function 172 | implements Cloneable 173 | { 174 | protected final void xFunc() {} 175 | protected abstract void xStep() throws SQLException; 176 | protected abstract void xFinal() throws SQLException; 177 | 178 | public Object clone() throws CloneNotSupportedException { 179 | return super.clone(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/org/sqlite/JDBC.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package org.sqlite; 18 | 19 | import java.sql.*; 20 | import java.util.*; 21 | 22 | public class JDBC implements Driver 23 | { 24 | private static final String PREFIX = "jdbc:sqlite:"; 25 | 26 | static { 27 | try { 28 | DriverManager.registerDriver(new JDBC()); 29 | } catch (SQLException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | public int getMajorVersion() { return 1; } 35 | public int getMinorVersion() { return 1; } 36 | 37 | public boolean jdbcCompliant() { return false; } 38 | 39 | public boolean acceptsURL(String url) { 40 | return url != null && url.toLowerCase().startsWith(PREFIX); 41 | } 42 | 43 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) 44 | throws SQLException { 45 | DriverPropertyInfo sharedCache = new DriverPropertyInfo( 46 | "shared_cache", "false"); 47 | sharedCache.choices = new String[] { "true", "false" }; 48 | sharedCache.description = 49 | "Enable SQLite Shared-Cache mode, native driver only."; 50 | sharedCache.required = false; 51 | 52 | return new DriverPropertyInfo[] { sharedCache }; 53 | } 54 | 55 | public Connection connect(String url, Properties info) throws SQLException { 56 | if (!acceptsURL(url)) return null; 57 | url = url.trim(); 58 | 59 | // if no file name is given use a memory database 60 | String file = PREFIX.equalsIgnoreCase(url) ? 61 | ":memory:" : url.substring(PREFIX.length()); 62 | 63 | if (info.getProperty("shared_cache") == null) 64 | return new Conn(url, file); 65 | else 66 | return new Conn(url, file, 67 | Boolean.parseBoolean(info.getProperty("shared_cache"))); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/org/sqlite/NativeDB.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package org.sqlite; 18 | 19 | import java.io.*; 20 | import java.sql.SQLException; 21 | 22 | /** This class provides a thin JNI layer over the SQLite3 C API. */ 23 | final class NativeDB extends DB 24 | { 25 | /** SQLite connection handle. */ 26 | long pointer = 0; 27 | 28 | private static Boolean loaded = null; 29 | 30 | static boolean load() { 31 | if (loaded != null) return loaded == Boolean.TRUE; 32 | 33 | String libpath = System.getProperty("org.sqlite.lib.path"); 34 | String libname = System.getProperty("org.sqlite.lib.name"); 35 | if (libname == null) libname = System.mapLibraryName("sqlitejdbc"); 36 | 37 | // look for a pre-installed library 38 | try { 39 | if (libpath == null) System.loadLibrary("sqlitejdbc"); 40 | else System.load(new File(libpath, libname).getAbsolutePath()); 41 | 42 | loaded = Boolean.TRUE; 43 | return true; 44 | } catch (UnsatisfiedLinkError e) { } // fall through 45 | 46 | // guess what a bundled library would be called 47 | String osname = System.getProperty("os.name").toLowerCase(); 48 | String osarch = System.getProperty("os.arch"); 49 | if (osname.startsWith("mac os")) { 50 | osname = "mac"; 51 | osarch = "universal"; 52 | } 53 | if (osname.startsWith("windows")) 54 | osname = "win"; 55 | if (osname.startsWith("sunos")) 56 | osname = "solaris"; 57 | if (osarch.startsWith("i") && osarch.endsWith("86")) 58 | osarch = "x86"; 59 | libname = osname + '-' + osarch + ".lib"; 60 | 61 | // try a bundled library 62 | try { 63 | ClassLoader cl = NativeDB.class.getClassLoader(); 64 | InputStream in = cl.getResourceAsStream(libname); 65 | if (in == null) 66 | throw new Exception("libname: "+libname+" not found"); 67 | File tmplib = File.createTempFile("libsqlitejdbc-", ".lib"); 68 | tmplib.deleteOnExit(); 69 | OutputStream out = new FileOutputStream(tmplib); 70 | byte[] buf = new byte[1024]; 71 | for (int len; (len = in.read(buf)) != -1;) 72 | out.write(buf, 0, len); 73 | in.close(); 74 | out.close(); 75 | 76 | System.load(tmplib.getAbsolutePath()); 77 | 78 | loaded = Boolean.TRUE; 79 | return true; 80 | } catch (Exception e) { } 81 | 82 | loaded = Boolean.FALSE; 83 | return false; 84 | } 85 | 86 | 87 | /** linked list of all instanced UDFDatas */ 88 | private long udfdatalist = 0; 89 | 90 | 91 | // WRAPPER FUNCTIONS //////////////////////////////////////////// 92 | 93 | protected native synchronized void _open(String file) throws SQLException; 94 | protected native synchronized void _close() throws SQLException; 95 | native synchronized int shared_cache(boolean enable); 96 | native synchronized void interrupt(); 97 | native synchronized void busy_timeout(int ms); 98 | //native synchronized void exec(String sql) throws SQLException; 99 | protected native synchronized long prepare(String sql) throws SQLException; 100 | native synchronized String errmsg(); 101 | native synchronized String libversion(); 102 | native synchronized int changes(); 103 | 104 | protected native synchronized int finalize(long stmt); 105 | protected native synchronized int step(long stmt); 106 | protected native synchronized int reset(long stmt); 107 | native synchronized int clear_bindings(long stmt); 108 | 109 | native synchronized int bind_parameter_count(long stmt); 110 | 111 | native synchronized int column_count (long stmt); 112 | native synchronized int column_type (long stmt, int col); 113 | native synchronized String column_decltype (long stmt, int col); 114 | native synchronized String column_table_name (long stmt, int col); 115 | native synchronized String column_name (long stmt, int col); 116 | native synchronized String column_text (long stmt, int col); 117 | native synchronized byte[] column_blob (long stmt, int col); 118 | native synchronized double column_double (long stmt, int col); 119 | native synchronized long column_long (long stmt, int col); 120 | native synchronized int column_int (long stmt, int col); 121 | 122 | native synchronized int bind_null (long stmt, int pos); 123 | native synchronized int bind_int (long stmt, int pos, int v); 124 | native synchronized int bind_long (long stmt, int pos, long v); 125 | native synchronized int bind_double(long stmt, int pos, double v); 126 | native synchronized int bind_text (long stmt, int pos, String v); 127 | native synchronized int bind_blob (long stmt, int pos, byte[] v); 128 | 129 | native synchronized void result_null (long context); 130 | native synchronized void result_text (long context, String val); 131 | native synchronized void result_blob (long context, byte[] val); 132 | native synchronized void result_double(long context, double val); 133 | native synchronized void result_long (long context, long val); 134 | native synchronized void result_int (long context, int val); 135 | native synchronized void result_error (long context, String err); 136 | 137 | native synchronized int value_bytes (Function f, int arg); 138 | native synchronized String value_text (Function f, int arg); 139 | native synchronized byte[] value_blob (Function f, int arg); 140 | native synchronized double value_double(Function f, int arg); 141 | native synchronized long value_long (Function f, int arg); 142 | native synchronized int value_int (Function f, int arg); 143 | native synchronized int value_type (Function f, int arg); 144 | 145 | native synchronized int create_function(String name, Function func); 146 | native synchronized int destroy_function(String name); 147 | native synchronized void free_functions(); 148 | 149 | // COMPOUND FUNCTIONS (for optimisation) ///////////////////////// 150 | 151 | /** Provides metadata for the columns of a statement. Returns: 152 | * res[col][0] = true if column constrained NOT NULL 153 | * res[col][1] = true if column is part of the primary key 154 | * res[col][2] = true if column is auto-increment 155 | */ 156 | native synchronized boolean[][] column_metadata(long stmt); 157 | 158 | static void throwex(String msg) throws SQLException { 159 | throw new SQLException(msg); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/org/sqlite/NestedDB.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include "sqlite3.h" 19 | 20 | /* Provides access to metadata across NestedVM 7-argument limit on functions.*/ 21 | struct metadata { 22 | int pNotNull; 23 | int pPrimaryKey; 24 | int pAutoinc; 25 | }; 26 | 27 | int column_metadata_helper( 28 | sqlite3 *db, 29 | sqlite3_stmt *stmt, 30 | int col, 31 | struct metadata *p 32 | ){ 33 | const char *zTableName, *zColumnName; 34 | int rc = 0; 35 | 36 | p->pNotNull = 0; 37 | p->pPrimaryKey = 0; 38 | p->pAutoinc = 0; 39 | 40 | zTableName = sqlite3_column_table_name(stmt, col); 41 | zColumnName = sqlite3_column_name(stmt, col); 42 | 43 | if (zTableName && zColumnName) { 44 | rc = sqlite3_table_column_metadata( 45 | db, 0, zTableName, zColumnName, 0, 0, 46 | &p->pNotNull, &p->pPrimaryKey, &p->pAutoinc 47 | ); 48 | } 49 | 50 | return rc; 51 | } 52 | 53 | 54 | extern int _call_java(int xType, int context, int args, int value); 55 | 56 | void xFunc_helper(sqlite3_context *context, int args, sqlite3_value** value) 57 | { 58 | _call_java(1, (int)context, args, (int)value); 59 | } 60 | 61 | void xStep_helper(sqlite3_context *context, int args, sqlite3_value** value) 62 | { 63 | _call_java(2, (int)context, args, (int)value); 64 | } 65 | 66 | void xFinal_helper(sqlite3_context *context) 67 | { 68 | _call_java(3, (int)context, 0, 0); 69 | } 70 | 71 | /* create function if pos is non-negative, aggregate if agg is true */ 72 | int create_function_helper(sqlite3 *db, const char *name, int pos, int agg) 73 | { 74 | return sqlite3_create_function(db, name, -1, SQLITE_ANY, (void*)pos, 75 | pos>=0 && !agg ? &xFunc_helper : 0, 76 | pos>=0 && agg ? &xStep_helper : 0, 77 | pos>=0 && agg ? &xFinal_helper : 0); 78 | } 79 | -------------------------------------------------------------------------------- /src/org/sqlite/NestedDB.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | package org.sqlite; 17 | 18 | import org.ibex.nestedvm.Runtime; 19 | 20 | import java.io.File; 21 | import java.io.PrintWriter; 22 | import java.sql.*; 23 | 24 | // FEATURE: strdup is wasteful, SQLite interface will take unterminated char* 25 | 26 | /** Communicates with the Java version of SQLite provided by NestedVM. */ 27 | final class NestedDB extends DB implements Runtime.CallJavaCB 28 | { 29 | /** database pointer */ 30 | int handle = 0; 31 | 32 | /** sqlite binary embedded in nestedvm */ 33 | private Runtime rt = null; 34 | 35 | /** user defined functions referenced by position (stored in used data) */ 36 | private Function[] functions = null; 37 | private String[] funcNames = null; 38 | 39 | 40 | // WRAPPER FUNCTIONS //////////////////////////////////////////// 41 | 42 | protected synchronized void _open(String filename) throws SQLException { 43 | if (handle != 0) throw new SQLException("DB already open"); 44 | 45 | // handle silly windows drive letter mapping 46 | if (filename.length() > 2) { 47 | char drive = Character.toLowerCase(filename.charAt(0)); 48 | if (filename.charAt(1) == ':' && drive >= 'a' && drive <= 'z') { 49 | 50 | // convert to nestedvm's "/c:/file" format 51 | filename = filename.substring(2); 52 | filename = filename.replace('\\', '/'); 53 | filename = "/" + drive + ":" + filename; 54 | } 55 | } 56 | 57 | // start the nestedvm runtime 58 | try { 59 | rt = (Runtime)Class.forName("org.sqlite.SQLite") 60 | .newInstance(); 61 | rt.start(); 62 | } catch (Exception e) { 63 | throw new CausedSQLException(e); 64 | } 65 | 66 | // callback for user defined functions 67 | rt.setCallJavaCB(this); 68 | 69 | // open the db and retrieve sqlite3_db* pointer 70 | int passback = rt.xmalloc(4); 71 | int str = rt.strdup(filename); 72 | if (call("sqlite3_open", str, passback) != SQLITE_OK) 73 | throwex(); 74 | handle = deref(passback); 75 | rt.free(str); 76 | rt.free(passback); 77 | } 78 | 79 | /* callback for Runtime.CallJavaCB above */ 80 | public int call(int xType, int context, int args, int value) { 81 | xUDF(xType, context, args, value); 82 | return 0; 83 | } 84 | 85 | protected synchronized void _close() throws SQLException { 86 | if (handle == 0) return; 87 | try { 88 | if (call("sqlite3_close", handle) != SQLITE_OK) 89 | throwex(); 90 | } finally { 91 | handle = 0; 92 | rt.stop(); 93 | rt = null; 94 | } 95 | } 96 | 97 | int shared_cache(boolean enable) throws SQLException { 98 | // The shared cache is per-process, so it is useless as 99 | // each nested connection is its own process. 100 | return -1; 101 | } 102 | synchronized void interrupt() throws SQLException { 103 | call("sqlite3_interrupt", handle); 104 | } 105 | synchronized void busy_timeout(int ms) throws SQLException { 106 | call("sqlite3_busy_timeout", handle, ms); 107 | } 108 | protected synchronized long prepare(String sql) throws SQLException { 109 | int passback = rt.xmalloc(4); 110 | int str = rt.strdup(sql); 111 | int ret = call("sqlite3_prepare_v2", handle, str, -1, passback, 0); 112 | rt.free(str); 113 | if (ret != SQLITE_OK) { 114 | rt.free(passback); 115 | throwex(); 116 | } 117 | int pointer = deref(passback); 118 | rt.free(passback); 119 | return pointer; 120 | } 121 | synchronized String errmsg() throws SQLException { 122 | return cstring(call("sqlite3_errmsg", handle)); 123 | } 124 | synchronized String libversion() throws SQLException { 125 | return cstring(call("sqlite3_libversion", handle)); 126 | } 127 | synchronized int changes() throws SQLException { 128 | return call("sqlite3_changes", handle); } 129 | 130 | protected synchronized int finalize(long stmt) throws SQLException { 131 | return call("sqlite3_finalize", (int)stmt); } 132 | protected synchronized int step(long stmt) throws SQLException { 133 | return call("sqlite3_step", (int)stmt); } 134 | protected synchronized int reset(long stmt) throws SQLException { 135 | return call("sqlite3_reset", (int)stmt); } 136 | synchronized int clear_bindings(long stmt) throws SQLException { 137 | return call("sqlite3_clear_bindings", (int)stmt); } 138 | 139 | synchronized int bind_parameter_count(long stmt) throws SQLException { 140 | return call("sqlite3_bind_parameter_count", (int)stmt); } 141 | 142 | synchronized int column_count(long stmt) throws SQLException { 143 | return call("sqlite3_column_count", (int)stmt); } 144 | synchronized int column_type(long stmt, int col) throws SQLException { 145 | return call("sqlite3_column_type", (int)stmt, col); } 146 | synchronized String column_name(long stmt, int col) throws SQLException { 147 | return utfstring(call("sqlite3_column_name", (int)stmt, col)); 148 | } 149 | synchronized String column_text(long stmt, int col) throws SQLException { 150 | return utfstring(call("sqlite3_column_text", (int)stmt, col)); 151 | } 152 | synchronized byte[] column_blob(long stmt, int col) throws SQLException { 153 | int addr = call("sqlite3_column_blob", (int)stmt, col); 154 | if (addr == 0) return null; 155 | byte[] blob = new byte[call("sqlite3_column_bytes", (int)stmt, col)]; 156 | copyin(addr, blob, blob.length); 157 | return blob; 158 | } 159 | synchronized double column_double(long stmt, int col) throws SQLException { 160 | try { return Double.parseDouble(column_text(stmt, col)); } 161 | catch (NumberFormatException e) { return Double.NaN; } // TODO 162 | } 163 | synchronized long column_long(long stmt, int col) throws SQLException { 164 | try { return Long.parseLong(column_text(stmt, col)); } 165 | catch (NumberFormatException e) { return 0; } // TODO 166 | } 167 | synchronized int column_int(long stmt, int col) throws SQLException { 168 | return call("sqlite3_column_int", (int)stmt, col); } 169 | synchronized String column_decltype(long stmt, int col) 170 | throws SQLException { 171 | return utfstring(call("sqlite3_column_decltype", (int)stmt, col)); 172 | } 173 | synchronized String column_table_name(long stmt, int col) 174 | throws SQLException { 175 | return utfstring(call("sqlite3_column_table_name", (int)stmt, col)); 176 | } 177 | 178 | synchronized int bind_null(long stmt, int pos) throws SQLException { 179 | return call("sqlite3_bind_null", (int)stmt, pos); 180 | } 181 | synchronized int bind_int(long stmt, int pos, int v) throws SQLException { 182 | return call("sqlite3_bind_int", (int)stmt, pos, v); 183 | } 184 | synchronized int bind_long(long stmt, int pos, long v) throws SQLException { 185 | return bind_text(stmt, pos, Long.toString(v)); // TODO 186 | } 187 | synchronized int bind_double(long stmt, int pos, double v) 188 | throws SQLException { 189 | return bind_text(stmt, pos, Double.toString(v)); // TODO 190 | } 191 | synchronized int bind_text(long stmt, int pos, String v) 192 | throws SQLException { 193 | if (v == null) return bind_null(stmt, pos); 194 | return call("sqlite3_bind_text", (int)stmt, pos, rt.strdup(v), 195 | -1, rt.lookupSymbol("free")); 196 | } 197 | synchronized int bind_blob(long stmt, int pos, byte[] buf) 198 | throws SQLException { 199 | if (buf == null || buf.length < 1) return bind_null(stmt, pos); 200 | int len = buf.length; 201 | int blob = rt.xmalloc(len); // free()ed by sqlite3_bind_blob 202 | copyout(buf, blob, len); 203 | return call("sqlite3_bind_blob", (int)stmt, pos, blob, len, 204 | rt.lookupSymbol("free")); 205 | } 206 | 207 | synchronized void result_null(long cxt) throws SQLException { 208 | call("sqlite3_result_null", (int)cxt); } 209 | synchronized void result_text(long cxt, String val) throws SQLException { 210 | call("sqlite3_result_text", (int)cxt, rt.strdup(val), -1, 211 | rt.lookupSymbol("free")); 212 | } 213 | synchronized void result_blob(long cxt, byte[] val) throws SQLException { 214 | if (val == null || val.length == 0) { result_null(cxt); return; } 215 | int blob = rt.xmalloc(val.length); 216 | copyout(val, blob, val.length); 217 | call("sqlite3_result_blob", (int)cxt, blob, 218 | val.length, rt.lookupSymbol("free")); 219 | } 220 | synchronized void result_double(long cxt, double val) throws SQLException { 221 | result_text(cxt, Double.toString(val)); } // TODO 222 | synchronized void result_long(long cxt, long val) throws SQLException { 223 | result_text(cxt, Long.toString(val)); } // TODO 224 | synchronized void result_int(long cxt, int val) throws SQLException { 225 | call("sqlite3_result_int", (int)cxt, val); } 226 | synchronized void result_error(long cxt, String err) throws SQLException { 227 | int str = rt.strdup(err); 228 | call("sqlite3_result_error", (int)cxt, str, -1); 229 | rt.free(str); 230 | } 231 | 232 | synchronized int value_bytes(Function f, int arg) throws SQLException { 233 | return call("sqlite3_value_bytes", value(f, arg)); 234 | } 235 | synchronized String value_text(Function f, int arg) throws SQLException { 236 | return utfstring(call("sqlite3_value_text", value(f, arg))); 237 | } 238 | synchronized byte[] value_blob(Function f, int arg) throws SQLException { 239 | int addr = call("sqlite3_value_blob", value(f, arg)); 240 | if (addr == 0) return null; 241 | byte[] blob = new byte[value_bytes(f, arg)]; 242 | copyin(addr, blob, blob.length); 243 | return blob; 244 | } 245 | synchronized double value_double(Function f, int arg) throws SQLException { 246 | return Double.parseDouble(value_text(f, arg)); // TODO 247 | } 248 | synchronized long value_long(Function f, int arg) throws SQLException { 249 | return Long.parseLong(value_text(f, arg)); // TODO 250 | } 251 | synchronized int value_int(Function f, int arg) throws SQLException { 252 | return call("sqlite3_value_int", value(f, arg)); 253 | } 254 | synchronized int value_type(Function f, int arg) throws SQLException { 255 | return call("sqlite3_value_type", value(f, arg)); 256 | } 257 | 258 | private int value(Function f, int arg) throws SQLException { 259 | return deref((int)f.value + (arg*4)); 260 | } 261 | 262 | 263 | synchronized int create_function(String name, Function func) 264 | throws SQLException { 265 | if (functions == null) { 266 | functions = new Function[10]; 267 | funcNames = new String[10]; 268 | } 269 | 270 | // find a position 271 | int pos; 272 | for (pos=0; pos < functions.length; pos++) 273 | if (functions[pos] == null) break; 274 | 275 | if (pos == functions.length) { // expand function arrays 276 | Function[] fnew = new Function[functions.length * 2]; 277 | String[] nnew = new String[funcNames.length * 2]; 278 | System.arraycopy(functions, 0, fnew, 0, functions.length); 279 | System.arraycopy(funcNames, 0, nnew, 0, funcNames.length); 280 | functions = fnew; 281 | funcNames = nnew; 282 | } 283 | 284 | // register function 285 | functions[pos] = func; 286 | funcNames[pos] = name; 287 | int rc; 288 | int str = rt.strdup(name); 289 | rc = call("create_function_helper", handle, str, pos, 290 | func instanceof Function.Aggregate ? 1 : 0); 291 | rt.free(str); 292 | return rc; 293 | } 294 | 295 | synchronized int destroy_function(String name) throws SQLException { 296 | if (name == null) return 0; 297 | 298 | // find function position number 299 | int pos; 300 | for (pos = 0; pos < funcNames.length; pos++) 301 | if (name.equals(funcNames[pos])) break; 302 | if (pos == funcNames.length) return 0; 303 | 304 | functions[pos] = null; 305 | funcNames[pos] = null; 306 | 307 | // deregister function 308 | int rc; 309 | int str = rt.strdup(name); 310 | rc = call("create_function_helper", handle, str, -1, 0); 311 | rt.free(str); 312 | return rc; 313 | } 314 | 315 | /* unused as we use the user_data pointer to store a single word */ 316 | synchronized void free_functions() {} 317 | 318 | /** Callback used by xFunc (1), xStep (2) and xFinal (3). */ 319 | synchronized void xUDF(int xType, int context, int args, int value) { 320 | Function func = null; 321 | 322 | try { 323 | int pos = call("sqlite3_user_data", context); 324 | func = functions[pos]; 325 | if (func == null) 326 | throw new SQLException("function state inconsistent"); 327 | 328 | func.context = context; 329 | func.value = value; 330 | func.args = args; 331 | 332 | switch (xType) { 333 | case 1: func.xFunc(); break; 334 | case 2: ((Function.Aggregate)func).xStep(); break; 335 | case 3: ((Function.Aggregate)func).xFinal(); break; 336 | } 337 | } catch (SQLException e) { 338 | try { 339 | String err = e.toString(); 340 | if (err == null) err = "unknown error"; 341 | int str = rt.strdup(err); 342 | call("sqlite3_result_error", context, str, -1); 343 | rt.free(str); 344 | } catch (SQLException exp) { 345 | exp.printStackTrace();//TODO 346 | } 347 | } finally { 348 | if (func != null) { 349 | func.context = 0; 350 | func.value = 0; 351 | func.args = 0; 352 | } 353 | } 354 | } 355 | 356 | 357 | /** Calls support function found in upstream/sqlite-metadata.patch */ 358 | synchronized boolean[][] column_metadata(long stmt) throws SQLException { 359 | int colCount = call("sqlite3_column_count", (int)stmt); 360 | boolean[][] meta = new boolean[colCount][3]; 361 | int pass; 362 | 363 | pass = rt.xmalloc(12); // struct metadata 364 | 365 | for (int i=0; i < colCount; i++) { 366 | call("column_metadata_helper", handle, (int)stmt, i, pass); 367 | meta[i][0] = deref(pass) == 1; 368 | meta[i][1] = deref(pass + 4) == 1; 369 | meta[i][2] = deref(pass + 8) == 1; 370 | } 371 | 372 | rt.free(pass); 373 | return meta; 374 | } 375 | 376 | 377 | // HELPER FUNCTIONS ///////////////////////////////////////////// 378 | 379 | /** safe to reuse parameter arrays as all functions are syncrhonized */ 380 | private final int[] 381 | p0 = new int[] {}, 382 | p1 = new int[] { 0 }, 383 | p2 = new int[] { 0, 0 }, 384 | p3 = new int[] { 0, 0, 0 }, 385 | p4 = new int[] { 0, 0, 0, 0 }, 386 | p5 = new int[] { 0, 0, 0, 0, 0 }; 387 | 388 | private int call(String addr, int a0) throws SQLException { 389 | p1[0] = a0; return call(addr, p1); } 390 | private int call(String addr, int a0, int a1) throws SQLException { 391 | p2[0] = a0; p2[1] = a1; return call(addr, p2); } 392 | private int call(String addr, int a0, int a1, int a2) throws SQLException { 393 | p3[0] = a0; p3[1] = a1; p3[2] = a2; return call(addr, p3); } 394 | private int call(String addr, int a0, int a1, int a2, int a3) 395 | throws SQLException { 396 | p4[0] = a0; p4[1] = a1; p4[2] = a2; p4[3] = a3; 397 | return call(addr, p4); 398 | } 399 | private int call(String addr, int a0, int a1, int a2, int a3, int a4) 400 | throws SQLException { 401 | p5[0] = a0; p5[1] = a1; p5[2] = a2; p5[3] = a3; p5[4] = a4; 402 | return call(addr, p5); 403 | } 404 | private int call(String func, int[] args) throws SQLException { 405 | try { 406 | return rt.call(func, args); 407 | } catch (Runtime.CallException e) { throw new CausedSQLException(e); } 408 | } 409 | 410 | /** Dereferences a pointer, returning the word it points to. */ 411 | private int deref(int pointer) throws SQLException { 412 | try { return rt.memRead(pointer); } 413 | catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e);} 414 | } 415 | private String utfstring(int str) throws SQLException { 416 | try { return rt.utfstring(str); } 417 | catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e);} 418 | } 419 | private String cstring(int str) throws SQLException { 420 | try { return rt.cstring(str); } 421 | catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e);} 422 | } 423 | private void copyin(int addr, byte[] buf, int count) throws SQLException { 424 | try { rt.copyin(addr, buf, count); } 425 | catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e);} 426 | } 427 | private void copyout(byte[] buf, int addr, int count) throws SQLException { 428 | try { rt.copyout(buf, addr, count); } 429 | catch (Runtime.FaultException e) { throw new CausedSQLException(e);} 430 | } 431 | 432 | /** Maps any exception onto an SQLException. */ 433 | private static final class CausedSQLException extends SQLException { 434 | private final Exception cause; 435 | CausedSQLException(Exception e) { 436 | if (e == null) throw new RuntimeException("null exception cause"); 437 | cause = e; 438 | } 439 | public Throwable getCause() { return cause; } 440 | public void printStackTrace() { cause.printStackTrace(); } 441 | public void printStackTrace(PrintWriter s) { cause.printStackTrace(s); } 442 | public Throwable fillInStackTrace() { return cause.fillInStackTrace(); } 443 | public StackTraceElement[] getStackTrace() { 444 | return cause.getStackTrace(); } 445 | public String getMessage() { return cause.getMessage(); } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/org/sqlite/PrepStmt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package org.sqlite; 18 | 19 | import java.io.Reader; 20 | import java.io.InputStream; 21 | import java.net.URL; 22 | import java.sql.*; 23 | import java.math.BigDecimal; 24 | import java.util.ArrayList; 25 | import java.util.Calendar; 26 | 27 | final class PrepStmt extends Stmt 28 | implements PreparedStatement, ParameterMetaData, Codes 29 | { 30 | private int columnCount; 31 | private int paramCount; 32 | 33 | PrepStmt(Conn conn, String sql) throws SQLException { 34 | super(conn); 35 | 36 | this.sql = sql; 37 | db.prepare(this); 38 | rs.colsMeta = db.column_names(pointer); 39 | columnCount = db.column_count(pointer); 40 | paramCount = db.bind_parameter_count(pointer); 41 | batch = new Object[paramCount]; 42 | batchPos = 0; 43 | } 44 | 45 | public void clearParameters() throws SQLException { 46 | checkOpen(); 47 | db.reset(pointer); 48 | clearBatch(); 49 | } 50 | 51 | protected void finalize() throws SQLException { close(); } 52 | 53 | public boolean execute() throws SQLException { 54 | checkOpen(); 55 | rs.close(); 56 | db.reset(pointer); 57 | resultsWaiting = db.execute(this, batch); 58 | return columnCount != 0; 59 | } 60 | 61 | public ResultSet executeQuery() throws SQLException { 62 | checkOpen(); 63 | if (columnCount == 0) 64 | throw new SQLException("query does not return results"); 65 | rs.close(); 66 | db.reset(pointer); 67 | resultsWaiting = db.execute(this, batch); 68 | return getResultSet(); 69 | } 70 | 71 | public int executeUpdate() throws SQLException { 72 | checkOpen(); 73 | if (columnCount != 0) 74 | throw new SQLException("query returns results"); 75 | rs.close(); 76 | db.reset(pointer); 77 | return db.executeUpdate(this, batch); 78 | } 79 | 80 | public int[] executeBatch() throws SQLException { 81 | if (batchPos == 0) return new int[] {}; 82 | try { 83 | return db.executeBatch(pointer, batchPos / paramCount, batch); 84 | } finally { 85 | clearBatch(); 86 | } 87 | } 88 | 89 | public int getUpdateCount() throws SQLException { 90 | checkOpen(); 91 | if (pointer == 0 || resultsWaiting) return -1; 92 | return db.changes(); 93 | } 94 | 95 | public void addBatch() throws SQLException { 96 | checkOpen(); 97 | batchPos += paramCount; 98 | if (batchPos + paramCount > batch.length) { 99 | Object[] nb = new Object[batch.length * 2]; 100 | System.arraycopy(batch, 0, nb, 0, batch.length); 101 | batch = nb; 102 | } 103 | System.arraycopy(batch, batchPos - paramCount, 104 | batch, batchPos, paramCount); 105 | } 106 | 107 | 108 | // ParameterMetaData FUNCTIONS ////////////////////////////////// 109 | 110 | public ParameterMetaData getParameterMetaData() { return this; } 111 | 112 | public int getParameterCount() throws SQLException { 113 | checkOpen(); return paramCount; } 114 | public String getParameterClassName(int param) throws SQLException { 115 | checkOpen(); return "java.lang.String"; } 116 | public String getParameterTypeName(int pos) { return "VARCHAR"; } 117 | public int getParameterType(int pos) { return Types.VARCHAR; } 118 | public int getParameterMode(int pos) { return parameterModeIn; } 119 | public int getPrecision(int pos) { return 0; } 120 | public int getScale(int pos) { return 0; } 121 | public int isNullable(int pos) { return parameterNullable; } 122 | public boolean isSigned(int pos) { return true; } 123 | public Statement getStatement() { return this; } 124 | 125 | 126 | // PARAMETER FUNCTIONS ////////////////////////////////////////// 127 | 128 | private void batch(int pos, Object value) throws SQLException { 129 | checkOpen(); 130 | if (batch == null) batch = new Object[paramCount]; 131 | batch[batchPos + pos - 1] = value; 132 | } 133 | 134 | public void setBoolean(int pos, boolean value) throws SQLException { 135 | setInt(pos, value ? 1 : 0); 136 | } 137 | public void setByte(int pos, byte value) throws SQLException { 138 | setInt(pos, (int)value); 139 | } 140 | public void setBytes(int pos, byte[] value) throws SQLException { 141 | batch(pos, value); 142 | } 143 | public void setDouble(int pos, double value) throws SQLException { 144 | batch(pos, new Double(value)); 145 | } 146 | public void setFloat(int pos, float value) throws SQLException { 147 | setDouble(pos, value); 148 | } 149 | public void setInt(int pos, int value) throws SQLException { 150 | batch(pos, new Integer(value)); 151 | } 152 | public void setLong(int pos, long value) throws SQLException { 153 | batch(pos, new Long(value)); 154 | } 155 | public void setNull(int pos, int u1) throws SQLException { 156 | setNull(pos, u1, null); 157 | } 158 | public void setNull(int pos, int u1, String u2) throws SQLException { 159 | batch(pos, null); 160 | } 161 | public void setObject(int pos, Object value) throws SQLException { 162 | if (value == null) 163 | batch(pos, null); 164 | else if (value instanceof java.util.Date) 165 | batch(pos, new Long(((java.util.Date)value).getTime())); 166 | else if (value instanceof Date) 167 | batch(pos, new Long(((Date)value).getTime())); 168 | else if (value instanceof Time) 169 | batch(pos, new Long(((Time)value).getTime())); 170 | else if (value instanceof Timestamp) 171 | batch(pos, new Long(((Timestamp)value).getTime())); 172 | else if (value instanceof Long) batch(pos, value); 173 | else if (value instanceof Integer) batch(pos, value); 174 | else if (value instanceof Float) batch(pos, value); 175 | else if (value instanceof Double) batch(pos, value); 176 | else 177 | batch(pos, value.toString()); 178 | } 179 | public void setObject(int p, Object v, int t) throws SQLException { 180 | setObject(p, v); } 181 | public void setObject(int p, Object v, int t, int s) throws SQLException { 182 | setObject(p, v); } 183 | public void setShort(int pos, short value) throws SQLException { 184 | setInt(pos, (int)value); } 185 | public void setString(int pos, String value) throws SQLException { 186 | batch(pos, value); 187 | } 188 | public void setDate(int pos, Date x) throws SQLException { 189 | setObject(pos, x); } 190 | public void setDate(int pos, Date x, Calendar cal) throws SQLException { 191 | setObject(pos, x); } 192 | public void setTime(int pos, Time x) throws SQLException { 193 | setObject(pos, x); } 194 | public void setTime(int pos, Time x, Calendar cal) throws SQLException { 195 | setObject(pos, x); } 196 | public void setTimestamp(int pos, Timestamp x) throws SQLException { 197 | setObject(pos, x); } 198 | public void setTimestamp(int pos, Timestamp x, Calendar cal) 199 | throws SQLException { 200 | setObject(pos, x); 201 | } 202 | 203 | public ResultSetMetaData getMetaData() throws SQLException { 204 | checkOpen(); return rs; } 205 | 206 | 207 | // UNUSED /////////////////////////////////////////////////////// 208 | 209 | public boolean execute(String sql) 210 | throws SQLException { throw unused(); } 211 | public int executeUpdate(String sql) 212 | throws SQLException { throw unused(); } 213 | public ResultSet executeQuery(String sql) 214 | throws SQLException { throw unused(); } 215 | public void addBatch(String sql) 216 | throws SQLException { throw unused(); } 217 | 218 | private SQLException unused() { 219 | return new SQLException("not supported by PreparedStatment"); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/org/sqlite/RS.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | package org.sqlite; 17 | 18 | import java.sql.*; 19 | 20 | import java.io.InputStream; 21 | import java.io.Reader; 22 | import java.math.BigDecimal; 23 | import java.net.URL; 24 | import java.util.Calendar; 25 | import java.util.Map; 26 | 27 | /** 28 | * Implements a JDBC ResultSet. 29 | */ 30 | final class RS extends Unused implements ResultSet, ResultSetMetaData, Codes 31 | { 32 | private final Stmt stmt; 33 | private final DB db; 34 | 35 | boolean open = false ; // true means have results and can iterate them 36 | int maxRows; // max. number of rows as set by a Statement 37 | String[] cols = null; // if null, the RS is closed() 38 | String[] colsMeta = null; // same as cols, but used by Meta interface 39 | boolean[][] meta = null; 40 | 41 | private int limitRows; // 0 means no limit, must check against maxRows 42 | private int row = 1; // number of current row, starts at 1 43 | private int lastCol; // last column accessed, for wasNull(). -1 if none 44 | 45 | RS(Stmt stmt) { 46 | this.stmt = stmt; 47 | this.db = stmt.db; 48 | } 49 | 50 | 51 | // INTERNAL FUNCTIONS /////////////////////////////////////////// 52 | 53 | boolean isOpen() { return open; } 54 | 55 | /* Throws SQLException if ResultSet is not open. */ 56 | void checkOpen() throws SQLException { 57 | if (!open) throw new SQLException("ResultSet closed"); 58 | } 59 | 60 | // takes col in [1,x] form, returns in [0,x-1] form 61 | private int checkCol(int col) throws SQLException { 62 | if (colsMeta == null) throw new IllegalStateException( 63 | "SQLite JDBC: inconsistent internal state"); 64 | if (col < 1 || col > colsMeta.length) throw new SQLException( 65 | "column " + col + " out of bounds [1," + colsMeta.length + "]"); 66 | return --col; 67 | } 68 | 69 | // takes col in [1,x] form, marks it as last accessed and returns [0,x-1] 70 | private int markCol(int col) throws SQLException { 71 | checkOpen(); checkCol(col); lastCol = col; return --col; 72 | } 73 | 74 | private void checkMeta() throws SQLException { 75 | checkCol(1); 76 | if (meta == null) meta = db.column_metadata(stmt.pointer); 77 | } 78 | 79 | 80 | // ResultSet Functions ////////////////////////////////////////// 81 | 82 | public boolean isClosed() throws SQLException { 83 | return !open; 84 | } 85 | 86 | public void close() throws SQLException { 87 | cols = null; 88 | colsMeta = null; 89 | meta = null; 90 | open = false; 91 | limitRows = 0; 92 | row = 1; 93 | lastCol = -1; 94 | 95 | if (stmt == null) 96 | return; 97 | if (stmt != null && stmt.pointer != 0) 98 | db.reset(stmt.pointer); 99 | } 100 | 101 | // returns col in [1,x] form 102 | public int findColumn(String col) throws SQLException { 103 | checkOpen(); 104 | int c = -1; 105 | for (int i=0; i < cols.length; i++) { 106 | if (col.equalsIgnoreCase(cols[i]) 107 | || (cols[i].toUpperCase().endsWith(col.toUpperCase()) && 108 | cols[i].charAt(cols[i].length() - col.length()) == '.')) { 109 | if (c == -1) 110 | c = i; 111 | else 112 | throw new SQLException("ambiguous column: '"+col+"'"); 113 | } 114 | } 115 | if (c == -1) 116 | throw new SQLException("no such column: '"+col+"'"); 117 | else 118 | return c + 1; 119 | } 120 | 121 | public boolean next() throws SQLException { 122 | if (!open) return false; // finished ResultSet 123 | lastCol = -1; 124 | 125 | // first row is loaded by execute(), so do not step() again 126 | if (row == 1) { row++; return true; } 127 | 128 | // check if we are row limited by the statement or the ResultSet 129 | if (maxRows != 0 && row > maxRows) return false; 130 | if (limitRows != 0 && row >= limitRows) return false; 131 | 132 | // do the real work 133 | switch (db.step(stmt.pointer)) { 134 | case SQLITE_DONE: 135 | close(); // agressive closing to avoid writer starvation 136 | return false; 137 | case SQLITE_ROW: row++; return true; 138 | case SQLITE_BUSY: 139 | throw new SQLException("database locked"); 140 | default: 141 | db.throwex(); return false; 142 | } 143 | } 144 | 145 | public int getType() throws SQLException { return TYPE_FORWARD_ONLY; } 146 | 147 | public int getFetchSize() throws SQLException { return limitRows; } 148 | public void setFetchSize(int rows) throws SQLException { 149 | if (0 > rows || (maxRows != 0 && rows > maxRows)) 150 | throw new SQLException("fetch size " + rows 151 | + " out of bounds " + maxRows); 152 | limitRows = rows; 153 | } 154 | 155 | public int getFetchDirection() throws SQLException { 156 | checkOpen(); return ResultSet.FETCH_FORWARD; } 157 | public void setFetchDirection(int d) throws SQLException { 158 | checkOpen(); 159 | if (d != ResultSet.FETCH_FORWARD) 160 | throw new SQLException("only FETCH_FORWARD direction supported"); 161 | } 162 | 163 | public boolean isAfterLast() throws SQLException { return !open; } 164 | public boolean isBeforeFirst() throws SQLException { 165 | return open && row == 1; } 166 | public boolean isFirst() throws SQLException { return row == 2; } 167 | public boolean isLast() throws SQLException { // FIXME 168 | throw new SQLException("function not yet implemented for SQLite"); } 169 | 170 | protected void finalize() throws SQLException { close(); } 171 | 172 | public int getRow() throws SQLException { return row; } 173 | 174 | public boolean wasNull() throws SQLException { 175 | return db.column_type(stmt.pointer, markCol(lastCol)) == SQLITE_NULL; 176 | } 177 | 178 | 179 | // DATA ACCESS FUNCTIONS //////////////////////////////////////// 180 | 181 | public boolean getBoolean(int col) throws SQLException { 182 | return getInt(col) == 0 ? false : true; } 183 | public boolean getBoolean(String col) throws SQLException { 184 | return getBoolean(findColumn(col)); } 185 | 186 | public byte getByte(int col) throws SQLException { 187 | return (byte)getInt(col); } 188 | public byte getByte(String col) throws SQLException { 189 | return getByte(findColumn(col)); } 190 | 191 | public byte[] getBytes(int col) throws SQLException { 192 | return db.column_blob(stmt.pointer, markCol(col)); } 193 | public byte[] getBytes(String col) throws SQLException { 194 | return getBytes(findColumn(col)); } 195 | 196 | public Date getDate(int col) throws SQLException { 197 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 198 | return null; 199 | return new Date(db.column_long(stmt.pointer, markCol(col))); 200 | } 201 | public Date getDate(int col, Calendar cal) throws SQLException { 202 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 203 | return null; 204 | if (cal == null) return getDate(col); 205 | cal.setTimeInMillis(db.column_long(stmt.pointer, markCol(col))); 206 | return new Date(cal.getTime().getTime()); 207 | } 208 | public Date getDate(String col) throws SQLException { 209 | return getDate(findColumn(col), Calendar.getInstance()); } 210 | public Date getDate(String col, Calendar cal) throws SQLException { 211 | return getDate(findColumn(col), cal); } 212 | 213 | public double getDouble(int col) throws SQLException { 214 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 215 | return 0; 216 | return db.column_double(stmt.pointer, markCol(col)); 217 | } 218 | public double getDouble(String col) throws SQLException { 219 | return getDouble(findColumn(col)); } 220 | 221 | public float getFloat(int col) throws SQLException { 222 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 223 | return 0; 224 | return (float)db.column_double(stmt.pointer, markCol(col)); 225 | } 226 | public float getFloat(String col) throws SQLException { 227 | return getFloat(findColumn(col)); } 228 | 229 | public int getInt(int col) throws SQLException { 230 | return db.column_int(stmt.pointer, markCol(col)); } 231 | public int getInt(String col) throws SQLException { 232 | return getInt(findColumn(col)); } 233 | 234 | public long getLong(int col) throws SQLException { 235 | return db.column_long(stmt.pointer, markCol(col)); } 236 | public long getLong(String col) throws SQLException { 237 | return getLong(findColumn(col)); } 238 | 239 | public short getShort(int col) throws SQLException { 240 | return (short)getInt(col); } 241 | public short getShort(String col) throws SQLException { 242 | return getShort(findColumn(col)); } 243 | 244 | public String getString(int col) throws SQLException { 245 | return db.column_text(stmt.pointer, markCol(col)); } 246 | public String getString(String col) throws SQLException { 247 | return getString(findColumn(col)); } 248 | 249 | public Time getTime(int col) throws SQLException { 250 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 251 | return null; 252 | return new Time(db.column_long(stmt.pointer, markCol(col))); } 253 | public Time getTime(int col, Calendar cal) throws SQLException { 254 | if (cal == null) return getTime(col); 255 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 256 | return null; 257 | cal.setTimeInMillis(db.column_long(stmt.pointer, markCol(col))); 258 | return new Time(cal.getTime().getTime()); 259 | } 260 | public Time getTime(String col) throws SQLException { 261 | return getTime(findColumn(col)); } 262 | public Time getTime(String col, Calendar cal) throws SQLException { 263 | return getTime(findColumn(col), cal); } 264 | 265 | public Timestamp getTimestamp(int col) throws SQLException { 266 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 267 | return null; 268 | return new Timestamp(db.column_long(stmt.pointer, markCol(col))); } 269 | public Timestamp getTimestamp(int col, Calendar cal) throws SQLException { 270 | if (cal == null) return getTimestamp(col); 271 | if (db.column_type(stmt.pointer, markCol(col)) == SQLITE_NULL) 272 | return null; 273 | cal.setTimeInMillis(db.column_long(stmt.pointer, markCol(col))); 274 | return new Timestamp(cal.getTime().getTime()); 275 | } 276 | public Timestamp getTimestamp(String col) throws SQLException { 277 | return getTimestamp(findColumn(col)); } 278 | public Timestamp getTimestamp(String c, Calendar ca) throws SQLException { 279 | return getTimestamp(findColumn(c), ca); } 280 | 281 | public Object getObject(int col) throws SQLException { 282 | switch (db.column_type(stmt.pointer, checkCol(col))) { 283 | case SQLITE_INTEGER: 284 | long val = getLong(col); 285 | if (val > (long)Integer.MAX_VALUE 286 | || val < (long)Integer.MIN_VALUE) 287 | return new Long(val); 288 | else 289 | return new Integer((int)val); 290 | case SQLITE_FLOAT: return new Double(getDouble(col)); 291 | case SQLITE_BLOB: return getBytes(col); 292 | case SQLITE_NULL: return null; 293 | case SQLITE_TEXT: 294 | default: 295 | return getString(col); 296 | } 297 | } 298 | public Object getObject(String col) throws SQLException { 299 | return getObject(findColumn(col)); } 300 | 301 | public Statement getStatement() { return stmt; } 302 | public String getCursorName() throws SQLException { return null; } 303 | public SQLWarning getWarnings() throws SQLException { return null; } 304 | public void clearWarnings() throws SQLException {} 305 | 306 | // ResultSetMetaData Functions ////////////////////////////////// 307 | 308 | // we do not need to check the RS is open, only that colsMeta 309 | // is not null, done with checkCol(int). 310 | 311 | public ResultSetMetaData getMetaData() throws SQLException { 312 | return this; } 313 | 314 | public String getCatalogName(int col) throws SQLException { 315 | return db.column_table_name(stmt.pointer, checkCol(col)); } 316 | public String getColumnClassName(int col) throws SQLException { 317 | checkCol(col); return "java.lang.Object"; } 318 | public int getColumnCount() throws SQLException { 319 | checkCol(1); return colsMeta.length; 320 | } 321 | public int getColumnDisplaySize(int col) throws SQLException { 322 | return Integer.MAX_VALUE; } 323 | public String getColumnLabel(int col) throws SQLException { 324 | return getColumnName(col); } 325 | public String getColumnName(int col) throws SQLException { 326 | return db.column_name(stmt.pointer, checkCol(col)); } 327 | public int getColumnType(int col) throws SQLException { 328 | switch (db.column_type(stmt.pointer, checkCol(col))) { 329 | case SQLITE_INTEGER: return Types.INTEGER; 330 | case SQLITE_FLOAT: return Types.FLOAT; 331 | case SQLITE_BLOB: return Types.BLOB; 332 | case SQLITE_NULL: return Types.NULL; 333 | case SQLITE_TEXT: 334 | default: 335 | return Types.VARCHAR; 336 | } 337 | } 338 | public String getColumnTypeName(int col) throws SQLException { 339 | switch (db.column_type(stmt.pointer, checkCol(col))) { 340 | case SQLITE_INTEGER: return "integer"; 341 | case SQLITE_FLOAT: return "float"; 342 | case SQLITE_BLOB: return "blob"; 343 | case SQLITE_NULL: return "null"; 344 | case SQLITE_TEXT: 345 | default: return "text"; 346 | } 347 | } 348 | public int getPrecision(int col) throws SQLException { return 0; } // FIXME 349 | public int getScale(int col) throws SQLException { return 0; } 350 | public String getSchemaName(int col) throws SQLException { return ""; } 351 | public String getTableName(int col) throws SQLException { 352 | return db.column_table_name(stmt.pointer, checkCol(col)); } 353 | public int isNullable(int col) throws SQLException { 354 | checkMeta(); 355 | return meta[checkCol(col)][1] ? columnNoNulls: columnNullable; 356 | } 357 | public boolean isAutoIncrement(int col) throws SQLException { 358 | checkMeta(); return meta[checkCol(col)][2]; } 359 | public boolean isCaseSensitive(int col) throws SQLException { return true; } 360 | public boolean isCurrency(int col) throws SQLException { return false; } 361 | public boolean isDefinitelyWritable(int col) throws SQLException { 362 | return true; } // FIXME: check db file constraints? 363 | public boolean isReadOnly(int col) throws SQLException { return false; } 364 | public boolean isSearchable(int col) throws SQLException { return true; } 365 | public boolean isSigned(int col) throws SQLException { return false; } 366 | public boolean isWritable(int col) throws SQLException { return true; } 367 | 368 | public int getConcurrency() throws SQLException { return CONCUR_READ_ONLY; } 369 | 370 | public boolean rowDeleted() throws SQLException { return false; } 371 | public boolean rowInserted() throws SQLException { return false; } 372 | public boolean rowUpdated() throws SQLException { return false; } 373 | } 374 | -------------------------------------------------------------------------------- /src/org/sqlite/Stmt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | package org.sqlite; 17 | 18 | import java.sql.*; 19 | import java.util.ArrayList; 20 | 21 | class Stmt extends Unused implements Statement, Codes 22 | { 23 | final Conn conn; 24 | final DB db; 25 | final RS rs; 26 | 27 | long pointer; 28 | String sql = null; 29 | 30 | int batchPos; 31 | Object[] batch = null; 32 | boolean resultsWaiting = false; 33 | 34 | Stmt(Conn c) { 35 | conn = c; 36 | db = conn.db(); 37 | rs = new RS(this); 38 | } 39 | 40 | protected final void checkOpen() throws SQLException { 41 | if (pointer == 0) throw new SQLException("statement is not executing"); 42 | } 43 | boolean isOpen() throws SQLException { 44 | return (pointer != 0); 45 | } 46 | 47 | /** Calls sqlite3_step() and sets up results. Expects a clean stmt. */ 48 | protected boolean exec() throws SQLException { 49 | if (sql == null) throw new SQLException( 50 | "SQLiteJDBC internal error: sql==null"); 51 | if (rs.isOpen()) throw new SQLException( 52 | "SQLite JDBC internal error: rs.isOpen() on exec."); 53 | 54 | boolean rc = false; 55 | try { 56 | rc = db.execute(this, null); 57 | } finally { 58 | resultsWaiting = rc; 59 | } 60 | 61 | return db.column_count(pointer) != 0; 62 | } 63 | 64 | 65 | // PUBLIC INTERFACE ///////////////////////////////////////////// 66 | 67 | public void close() throws SQLException { 68 | if (pointer == 0) return; 69 | rs.close(); 70 | batch = null; 71 | batchPos = 0; 72 | int resp = db.finalize(this); 73 | if (resp != SQLITE_OK && resp != SQLITE_MISUSE) 74 | db.throwex(); 75 | } 76 | 77 | protected void finalize() throws SQLException { close(); } 78 | 79 | public boolean execute(String sql) throws SQLException { 80 | close(); 81 | this.sql = sql; 82 | db.prepare(this); 83 | return exec(); 84 | } 85 | 86 | public ResultSet executeQuery(String sql) throws SQLException { 87 | close(); 88 | this.sql = sql; 89 | db.prepare(this); 90 | if (!exec()) { 91 | close(); 92 | throw new SQLException("query does not return ResultSet"); 93 | } 94 | return getResultSet(); 95 | } 96 | 97 | public int executeUpdate(String sql) throws SQLException { 98 | close(); 99 | this.sql = sql; 100 | int changes = 0; 101 | try { 102 | db.prepare(this); 103 | changes = db.executeUpdate(this, null); 104 | } finally { close(); } 105 | return changes; 106 | } 107 | 108 | public ResultSet getResultSet() throws SQLException { 109 | checkOpen(); 110 | if (rs.isOpen()) throw new SQLException("ResultSet already requested"); 111 | if (db.column_count(pointer) == 0) throw new SQLException( 112 | "no ResultSet available"); 113 | if (rs.colsMeta == null) 114 | rs.colsMeta = db.column_names(pointer); 115 | rs.cols = rs.colsMeta; 116 | 117 | rs.open = resultsWaiting; 118 | resultsWaiting = false; 119 | return rs; 120 | } 121 | 122 | /* 123 | * This function has a complex behaviour best understood by carefully 124 | * reading the JavaDoc for getMoreResults() and considering the test 125 | * StatementTest.execute(). 126 | */ 127 | public int getUpdateCount() throws SQLException { 128 | if (pointer != 0 129 | && !rs.isOpen() 130 | && !resultsWaiting 131 | && db.column_count(pointer) == 0) 132 | return db.changes(); 133 | return -1; 134 | } 135 | 136 | public void addBatch(String sql) throws SQLException { 137 | close(); 138 | if (batch == null || batchPos + 1 >= batch.length) { 139 | Object[] nb = new Object[Math.max(10, batchPos * 2)]; 140 | if (batch != null) 141 | System.arraycopy(batch, 0, nb, 0, batch.length); 142 | batch = nb; 143 | } 144 | batch[batchPos++] = sql; 145 | } 146 | 147 | public void clearBatch() throws SQLException { 148 | batchPos = 0; 149 | if (batch != null) 150 | for (int i=0; i < batch.length; i++) 151 | batch[i] = null; 152 | } 153 | 154 | public int[] executeBatch() throws SQLException { 155 | // TODO: optimise 156 | close(); 157 | if (batch == null || batchPos == 0) return new int[] {}; 158 | 159 | int[] changes = new int[batchPos]; 160 | 161 | synchronized (db) { try { 162 | for (int i=0; i < changes.length; i++) { 163 | try { 164 | this.sql = (String)batch[i]; 165 | db.prepare(this); 166 | changes[i] = db.executeUpdate(this, null); 167 | } catch (SQLException e) { 168 | throw new BatchUpdateException( 169 | "batch entry " + i + ": " + e.getMessage(), changes); 170 | } finally { 171 | db.finalize(this); 172 | } 173 | } 174 | } finally { 175 | clearBatch(); 176 | } } 177 | 178 | return changes; 179 | } 180 | 181 | public void setCursorName(String name) {} 182 | 183 | public SQLWarning getWarnings() throws SQLException { return null; } 184 | public void clearWarnings() throws SQLException {} 185 | 186 | public Connection getConnection() throws SQLException { 187 | return conn; } 188 | 189 | public void cancel() throws SQLException { rs.checkOpen(); db.interrupt(); } 190 | public int getQueryTimeout() throws SQLException { 191 | return conn.getTimeout(); } 192 | public void setQueryTimeout(int seconds) throws SQLException { 193 | if (seconds < 0) throw new SQLException("query timeout must be >= 0"); 194 | conn.setTimeout(1000 * seconds); 195 | } 196 | 197 | public int getMaxRows() throws SQLException { 198 | return rs.maxRows; 199 | } 200 | public void setMaxRows(int max) throws SQLException { 201 | if (max < 0) throw new SQLException("max row count must be >= 0"); 202 | rs.maxRows = max; 203 | } 204 | 205 | public int getMaxFieldSize() throws SQLException { return 0; } 206 | public void setMaxFieldSize(int max) throws SQLException { 207 | if (max < 0) throw new SQLException( 208 | "max field size "+max+" cannot be negative"); 209 | } 210 | 211 | public int getFetchSize() throws SQLException { return rs.getFetchSize(); } 212 | public void setFetchSize(int r) throws SQLException { rs.setFetchSize(r); } 213 | public int getFetchDirection() throws SQLException { 214 | return rs.getFetchDirection(); 215 | } 216 | public void setFetchDirection(int d) throws SQLException { 217 | rs.setFetchDirection(d); 218 | } 219 | 220 | /** As SQLite's last_insert_rowid() function is DB-specific not 221 | * statement specific, this function introduces a race condition 222 | * if the same connection is used by two threads and both insert. */ 223 | public ResultSet getGeneratedKeys() throws SQLException { 224 | return ((MetaData)conn.getMetaData()).getGeneratedKeys(); 225 | } 226 | 227 | /** SQLite does not support multiple results from execute(). */ 228 | public boolean getMoreResults() throws SQLException { 229 | return getMoreResults(0); 230 | } 231 | public boolean getMoreResults(int c) throws SQLException { 232 | checkOpen(); 233 | close(); // as we never have another result, clean up pointer 234 | return false; 235 | } 236 | 237 | public int getResultSetConcurrency() throws SQLException { 238 | return ResultSet.CONCUR_READ_ONLY; } 239 | public int getResultSetHoldability() throws SQLException { 240 | return ResultSet.CLOSE_CURSORS_AT_COMMIT; } 241 | public int getResultSetType() throws SQLException { 242 | return ResultSet.TYPE_FORWARD_ONLY; } 243 | 244 | public void setEscapeProcessing(boolean enable) {} 245 | } 246 | -------------------------------------------------------------------------------- /src/org/sqlite/Unused.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007 David Crawshaw 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package org.sqlite; 18 | 19 | import java.io.*; 20 | import java.math.*; 21 | import java.net.*; 22 | import java.sql.*; 23 | import java.util.Map; 24 | 25 | /** Unused JDBC functions from Statement, PreparedStatement and ResultSet. */ 26 | abstract class Unused 27 | { 28 | private SQLException unused() { 29 | return new SQLException("not implemented by SQLite JDBC driver"); 30 | } 31 | 32 | 33 | // Statement //////////////////////////////////////////////////// 34 | 35 | public boolean execute(String sql, int[] colinds) 36 | throws SQLException { throw unused(); } 37 | public boolean execute(String sql, String[] colnames) 38 | throws SQLException { throw unused(); } 39 | public int executeUpdate(String sql, int autoKeys) 40 | throws SQLException { throw unused(); } 41 | public int executeUpdate(String sql, int[] colinds) 42 | throws SQLException { throw unused(); } 43 | public int executeUpdate(String sql, String[] cols) 44 | throws SQLException { throw unused(); } 45 | public boolean execute(String sql, int autokeys) 46 | throws SQLException { throw unused(); } 47 | 48 | 49 | // PreparedStatement //////////////////////////////////////////// 50 | 51 | public void setArray(int i, Array x) 52 | throws SQLException { throw unused(); } 53 | public void setAsciiStream(int parameterIndex, InputStream x, int length) 54 | throws SQLException { throw unused(); } 55 | public void setBigDecimal(int parameterIndex, BigDecimal x) 56 | throws SQLException { throw unused(); } 57 | public void setBinaryStream(int parameterIndex, InputStream x, int length) 58 | throws SQLException { throw unused(); } 59 | public void setBlob(int i, Blob x) 60 | throws SQLException { throw unused(); } 61 | public void setCharacterStream(int pos, Reader reader, int length) 62 | throws SQLException { throw unused(); } 63 | public void setClob(int i, Clob x) 64 | throws SQLException { throw unused(); } 65 | public void setRef(int i, Ref x) 66 | throws SQLException { throw unused(); } 67 | public void setUnicodeStream(int pos, InputStream x, int length) 68 | throws SQLException { throw unused(); } 69 | public void setURL(int pos, URL x) 70 | throws SQLException { throw unused(); } 71 | 72 | 73 | // ResultSet //////////////////////////////////////////////////// 74 | 75 | public Array getArray(int i) 76 | throws SQLException { throw unused(); } 77 | public Array getArray(String col) 78 | throws SQLException { throw unused(); } 79 | public InputStream getAsciiStream(int col) 80 | throws SQLException { throw unused(); } 81 | public InputStream getAsciiStream(String col) 82 | throws SQLException { throw unused(); } 83 | public BigDecimal getBigDecimal(int col) 84 | throws SQLException { throw unused(); } 85 | public BigDecimal getBigDecimal(int col, int s) 86 | throws SQLException { throw unused(); } 87 | public BigDecimal getBigDecimal(String col) 88 | throws SQLException { throw unused(); } 89 | public BigDecimal getBigDecimal(String col, int s) 90 | throws SQLException { throw unused(); } 91 | public InputStream getBinaryStream(int col) 92 | throws SQLException { throw unused(); } 93 | public InputStream getBinaryStream(String col) 94 | throws SQLException { throw unused(); } 95 | public Blob getBlob(int col) 96 | throws SQLException { throw unused(); } 97 | public Blob getBlob(String col) 98 | throws SQLException { throw unused(); } 99 | public Reader getCharacterStream(int col) 100 | throws SQLException { throw unused(); } 101 | public Reader getCharacterStream(String col) 102 | throws SQLException { throw unused(); } 103 | public Clob getClob(int col) 104 | throws SQLException { throw unused(); } 105 | public Clob getClob(String col) 106 | throws SQLException { throw unused(); } 107 | public Object getObject(int col, Map map) 108 | throws SQLException { throw unused(); } 109 | public Object getObject(String col, Map map) 110 | throws SQLException { throw unused(); } 111 | public Ref getRef(int i) 112 | throws SQLException { throw unused(); } 113 | public Ref getRef(String col) 114 | throws SQLException { throw unused(); } 115 | 116 | public InputStream getUnicodeStream(int col) 117 | throws SQLException { throw unused(); } 118 | public InputStream getUnicodeStream(String col) 119 | throws SQLException { throw unused(); } 120 | public URL getURL(int col) 121 | throws SQLException { throw unused(); } 122 | public URL getURL(String col) 123 | throws SQLException { throw unused(); } 124 | 125 | public void insertRow() throws SQLException { 126 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 127 | public void moveToCurrentRow() throws SQLException { 128 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 129 | public void moveToInsertRow() throws SQLException { 130 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 131 | public boolean last() throws SQLException { 132 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 133 | public boolean previous() throws SQLException { 134 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 135 | public boolean relative(int rows) throws SQLException { 136 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 137 | public boolean absolute(int row) throws SQLException { 138 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 139 | public void afterLast() throws SQLException { 140 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 141 | public void beforeFirst() throws SQLException { 142 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 143 | public boolean first() throws SQLException { 144 | throw new SQLException("ResultSet is TYPE_FORWARD_ONLY"); } 145 | 146 | public void cancelRowUpdates() 147 | throws SQLException { throw unused(); } 148 | public void deleteRow() 149 | throws SQLException { throw unused(); } 150 | 151 | public void updateArray(int col, Array x) 152 | throws SQLException { throw unused(); } 153 | public void updateArray(String col, Array x) 154 | throws SQLException { throw unused(); } 155 | public void updateAsciiStream(int col, InputStream x, int l) 156 | throws SQLException { throw unused(); } 157 | public void updateAsciiStream(String col, InputStream x, int l) 158 | throws SQLException { throw unused(); } 159 | public void updateBigDecimal(int col, BigDecimal x) 160 | throws SQLException { throw unused(); } 161 | public void updateBigDecimal(String col, BigDecimal x) 162 | throws SQLException { throw unused(); } 163 | public void updateBinaryStream(int c, InputStream x, int l) 164 | throws SQLException { throw unused(); } 165 | public void updateBinaryStream(String c, InputStream x, int l) 166 | throws SQLException { throw unused(); } 167 | public void updateBlob(int col, Blob x) 168 | throws SQLException { throw unused(); } 169 | public void updateBlob(String col, Blob x) 170 | throws SQLException { throw unused(); } 171 | public void updateBoolean(int col, boolean x) 172 | throws SQLException { throw unused(); } 173 | public void updateBoolean(String col, boolean x) 174 | throws SQLException { throw unused(); } 175 | public void updateByte(int col, byte x) 176 | throws SQLException { throw unused(); } 177 | public void updateByte(String col, byte x) 178 | throws SQLException { throw unused(); } 179 | public void updateBytes(int col, byte[] x) 180 | throws SQLException { throw unused(); } 181 | public void updateBytes(String col, byte[] x) 182 | throws SQLException { throw unused(); } 183 | public void updateCharacterStream(int c, Reader x, int l) 184 | throws SQLException { throw unused(); } 185 | public void updateCharacterStream(String c, Reader r, int l) 186 | throws SQLException { throw unused(); } 187 | public void updateClob(int col, Clob x) 188 | throws SQLException { throw unused(); } 189 | public void updateClob(String col, Clob x) 190 | throws SQLException { throw unused(); } 191 | public void updateDate(int col, Date x) 192 | throws SQLException { throw unused(); } 193 | public void updateDate(String col, Date x) 194 | throws SQLException { throw unused(); } 195 | public void updateDouble(int col, double x) 196 | throws SQLException { throw unused(); } 197 | public void updateDouble(String col, double x) 198 | throws SQLException { throw unused(); } 199 | public void updateFloat(int col, float x) 200 | throws SQLException { throw unused(); } 201 | public void updateFloat(String col, float x) 202 | throws SQLException { throw unused(); } 203 | public void updateInt(int col, int x) 204 | throws SQLException { throw unused(); } 205 | public void updateInt(String col, int x) 206 | throws SQLException { throw unused(); } 207 | public void updateLong(int col, long x) 208 | throws SQLException { throw unused(); } 209 | public void updateLong(String col, long x) 210 | throws SQLException { throw unused(); } 211 | public void updateNull(int col) 212 | throws SQLException { throw unused(); } 213 | public void updateNull(String col) 214 | throws SQLException { throw unused(); } 215 | public void updateObject(int c, Object x) 216 | throws SQLException { throw unused(); } 217 | public void updateObject(int c, Object x, int s) 218 | throws SQLException { throw unused(); } 219 | public void updateObject(String col, Object x) 220 | throws SQLException { throw unused(); } 221 | public void updateObject(String c, Object x, int s) 222 | throws SQLException { throw unused(); } 223 | public void updateRef(int col, Ref x) 224 | throws SQLException { throw unused(); } 225 | public void updateRef(String c, Ref x) 226 | throws SQLException { throw unused(); } 227 | public void updateRow() 228 | throws SQLException { throw unused(); } 229 | public void updateShort(int c, short x) 230 | throws SQLException { throw unused(); } 231 | public void updateShort(String c, short x) 232 | throws SQLException { throw unused(); } 233 | public void updateString(int c, String x) 234 | throws SQLException { throw unused(); } 235 | public void updateString(String c, String x) 236 | throws SQLException { throw unused(); } 237 | public void updateTime(int c, Time x) 238 | throws SQLException { throw unused(); } 239 | public void updateTime(String c, Time x) 240 | throws SQLException { throw unused(); } 241 | public void updateTimestamp(int c, Timestamp x) 242 | throws SQLException { throw unused(); } 243 | public void updateTimestamp(String c, Timestamp x) 244 | throws SQLException { throw unused(); } 245 | 246 | public void refreshRow() 247 | throws SQLException { throw unused(); } 248 | } 249 | -------------------------------------------------------------------------------- /src/test/ConnectionTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.io.File; 4 | import java.sql.*; 5 | import org.junit.*; 6 | import static org.junit.Assert.*; 7 | 8 | /** These tests check whether access to files is woring correctly and 9 | * some Connection.close() cases. */ 10 | public class ConnectionTest 11 | { 12 | @BeforeClass public static void forName() throws Exception { 13 | Class.forName("org.sqlite.JDBC"); 14 | } 15 | 16 | @Test public void openMemory() throws SQLException { 17 | Connection conn = DriverManager.getConnection("jdbc:sqlite:"); 18 | conn.close(); 19 | } 20 | 21 | @Test public void isClosed() throws SQLException { 22 | Connection conn = DriverManager.getConnection("jdbc:sqlite:"); 23 | assertFalse(conn.isReadOnly()); 24 | conn.close(); 25 | assertTrue(conn.isClosed()); 26 | } 27 | 28 | @Test public void openFile() throws SQLException { 29 | File testdb = new File("test.db"); 30 | if (testdb.exists()) testdb.delete(); 31 | 32 | assertFalse(testdb.exists()); 33 | Connection conn = DriverManager.getConnection("jdbc:sqlite:test.db"); 34 | assertFalse(conn.isReadOnly()); 35 | conn.close(); 36 | 37 | assertTrue(testdb.exists()); 38 | conn = DriverManager.getConnection("jdbc:sqlite:test.db"); 39 | assertFalse(conn.isReadOnly()); 40 | conn.close(); 41 | 42 | assertTrue(testdb.exists()); 43 | testdb.delete(); 44 | } 45 | 46 | @Test(expected= SQLException.class) 47 | public void closeTest() throws SQLException { 48 | Connection conn = DriverManager.getConnection("jdbc:sqlite:"); 49 | PreparedStatement prep = conn.prepareStatement("select null;"); 50 | ResultSet rs = prep.executeQuery(); 51 | conn.close(); 52 | prep.clearParameters(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/DBMetaDataTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.sql.*; 4 | import org.junit.*; 5 | import static org.junit.Assert.*; 6 | 7 | /** These tests are designed to stress Statements on memory databases. */ 8 | public class DBMetaDataTest 9 | { 10 | private Connection conn; 11 | private Statement stat; 12 | private DatabaseMetaData meta; 13 | 14 | @BeforeClass public static void forName() throws Exception { 15 | Class.forName("org.sqlite.JDBC"); 16 | } 17 | 18 | @Before public void connect() throws Exception { 19 | conn = DriverManager.getConnection("jdbc:sqlite:"); 20 | stat = conn.createStatement(); 21 | stat.executeUpdate( 22 | "create table test (id integer primary key, fn, sn);"); 23 | stat.executeUpdate("create view testView as select * from test;"); 24 | meta = conn.getMetaData(); 25 | } 26 | 27 | @After public void close() throws SQLException { 28 | meta = null; 29 | stat.close(); 30 | conn.close(); 31 | } 32 | 33 | @Test public void getTables() throws SQLException { 34 | ResultSet rs = meta.getTables(null, null, null, null); 35 | assertNotNull(rs); 36 | assertTrue(rs.next()); 37 | assertEquals(rs.getString("TABLE_NAME"), "TEST"); // 3 38 | assertEquals(rs.getString("TABLE_TYPE"), "TABLE"); // 4 39 | assertTrue(rs.next()); 40 | assertEquals(rs.getString("TABLE_NAME"), "TESTVIEW"); 41 | assertEquals(rs.getString("TABLE_TYPE"), "VIEW"); 42 | rs.close(); 43 | 44 | rs = meta.getTables(null, null, "bob", null); 45 | assertFalse(rs.next()); 46 | rs.close(); 47 | rs = meta.getTables(null, null, "test", null); 48 | assertTrue(rs.next()); 49 | assertFalse(rs.next()); 50 | rs.close(); 51 | rs = meta.getTables(null, null, "test%", null); 52 | assertTrue(rs.next()); 53 | assertTrue(rs.next()); 54 | rs.close(); 55 | 56 | rs = meta.getTables(null, null, null, new String[] { "table" }); 57 | assertTrue(rs.next()); 58 | assertEquals(rs.getString("TABLE_NAME"), "TEST"); 59 | assertFalse(rs.next()); 60 | rs.close(); 61 | 62 | rs = meta.getTables(null, null, null, new String[] { "view" }); 63 | assertTrue(rs.next()); 64 | assertEquals(rs.getString("TABLE_NAME"), "TESTVIEW"); 65 | assertFalse(rs.next()); 66 | rs.close(); 67 | } 68 | 69 | @Test public void getTableTypes() throws SQLException { 70 | ResultSet rs = meta.getTableTypes(); 71 | assertNotNull(rs); 72 | assertTrue(rs.next()); 73 | assertEquals(rs.getString("TABLE_TYPE"), "TABLE"); 74 | assertTrue(rs.next()); 75 | assertEquals(rs.getString("TABLE_TYPE"), "VIEW"); 76 | assertFalse(rs.next()); 77 | } 78 | 79 | @Test public void getTypeInfo() throws SQLException { 80 | ResultSet rs = meta.getTypeInfo(); 81 | assertNotNull(rs); 82 | assertTrue(rs.next()); 83 | assertEquals(rs.getString("TYPE_NAME"), "BLOB"); 84 | assertTrue(rs.next()); 85 | assertEquals(rs.getString("TYPE_NAME"), "INTEGER"); 86 | assertTrue(rs.next()); 87 | assertEquals(rs.getString("TYPE_NAME"), "NULL"); 88 | assertTrue(rs.next()); 89 | assertEquals(rs.getString("TYPE_NAME"), "REAL"); 90 | assertTrue(rs.next()); 91 | assertEquals(rs.getString("TYPE_NAME"), "TEXT"); 92 | assertFalse(rs.next()); 93 | } 94 | 95 | @Test public void getColumns() throws SQLException { 96 | ResultSet rs = meta.getColumns(null, null, "test", "id"); 97 | assertTrue(rs.next()); 98 | assertEquals(rs.getString("TABLE_NAME"), "test"); 99 | assertEquals(rs.getString("COLUMN_NAME"), "id"); 100 | assertFalse(rs.next()); 101 | 102 | rs = meta.getColumns(null, null, "test", "fn"); 103 | assertTrue(rs.next()); 104 | assertEquals(rs.getString("COLUMN_NAME"), "fn"); 105 | assertFalse(rs.next()); 106 | 107 | rs = meta.getColumns(null, null, "test", "sn"); 108 | assertTrue(rs.next()); 109 | assertEquals(rs.getString("COLUMN_NAME"), "sn"); 110 | assertFalse(rs.next()); 111 | 112 | rs = meta.getColumns(null, null, "test", "%"); 113 | assertTrue(rs.next()); 114 | assertEquals(rs.getString("COLUMN_NAME"), "id"); 115 | assertTrue(rs.next()); 116 | assertEquals(rs.getString("COLUMN_NAME"), "fn"); 117 | assertTrue(rs.next()); 118 | assertEquals(rs.getString("COLUMN_NAME"), "sn"); 119 | assertFalse(rs.next()); 120 | 121 | rs = meta.getColumns(null, null, "test", "%n"); 122 | assertTrue(rs.next()); 123 | assertEquals(rs.getString("COLUMN_NAME"), "fn"); 124 | assertTrue(rs.next()); 125 | assertEquals(rs.getString("COLUMN_NAME"), "sn"); 126 | assertFalse(rs.next()); 127 | 128 | rs = meta.getColumns(null, null, "test%", "%"); 129 | assertTrue(rs.next()); 130 | assertEquals(rs.getString("COLUMN_NAME"), "id"); 131 | assertTrue(rs.next()); 132 | assertEquals(rs.getString("COLUMN_NAME"), "fn"); 133 | assertTrue(rs.next()); 134 | assertEquals(rs.getString("COLUMN_NAME"), "sn"); 135 | assertFalse(rs.next()); 136 | 137 | rs = meta.getColumns(null, null, "%", "%"); 138 | assertTrue(rs.next()); 139 | assertEquals(rs.getString("TABLE_NAME"), "test"); 140 | assertEquals(rs.getString("COLUMN_NAME"), "id"); 141 | assertTrue(rs.next()); 142 | assertEquals(rs.getString("COLUMN_NAME"), "fn"); 143 | assertTrue(rs.next()); 144 | assertEquals(rs.getString("COLUMN_NAME"), "sn"); 145 | assertFalse(rs.next()); 146 | 147 | rs = meta.getColumns(null, null, "doesnotexist", "%"); 148 | assertFalse(rs.next()); 149 | } 150 | 151 | @Test public void columnOrderOfgetTables() throws SQLException { 152 | ResultSet rs = meta.getTables(null, null, null, null); 153 | assertTrue(rs.next()); 154 | ResultSetMetaData rsmeta = rs.getMetaData(); 155 | assertEquals(rsmeta.getColumnCount(), 10); 156 | assertEquals(rsmeta.getColumnName(1), "TABLE_CAT"); 157 | assertEquals(rsmeta.getColumnName(2), "TABLE_SCHEM"); 158 | assertEquals(rsmeta.getColumnName(3), "TABLE_NAME"); 159 | assertEquals(rsmeta.getColumnName(4), "TABLE_TYPE"); 160 | assertEquals(rsmeta.getColumnName(5), "REMARKS"); 161 | assertEquals(rsmeta.getColumnName(6), "TYPE_CAT"); 162 | assertEquals(rsmeta.getColumnName(7), "TYPE_SCHEM"); 163 | assertEquals(rsmeta.getColumnName(8), "TYPE_NAME"); 164 | assertEquals(rsmeta.getColumnName(9), "SELF_REFERENCING_COL_NAME"); 165 | assertEquals(rsmeta.getColumnName(10), "REF_GENERATION"); 166 | } 167 | 168 | @Test public void columnOrderOfgetTableTypes() throws SQLException { 169 | ResultSet rs = meta.getTableTypes(); 170 | assertTrue(rs.next()); 171 | ResultSetMetaData rsmeta = rs.getMetaData(); 172 | assertEquals(rsmeta.getColumnCount(), 1); 173 | assertEquals(rsmeta.getColumnName(1), "TABLE_TYPE"); 174 | } 175 | 176 | @Test public void columnOrderOfgetTypeInfo() throws SQLException { 177 | ResultSet rs = meta.getTypeInfo(); 178 | assertTrue(rs.next()); 179 | ResultSetMetaData rsmeta = rs.getMetaData(); 180 | assertEquals(rsmeta.getColumnCount(), 18); 181 | assertEquals(rsmeta.getColumnName(1), "TYPE_NAME"); 182 | assertEquals(rsmeta.getColumnName(2), "DATA_TYPE"); 183 | assertEquals(rsmeta.getColumnName(3), "PRECISION"); 184 | assertEquals(rsmeta.getColumnName(4), "LITERAL_PREFIX"); 185 | assertEquals(rsmeta.getColumnName(5), "LITERAL_SUFFIX"); 186 | assertEquals(rsmeta.getColumnName(6), "CREATE_PARAMS"); 187 | assertEquals(rsmeta.getColumnName(7), "NULLABLE"); 188 | assertEquals(rsmeta.getColumnName(8), "CASE_SENSITIVE"); 189 | assertEquals(rsmeta.getColumnName(9), "SEARCHABLE"); 190 | assertEquals(rsmeta.getColumnName(10), "UNSIGNED_ATTRIBUTE"); 191 | assertEquals(rsmeta.getColumnName(11), "FIXED_PREC_SCALE"); 192 | assertEquals(rsmeta.getColumnName(12), "AUTO_INCREMENT"); 193 | assertEquals(rsmeta.getColumnName(13), "LOCAL_TYPE_NAME"); 194 | assertEquals(rsmeta.getColumnName(14), "MINIMUM_SCALE"); 195 | assertEquals(rsmeta.getColumnName(15), "MAXIMUM_SCALE"); 196 | assertEquals(rsmeta.getColumnName(16), "SQL_DATA_TYPE"); 197 | assertEquals(rsmeta.getColumnName(17), "SQL_DATETIME_SUB"); 198 | assertEquals(rsmeta.getColumnName(18), "NUM_PREC_RADIX"); 199 | } 200 | 201 | @Test public void columnOrderOfgetColumns() throws SQLException { 202 | ResultSet rs = meta.getColumns(null, null, "test", null); 203 | assertTrue(rs.next()); 204 | ResultSetMetaData rsmeta = rs.getMetaData(); 205 | assertEquals(rsmeta.getColumnCount(), 22); 206 | assertEquals(rsmeta.getColumnName(1), "TABLE_CAT"); 207 | assertEquals(rsmeta.getColumnName(2), "TABLE_SCHEM"); 208 | assertEquals(rsmeta.getColumnName(3), "TABLE_NAME"); 209 | assertEquals(rsmeta.getColumnName(4), "COLUMN_NAME"); 210 | assertEquals(rsmeta.getColumnName(5), "DATA_TYPE"); 211 | assertEquals(rsmeta.getColumnName(6), "TYPE_NAME"); 212 | assertEquals(rsmeta.getColumnName(7), "COLUMN_SIZE"); 213 | assertEquals(rsmeta.getColumnName(8), "BUFFER_LENGTH"); 214 | assertEquals(rsmeta.getColumnName(9), "DECIMAL_DIGITS"); 215 | assertEquals(rsmeta.getColumnName(10), "NUM_PREC_RADIX"); 216 | assertEquals(rsmeta.getColumnName(11), "NULLABLE"); 217 | assertEquals(rsmeta.getColumnName(12), "REMARKS"); 218 | assertEquals(rsmeta.getColumnName(13), "COLUMN_DEF"); 219 | assertEquals(rsmeta.getColumnName(14), "SQL_DATA_TYPE"); 220 | assertEquals(rsmeta.getColumnName(15), "SQL_DATETIME_SUB"); 221 | assertEquals(rsmeta.getColumnName(16), "CHAR_OCTET_LENGTH"); 222 | assertEquals(rsmeta.getColumnName(17), "ORDINAL_POSITION"); 223 | assertEquals(rsmeta.getColumnName(18), "IS_NULLABLE"); 224 | // should be SCOPE_CATALOG, but misspelt in the standard 225 | assertEquals(rsmeta.getColumnName(19), "SCOPE_CATLOG"); 226 | assertEquals(rsmeta.getColumnName(20), "SCOPE_SCHEMA"); 227 | assertEquals(rsmeta.getColumnName(21), "SCOPE_TABLE"); 228 | assertEquals(rsmeta.getColumnName(22), "SOURCE_DATA_TYPE"); 229 | } 230 | 231 | // the following functions always return an empty resultset, so 232 | // do not bother testing their parameters, only the column types 233 | 234 | @Test public void columnOrderOfgetProcedures() throws SQLException { 235 | ResultSet rs = meta.getProcedures(null, null, null); 236 | assertFalse(rs.next()); 237 | ResultSetMetaData rsmeta = rs.getMetaData(); 238 | assertEquals(rsmeta.getColumnCount(), 8); 239 | assertEquals(rsmeta.getColumnName(1), "PROCEDURE_CAT"); 240 | assertEquals(rsmeta.getColumnName(2), "PROCEDURE_SCHEM"); 241 | assertEquals(rsmeta.getColumnName(3), "PROCEDURE_NAME"); 242 | // currently (Java 1.5), cols 4,5,6 are undefined 243 | assertEquals(rsmeta.getColumnName(7), "REMARKS"); 244 | assertEquals(rsmeta.getColumnName(8), "PROCEDURE_TYPE"); 245 | } 246 | 247 | @Test public void columnOrderOfgetProcedurColumns() throws SQLException { 248 | ResultSet rs = meta.getProcedureColumns(null, null, null, null); 249 | assertFalse(rs.next()); 250 | ResultSetMetaData rsmeta = rs.getMetaData(); 251 | assertEquals(rsmeta.getColumnCount(), 13); 252 | assertEquals(rsmeta.getColumnName(1), "PROCEDURE_CAT"); 253 | assertEquals(rsmeta.getColumnName(2), "PROCEDURE_SCHEM"); 254 | assertEquals(rsmeta.getColumnName(3), "PROCEDURE_NAME"); 255 | assertEquals(rsmeta.getColumnName(4), "COLUMN_NAME"); 256 | assertEquals(rsmeta.getColumnName(5), "COLUMN_TYPE"); 257 | assertEquals(rsmeta.getColumnName(6), "DATA_TYPE"); 258 | assertEquals(rsmeta.getColumnName(7), "TYPE_NAME"); 259 | assertEquals(rsmeta.getColumnName(8), "PRECISION"); 260 | assertEquals(rsmeta.getColumnName(9), "LENGTH"); 261 | assertEquals(rsmeta.getColumnName(10), "SCALE"); 262 | assertEquals(rsmeta.getColumnName(11), "RADIX"); 263 | assertEquals(rsmeta.getColumnName(12), "NULLABLE"); 264 | assertEquals(rsmeta.getColumnName(13), "REMARKS"); 265 | } 266 | 267 | @Test public void columnOrderOfgetSchemas() throws SQLException { 268 | ResultSet rs = meta.getSchemas(); 269 | assertFalse(rs.next()); 270 | ResultSetMetaData rsmeta = rs.getMetaData(); 271 | assertEquals(rsmeta.getColumnCount(), 2); 272 | assertEquals(rsmeta.getColumnName(1), "TABLE_SCHEM"); 273 | assertEquals(rsmeta.getColumnName(2), "TABLE_CATALOG"); 274 | } 275 | 276 | @Test public void columnOrderOfgetCatalogs() throws SQLException { 277 | ResultSet rs = meta.getCatalogs(); 278 | assertFalse(rs.next()); 279 | ResultSetMetaData rsmeta = rs.getMetaData(); 280 | assertEquals(rsmeta.getColumnCount(), 1); 281 | assertEquals(rsmeta.getColumnName(1), "TABLE_CAT"); 282 | } 283 | 284 | @Test public void columnOrderOfgetColumnPrivileges() throws SQLException { 285 | ResultSet rs = meta.getColumnPrivileges(null, null, null, null); 286 | assertFalse(rs.next()); 287 | ResultSetMetaData rsmeta = rs.getMetaData(); 288 | assertEquals(rsmeta.getColumnCount(), 8); 289 | assertEquals(rsmeta.getColumnName(1), "TABLE_CAT"); 290 | assertEquals(rsmeta.getColumnName(2), "TABLE_SCHEM"); 291 | assertEquals(rsmeta.getColumnName(3), "TABLE_NAME"); 292 | assertEquals(rsmeta.getColumnName(4), "COLUMN_NAME"); 293 | assertEquals(rsmeta.getColumnName(5), "GRANTOR"); 294 | assertEquals(rsmeta.getColumnName(6), "GRANTEE"); 295 | assertEquals(rsmeta.getColumnName(7), "PRIVILEGE"); 296 | assertEquals(rsmeta.getColumnName(8), "IS_GRANTABLE"); 297 | } 298 | 299 | @Test public void columnOrderOfgetTablePrivileges() throws SQLException { 300 | ResultSet rs = meta.getTablePrivileges(null, null, null); 301 | assertFalse(rs.next()); 302 | ResultSetMetaData rsmeta = rs.getMetaData(); 303 | assertEquals(rsmeta.getColumnCount(), 7); 304 | assertEquals(rsmeta.getColumnName(1), "TABLE_CAT"); 305 | assertEquals(rsmeta.getColumnName(2), "TABLE_SCHEM"); 306 | assertEquals(rsmeta.getColumnName(3), "TABLE_NAME"); 307 | assertEquals(rsmeta.getColumnName(4), "GRANTOR"); 308 | assertEquals(rsmeta.getColumnName(5), "GRANTEE"); 309 | assertEquals(rsmeta.getColumnName(6), "PRIVILEGE"); 310 | assertEquals(rsmeta.getColumnName(7), "IS_GRANTABLE"); 311 | } 312 | 313 | @Test public void columnOrderOfgetBestRowIdentifier() throws SQLException { 314 | ResultSet rs = meta.getBestRowIdentifier(null, null, null, 0, false); 315 | assertFalse(rs.next()); 316 | ResultSetMetaData rsmeta = rs.getMetaData(); 317 | assertEquals(rsmeta.getColumnCount(), 8); 318 | assertEquals(rsmeta.getColumnName(1), "SCOPE"); 319 | assertEquals(rsmeta.getColumnName(2), "COLUMN_NAME"); 320 | assertEquals(rsmeta.getColumnName(3), "DATA_TYPE"); 321 | assertEquals(rsmeta.getColumnName(4), "TYPE_NAME"); 322 | assertEquals(rsmeta.getColumnName(5), "COLUMN_SIZE"); 323 | assertEquals(rsmeta.getColumnName(6), "BUFFER_LENGTH"); 324 | assertEquals(rsmeta.getColumnName(7), "DECIMAL_DIGITS"); 325 | assertEquals(rsmeta.getColumnName(8), "PSEUDO_COLUMN"); 326 | } 327 | 328 | @Test public void columnOrderOfgetVersionColumns() throws SQLException { 329 | ResultSet rs = meta.getVersionColumns(null, null, null); 330 | assertFalse(rs.next()); 331 | ResultSetMetaData rsmeta = rs.getMetaData(); 332 | assertEquals(rsmeta.getColumnCount(), 8); 333 | assertEquals(rsmeta.getColumnName(1), "SCOPE"); 334 | assertEquals(rsmeta.getColumnName(2), "COLUMN_NAME"); 335 | assertEquals(rsmeta.getColumnName(3), "DATA_TYPE"); 336 | assertEquals(rsmeta.getColumnName(4), "TYPE_NAME"); 337 | assertEquals(rsmeta.getColumnName(5), "COLUMN_SIZE"); 338 | assertEquals(rsmeta.getColumnName(6), "BUFFER_LENGTH"); 339 | assertEquals(rsmeta.getColumnName(7), "DECIMAL_DIGITS"); 340 | assertEquals(rsmeta.getColumnName(8), "PSEUDO_COLUMN"); 341 | } 342 | 343 | @Test public void columnOrderOfgetPrimaryKeys() throws SQLException { 344 | ResultSet rs; 345 | ResultSetMetaData rsmeta; 346 | 347 | stat.executeUpdate("create table nopk (c1, c2, c3, c4);"); 348 | stat.executeUpdate("create table pk1 (col1 primary key, col2, col3);"); 349 | stat.executeUpdate("create table pk2 (col1, col2 primary key, col3);"); 350 | stat.executeUpdate("create table pk3 (col1, col2, col3, col4, " 351 | + "primary key (col2, col3));"); 352 | 353 | rs = meta.getPrimaryKeys(null, null, "nopk"); 354 | assertFalse(rs.next()); 355 | rsmeta = rs.getMetaData(); 356 | assertEquals(rsmeta.getColumnCount(), 6); 357 | assertEquals(rsmeta.getColumnName(1), "TABLE_CAT"); 358 | assertEquals(rsmeta.getColumnName(2), "TABLE_SCHEM"); 359 | assertEquals(rsmeta.getColumnName(3), "TABLE_NAME"); 360 | assertEquals(rsmeta.getColumnName(4), "COLUMN_NAME"); 361 | assertEquals(rsmeta.getColumnName(5), "KEY_SEQ"); 362 | assertEquals(rsmeta.getColumnName(6), "PK_NAME"); 363 | rs.close(); 364 | 365 | rs = meta.getPrimaryKeys(null, null, "pk1"); 366 | assertTrue(rs.next()); 367 | assertEquals(rs.getString("COLUMN_NAME"), "col1"); 368 | assertFalse(rs.next()); 369 | rs.close(); 370 | 371 | rs = meta.getPrimaryKeys(null, null, "pk2"); 372 | assertTrue(rs.next()); 373 | assertEquals(rs.getString("COLUMN_NAME"), "col2"); 374 | assertFalse(rs.next()); 375 | rs.close(); 376 | 377 | rs = meta.getPrimaryKeys(null, null, "pk3"); 378 | assertTrue(rs.next()); 379 | assertEquals(rs.getString("COLUMN_NAME"), "col2"); 380 | assertTrue(rs.next()); 381 | assertEquals(rs.getString("COLUMN_NAME"), "col3"); 382 | assertFalse(rs.next()); 383 | rs.close(); 384 | } 385 | 386 | /* TODO 387 | @Test public void columnOrderOfgetImportedKeys() throws SQLException { 388 | @Test public void columnOrderOfgetExportedKeys() throws SQLException { 389 | @Test public void columnOrderOfgetCrossReference() throws SQLException { 390 | @Test public void columnOrderOfgetTypeInfo() throws SQLException { 391 | @Test public void columnOrderOfgetIndexInfo() throws SQLException { 392 | @Test public void columnOrderOfgetSuperTypes() throws SQLException { 393 | @Test public void columnOrderOfgetSuperTables() throws SQLException { 394 | @Test public void columnOrderOfgetAttributes() throws SQLException {*/ 395 | 396 | @Test public void columnOrderOfgetUDTs() throws SQLException { 397 | ResultSet rs = meta.getUDTs(null, null, null, null); 398 | assertFalse(rs.next()); 399 | ResultSetMetaData rsmeta = rs.getMetaData(); 400 | assertEquals(rsmeta.getColumnCount(), 7); 401 | assertEquals(rsmeta.getColumnName(1), "TYPE_CAT"); 402 | assertEquals(rsmeta.getColumnName(2), "TYPE_SCHEM"); 403 | assertEquals(rsmeta.getColumnName(3), "TYPE_NAME"); 404 | assertEquals(rsmeta.getColumnName(4), "CLASS_NAME"); 405 | assertEquals(rsmeta.getColumnName(5), "DATA_TYPE"); 406 | assertEquals(rsmeta.getColumnName(6), "REMARKS"); 407 | assertEquals(rsmeta.getColumnName(7), "BASE_TYPE"); 408 | } 409 | 410 | @Test public void version() throws SQLException { 411 | assertNotNull(meta.getDatabaseProductVersion()); 412 | assertTrue( 413 | "pure".equals(meta.getDriverVersion()) || 414 | "native".equals(meta.getDriverVersion()) 415 | ); 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /src/test/PrepStmtTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.sql.*; 4 | import java.util.StringTokenizer; 5 | import org.junit.*; 6 | import static org.junit.Assert.*; 7 | 8 | /** These tests are designed to stress PreparedStatements on memory dbs. */ 9 | public class PrepStmtTest 10 | { 11 | static byte[] b1 = new byte[] { 1,2,7,4,2,6,2,8,5,2,3,1,5,3,6,3,3,6,2,5 }; 12 | static byte[] b2 = "To be or not to be.".getBytes(); 13 | static byte[] b3 = "Question!#$%".getBytes(); 14 | static String utf01 = "\uD840\uDC40"; 15 | static String utf02 = "\uD840\uDC47 "; 16 | static String utf03 = " \uD840\uDC43"; 17 | static String utf04 = " \uD840\uDC42 "; 18 | static String utf05 = "\uD840\uDC40\uD840\uDC44"; 19 | static String utf06 = "Hello World, \uD840\uDC40 \uD880\uDC99"; 20 | static String utf07 = "\uD840\uDC41 testing \uD880\uDC99"; 21 | static String utf08 = "\uD840\uDC40\uD840\uDC44 testing"; 22 | 23 | private Connection conn; 24 | private Statement stat; 25 | 26 | @BeforeClass public static void forName() throws Exception { 27 | Class.forName("org.sqlite.JDBC"); 28 | } 29 | 30 | @Before public void connect() throws Exception { 31 | conn = DriverManager.getConnection("jdbc:sqlite:"); 32 | stat = conn.createStatement(); 33 | } 34 | 35 | @After public void close() throws SQLException { 36 | stat.close(); 37 | conn.close(); 38 | } 39 | 40 | @Test public void update() throws SQLException { 41 | assertEquals(conn.prepareStatement( 42 | "create table s1 (c1);").executeUpdate(), 0); 43 | PreparedStatement prep = conn.prepareStatement( 44 | "insert into s1 values (?);"); 45 | prep.setInt(1, 3); assertEquals(prep.executeUpdate(), 1); 46 | prep.setInt(1, 5); assertEquals(prep.executeUpdate(), 1); 47 | prep.setInt(1, 7); assertEquals(prep.executeUpdate(), 1); 48 | prep.close(); 49 | 50 | // check results with normal statement 51 | ResultSet rs = stat.executeQuery("select sum(c1) from s1;"); 52 | assertTrue(rs.next()); 53 | assertEquals(rs.getInt(1), 15); 54 | rs.close(); 55 | } 56 | 57 | @Test public void multiUpdate() throws SQLException { 58 | stat.executeUpdate("create table test (c1);"); 59 | PreparedStatement prep = conn.prepareStatement( 60 | "insert into test values (?);"); 61 | 62 | for (int i=0; i < 10; i++) { 63 | prep.setInt(1, i); 64 | prep.executeUpdate(); 65 | prep.execute(); 66 | } 67 | 68 | prep.close(); 69 | stat.executeUpdate("drop table test;"); 70 | } 71 | 72 | @Test public void emptyRS() throws SQLException { 73 | PreparedStatement prep = conn.prepareStatement("select null limit 0;"); 74 | ResultSet rs = prep.executeQuery(); 75 | assertFalse(rs.next()); 76 | rs.close(); 77 | prep.close(); 78 | } 79 | 80 | @Test public void singleRowRS() throws SQLException { 81 | PreparedStatement prep = conn.prepareStatement("select ?;"); 82 | prep.setInt(1, Integer.MAX_VALUE); 83 | ResultSet rs = prep.executeQuery(); 84 | assertTrue(rs.next()); 85 | assertEquals(rs.getInt(1), Integer.MAX_VALUE); 86 | assertEquals(rs.getString(1), Integer.toString(Integer.MAX_VALUE)); 87 | assertEquals(rs.getDouble(1), 88 | new Integer(Integer.MAX_VALUE).doubleValue()); 89 | assertFalse(rs.next()); 90 | rs.close(); 91 | prep.close(); 92 | } 93 | 94 | @Test public void twoRowRS() throws SQLException { 95 | PreparedStatement prep = conn.prepareStatement( 96 | "select ? union all select ?;"); 97 | prep.setDouble(1, Double.MAX_VALUE); 98 | prep.setDouble(2, Double.MIN_VALUE); 99 | ResultSet rs = prep.executeQuery(); 100 | assertTrue(rs.next()); 101 | assertEquals(rs.getDouble(1), Double.MAX_VALUE); 102 | assertTrue(rs.next()); 103 | assertEquals(rs.getDouble(1), Double.MIN_VALUE); 104 | assertFalse(rs.next()); 105 | rs.close(); 106 | } 107 | 108 | @Test public void stringRS() throws SQLException { 109 | String name = "Gandhi"; 110 | PreparedStatement prep = conn.prepareStatement("select ?;"); 111 | prep.setString(1, name); 112 | ResultSet rs = prep.executeQuery(); 113 | assertTrue(rs.next()); 114 | assertEquals(rs.getString(1), name); 115 | assertFalse(rs.next()); 116 | rs.close(); 117 | } 118 | 119 | @Test public void finalizePrep() throws SQLException { 120 | conn.prepareStatement("select null;"); 121 | System.gc(); 122 | } 123 | 124 | @Test public void set() throws SQLException { 125 | ResultSet rs; 126 | PreparedStatement prep = conn.prepareStatement("select ?, ?, ?;"); 127 | 128 | // integers 129 | prep.setInt(1, Integer.MIN_VALUE); 130 | prep.setInt(2, Integer.MAX_VALUE); 131 | prep.setInt(3, 0); 132 | rs = prep.executeQuery(); 133 | assertTrue(rs.next()); 134 | assertEquals(rs.getInt(1), Integer.MIN_VALUE); 135 | assertEquals(rs.getInt(2), Integer.MAX_VALUE); 136 | assertEquals(rs.getInt(3), 0); 137 | 138 | // strings 139 | String name = "Winston Leonard Churchill"; 140 | String fn = name.substring(0, 7), 141 | mn = name.substring(8, 15), 142 | sn = name.substring(16, 25); 143 | prep.clearParameters(); 144 | prep.setString(1, fn); 145 | prep.setString(2, mn); 146 | prep.setString(3, sn); 147 | prep.executeQuery(); 148 | assertTrue(rs.next()); 149 | assertEquals(rs.getString(1), fn); 150 | assertEquals(rs.getString(2), mn); 151 | assertEquals(rs.getString(3), sn); 152 | 153 | // mixed 154 | prep.setString(1, name); 155 | prep.setString(2, null); 156 | prep.setLong(3, Long.MAX_VALUE); 157 | prep.executeQuery(); 158 | assertTrue(rs.next()); 159 | assertEquals(rs.getString(1), name); 160 | assertNull(rs.getString(2)); 161 | assertTrue(rs.wasNull()); 162 | assertEquals(rs.getLong(3), Long.MAX_VALUE); 163 | 164 | // bytes 165 | prep.setBytes(1, b1); 166 | prep.setBytes(2, b2); 167 | prep.setBytes(3, b3); 168 | prep.executeQuery(); 169 | assertTrue(rs.next()); 170 | assertArrayEq(rs.getBytes(1), b1); 171 | assertArrayEq(rs.getBytes(2), b2); 172 | assertArrayEq(rs.getBytes(3), b3); 173 | assertFalse(rs.next()); 174 | rs.close(); 175 | } 176 | 177 | @Test public void colNameAccess() throws SQLException { 178 | PreparedStatement prep = conn.prepareStatement( 179 | "select ? as col1, ? as col2, ? as bingo;"); 180 | prep.setNull(1, 0); 181 | prep.setFloat(2, Float.MIN_VALUE); 182 | prep.setShort(3, Short.MIN_VALUE); 183 | prep.executeQuery(); 184 | ResultSet rs = prep.executeQuery(); 185 | assertTrue(rs.next()); 186 | assertNull(rs.getString("col1")); 187 | assertTrue(rs.wasNull()); 188 | assertEquals(rs.getFloat("col2"), Float.MIN_VALUE); 189 | assertEquals(rs.getShort("bingo"), Short.MIN_VALUE); 190 | rs.close(); 191 | prep.close(); 192 | } 193 | 194 | @Test public void insert1000() throws SQLException { 195 | stat.executeUpdate("create table in1000 (a);"); 196 | PreparedStatement prep = conn.prepareStatement( 197 | "insert into in1000 values (?);"); 198 | conn.setAutoCommit(false); 199 | for (int i=0; i < 1000; i++) { 200 | prep.setInt(1, i); 201 | prep.executeUpdate(); 202 | } 203 | conn.commit(); 204 | 205 | ResultSet rs = stat.executeQuery("select count(a) from in1000;"); 206 | assertTrue(rs.next()); 207 | assertEquals(rs.getInt(1), 1000); 208 | rs.close(); 209 | } 210 | 211 | @Ignore 212 | @Test public void getObject() throws SQLException { 213 | stat.executeUpdate("create table testobj (" 214 | + "c1 integer, c2 float, c3, c4 varchar, c5 bit, c6, c7);"); 215 | PreparedStatement prep = conn.prepareStatement( 216 | "insert into testobj values (?,?,?,?,?,?,?);"); 217 | 218 | prep.setInt (1, Integer.MAX_VALUE); 219 | prep.setFloat (2, Float.MAX_VALUE); 220 | prep.setDouble (3, Double.MAX_VALUE); 221 | prep.setLong (4, Long.MAX_VALUE); 222 | prep.setBoolean(5, false); 223 | prep.setByte (6, (byte)7); 224 | prep.setBytes (7, b1); 225 | prep.executeUpdate(); 226 | 227 | ResultSet rs = stat.executeQuery( 228 | "select c1,c2,c3,c4,c5,c6,c7 from testobj;"); 229 | assertTrue(rs.next()); 230 | 231 | assertEquals(rs.getInt(1), Integer.MAX_VALUE); 232 | assertEquals((int)rs.getLong(1), Integer.MAX_VALUE); 233 | assertEquals(rs.getFloat(2), Float.MAX_VALUE); 234 | assertEquals(rs.getDouble(3), Double.MAX_VALUE); 235 | assertEquals(rs.getLong(4), Long.MAX_VALUE); 236 | assertFalse(rs.getBoolean(5)); 237 | assertEquals(rs.getByte(6), (byte)7); 238 | assertArrayEq(rs.getBytes(7), b1); 239 | 240 | assertNotNull(rs.getObject(1)); 241 | assertNotNull(rs.getObject(2)); 242 | assertNotNull(rs.getObject(3)); 243 | assertNotNull(rs.getObject(4)); 244 | assertNotNull(rs.getObject(5)); 245 | assertNotNull(rs.getObject(6)); 246 | assertNotNull(rs.getObject(7)); 247 | assertTrue(rs.getObject(1) instanceof Integer); 248 | assertTrue(rs.getObject(2) instanceof Double); 249 | assertTrue(rs.getObject(3) instanceof Double); 250 | assertTrue(rs.getObject(4) instanceof String); 251 | assertTrue(rs.getObject(5) instanceof Integer); 252 | assertTrue(rs.getObject(6) instanceof Integer); 253 | assertTrue(rs.getObject(7) instanceof byte[]); 254 | rs.close(); 255 | } 256 | 257 | @Test public void tokens() throws SQLException { 258 | /* checks for a bug where a substring is read by the driver as the 259 | * full original string, caused by my idiocyin assuming the 260 | * pascal-style string was null terminated. Thanks Oliver Randschau. */ 261 | StringTokenizer st = new StringTokenizer("one two three"); 262 | st.nextToken(); 263 | String substr = st.nextToken(); 264 | 265 | PreparedStatement prep = conn.prepareStatement("select ?;"); 266 | prep.setString(1, substr); 267 | ResultSet rs = prep.executeQuery(); 268 | assertTrue(rs.next()); 269 | assertEquals(rs.getString(1), substr); 270 | } 271 | 272 | @Test public void utf() throws SQLException { 273 | ResultSet rs = stat.executeQuery("select '" 274 | +utf01+"','"+utf02+"','"+utf03+"','"+utf04+"','" 275 | +utf05+"','"+utf06+"','"+utf07+"','"+utf08+"';"); 276 | assertEquals(rs.getString(1), utf01); 277 | assertEquals(rs.getString(2), utf02); 278 | assertEquals(rs.getString(3), utf03); 279 | assertEquals(rs.getString(4), utf04); 280 | assertEquals(rs.getString(5), utf05); 281 | assertEquals(rs.getString(6), utf06); 282 | assertEquals(rs.getString(7), utf07); 283 | assertEquals(rs.getString(8), utf08); 284 | rs.close(); 285 | 286 | PreparedStatement prep = conn.prepareStatement( 287 | "select ?,?,?,?,?,?,?,?;"); 288 | prep.setString(1, utf01); prep.setString(2, utf02); 289 | prep.setString(3, utf03); prep.setString(4, utf04); 290 | prep.setString(5, utf05); prep.setString(6, utf06); 291 | prep.setString(7, utf07); prep.setString(8, utf08); 292 | rs = prep.executeQuery(); 293 | assertTrue(rs.next()); 294 | assertEquals(rs.getString(1), utf01); 295 | assertEquals(rs.getString(2), utf02); 296 | assertEquals(rs.getString(3), utf03); 297 | assertEquals(rs.getString(4), utf04); 298 | assertEquals(rs.getString(5), utf05); 299 | assertEquals(rs.getString(6), utf06); 300 | assertEquals(rs.getString(7), utf07); 301 | assertEquals(rs.getString(8), utf08); 302 | rs.close(); 303 | } 304 | 305 | @Test public void batch() throws SQLException { 306 | ResultSet rs; 307 | 308 | stat.executeUpdate("create table test (c1, c2, c3, c4);"); 309 | PreparedStatement prep = conn.prepareStatement( 310 | "insert into test values (?,?,?,?);"); 311 | for (int i=0; i < 10; i++) { 312 | prep.setInt(1, Integer.MIN_VALUE + i); 313 | prep.setFloat(2, Float.MIN_VALUE + i); 314 | prep.setString(3, "Hello " + i); 315 | prep.setDouble(4, Double.MAX_VALUE + i); 316 | prep.addBatch(); 317 | } 318 | assertArrayEq(prep.executeBatch(), new int[] { 1,1,1,1,1,1,1,1,1,1 }); 319 | assertEquals(prep.executeBatch().length, 0); 320 | prep.close(); 321 | 322 | rs = stat.executeQuery("select * from test;"); 323 | for (int i=0; i < 10; i++) { 324 | assertTrue(rs.next()); 325 | assertEquals(rs.getInt(1), Integer.MIN_VALUE + i); 326 | assertEquals(rs.getFloat(2), Float.MIN_VALUE + i); 327 | assertEquals(rs.getString(3), "Hello " + i); 328 | assertEquals(rs.getDouble(4), Double.MAX_VALUE + i); 329 | } 330 | rs.close(); 331 | stat.executeUpdate("drop table test;"); 332 | } 333 | 334 | @Test public void retainKeysInBatch() throws SQLException { 335 | stat.executeUpdate("create table test (c1, c2);"); 336 | PreparedStatement prep = conn.prepareStatement( 337 | "insert into test values (?, ?);" 338 | ); 339 | prep.setInt(1, 10); prep.setString(2, "ten"); prep.addBatch(); 340 | prep.setInt(1, 100); prep.setString(2, "hundred"); prep.addBatch(); 341 | prep.setString(2, "one hundred"); prep.addBatch(); 342 | prep.setInt(1, 1000); prep.setString(2, "thousand"); prep.addBatch(); 343 | prep.executeBatch(); 344 | prep.close(); 345 | 346 | ResultSet rs = stat.executeQuery("select * from test;"); 347 | assertTrue(rs.next()); 348 | assertEquals(rs.getInt(1), 10); 349 | assertEquals(rs.getString(2), "ten"); 350 | assertTrue(rs.next()); 351 | assertEquals(rs.getInt(1), 100); 352 | assertEquals(rs.getString(2), "hundred"); 353 | assertTrue(rs.next()); 354 | assertEquals(rs.getInt(1), 100); 355 | assertEquals(rs.getString(2), "one hundred"); 356 | assertTrue(rs.next()); 357 | assertEquals(rs.getInt(1), 1000); 358 | assertEquals(rs.getString(2), "thousand"); 359 | assertFalse(rs.next()); 360 | rs.close(); 361 | } 362 | 363 | @Test public void dblock() throws SQLException { 364 | stat.executeUpdate("create table test (c1);"); 365 | stat.executeUpdate("insert into test values (1);"); 366 | conn.prepareStatement("select * from test;").executeQuery().close(); 367 | stat.executeUpdate("drop table test;"); 368 | 369 | } 370 | 371 | @Test public void dbclose() throws SQLException { 372 | conn.prepareStatement("select ?;").setString(1, "Hello World"); 373 | conn.prepareStatement("select null;").close(); 374 | conn.prepareStatement("select null;").executeQuery().close(); 375 | conn.prepareStatement("create table t (c);").executeUpdate(); 376 | conn.prepareStatement("select null;"); 377 | } 378 | 379 | @Test public void batchOneParam() throws SQLException { 380 | stat.executeUpdate("create table test (c1);"); 381 | PreparedStatement prep = conn.prepareStatement( 382 | "insert into test values (?);"); 383 | for (int i=0; i < 10; i++) { 384 | prep.setInt(1, Integer.MIN_VALUE + i); 385 | prep.addBatch(); 386 | } 387 | assertArrayEq(prep.executeBatch(), new int[] { 1,1,1,1,1,1,1,1,1,1 }); 388 | prep.close(); 389 | ResultSet rs = stat.executeQuery("select count(*) from test;"); 390 | assertTrue(rs.next()); 391 | assertEquals(rs.getInt(1), 10); 392 | rs.close(); 393 | } 394 | 395 | @Test public void paramMetaData() throws SQLException { 396 | PreparedStatement prep = conn.prepareStatement("select ?,?,?,?;"); 397 | assertEquals(prep.getParameterMetaData().getParameterCount(), 4); 398 | } 399 | 400 | @Test public void metaData() throws SQLException { 401 | PreparedStatement prep = conn.prepareStatement( 402 | "select ? as col1, ? as col2, ? as delta;"); 403 | ResultSetMetaData meta = prep.getMetaData(); 404 | assertEquals(meta.getColumnCount(), 3); 405 | assertEquals(meta.getColumnName(1), "col1"); 406 | assertEquals(meta.getColumnName(2), "col2"); 407 | assertEquals(meta.getColumnName(3), "delta"); 408 | /*assertEquals(meta.getColumnType(1), Types.INTEGER); 409 | assertEquals(meta.getColumnType(2), Types.INTEGER); 410 | assertEquals(meta.getColumnType(3), Types.INTEGER);*/ 411 | 412 | meta = prep.executeQuery().getMetaData(); 413 | assertEquals(meta.getColumnCount(), 3); 414 | prep.close(); 415 | } 416 | 417 | @Test public void date1() throws SQLException { 418 | Date d1 = new Date(987654321); 419 | 420 | stat.execute("create table t (c1);"); 421 | PreparedStatement prep = conn.prepareStatement( 422 | "insert into t values(?);"); 423 | prep.setDate(1, d1); 424 | prep.executeUpdate(); 425 | prep.setDate(1, null); 426 | prep.executeUpdate(); 427 | 428 | ResultSet rs = stat.executeQuery("select c1 from t;"); 429 | assertTrue(rs.next()); 430 | assertEquals(rs.getLong(1), d1.getTime()); 431 | assertTrue(rs.getDate(1).equals(d1)); 432 | assertTrue(rs.next()); 433 | assertEquals(rs.getDate(1), null); 434 | rs.close(); 435 | } 436 | 437 | @Test public void date2() throws SQLException { 438 | Date d1 = new Date(1092941466000L); 439 | stat.execute("create table t (c1);"); 440 | PreparedStatement prep = conn.prepareStatement( 441 | "insert into t values (datetime(?/1000, 'unixepoch'));"); 442 | prep.setDate(1, d1); 443 | prep.executeUpdate(); 444 | 445 | ResultSet rs = stat.executeQuery( 446 | "select strftime('%s', c1) * 1000 from t;"); 447 | assertTrue(rs.next()); 448 | assertEquals(rs.getLong(1), d1.getTime()); 449 | assertTrue(rs.getDate(1).equals(d1)); 450 | } 451 | 452 | @Test public void changeSchema() throws SQLException { 453 | stat.execute("create table t (c1);"); 454 | PreparedStatement prep = conn.prepareStatement( 455 | "insert into t values (?);"); 456 | conn.createStatement().execute("create table t2 (c2);"); 457 | prep.setInt(1, 1000); 458 | prep.execute(); 459 | prep.executeUpdate(); 460 | } 461 | 462 | @Test public void reusingSetValues() throws SQLException { 463 | PreparedStatement prep = conn.prepareStatement("select ?,?;"); 464 | prep.setInt(1, 9); 465 | 466 | for (int i=0; i < 10; i++) { 467 | prep.setInt(2, i); 468 | ResultSet rs = prep.executeQuery(); 469 | assertTrue(rs.next()); 470 | assertEquals(rs.getInt(1), 9); 471 | assertEquals(rs.getInt(2), i); 472 | } 473 | 474 | for (int i=0; i < 10; i++) { 475 | prep.setInt(2, i); 476 | ResultSet rs = prep.executeQuery(); 477 | assertTrue(rs.next()); 478 | assertEquals(rs.getInt(1), 9); 479 | assertEquals(rs.getInt(2), i); 480 | rs.close(); 481 | } 482 | 483 | prep.close(); 484 | } 485 | 486 | @Test public void setmaxrows() throws SQLException { 487 | PreparedStatement prep = conn.prepareStatement( 488 | "select 1 union select 2;"); 489 | prep.setMaxRows(1); 490 | ResultSet rs = prep.executeQuery(); 491 | assertTrue(rs.next()); 492 | assertEquals(rs.getInt(1), 1); 493 | assertFalse(rs.next()); 494 | prep.close(); 495 | } 496 | 497 | @Test public void doubleclose() throws SQLException { 498 | PreparedStatement prep = conn.prepareStatement("select null;"); 499 | ResultSet rs = prep.executeQuery(); 500 | rs.close(); 501 | prep.close(); 502 | prep.close(); 503 | } 504 | 505 | @Test(expected= SQLException.class) 506 | public void noSuchTable() throws SQLException { 507 | PreparedStatement prep = 508 | conn.prepareStatement("select * from doesnotexist;"); 509 | prep.executeQuery(); 510 | } 511 | 512 | @Test(expected= SQLException.class) 513 | public void noSuchCol() throws SQLException { 514 | PreparedStatement prep = 515 | conn.prepareStatement("select notacol from (select 1);"); 516 | prep.executeQuery(); 517 | } 518 | 519 | @Test(expected= SQLException.class) 520 | public void noSuchColName() throws SQLException { 521 | ResultSet rs = conn.prepareStatement("select 1;").executeQuery(); 522 | assertTrue(rs.next()); 523 | rs.getInt("noSuchColName"); 524 | } 525 | 526 | private void assertArrayEq(byte[] a, byte[] b) { 527 | assertNotNull(a); 528 | assertNotNull(b); 529 | assertEquals(a.length, b.length); 530 | for (int i=0; i < a.length; i++) 531 | assertEquals(a[i], b[i]); 532 | } 533 | private void assertArrayEq(int[] a, int[] b) { 534 | assertNotNull(a); 535 | assertNotNull(b); 536 | assertEquals(a.length, b.length); 537 | for (int i=0; i < a.length; i++) 538 | assertEquals(a[i], b[i]); 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /src/test/RSMetaDataTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.sql.*; 4 | import org.junit.*; 5 | import static org.junit.Assert.*; 6 | 7 | public class RSMetaDataTest 8 | { 9 | private Connection conn; 10 | private Statement stat; 11 | private ResultSetMetaData meta; 12 | 13 | @BeforeClass public static void forName() throws Exception { 14 | Class.forName("org.sqlite.JDBC"); 15 | } 16 | 17 | @Before public void connect() throws Exception { 18 | conn = DriverManager.getConnection("jdbc:sqlite:"); 19 | stat = conn.createStatement(); 20 | stat.executeUpdate( 21 | "create table People (pid integer primary key autoincrement, " 22 | + " firstname string, surname string, dob date);"); 23 | stat.executeUpdate( 24 | "insert into people values (null, 'Mohandas', 'Gandhi', " 25 | + " '1869-10-02');"); 26 | meta = stat.executeQuery( 27 | "select pid, firstname, surname from people;").getMetaData(); 28 | } 29 | 30 | @After public void close() throws SQLException { 31 | stat.executeUpdate("drop table people;"); 32 | stat.close(); 33 | conn.close(); 34 | } 35 | 36 | @Test public void catalogName() throws SQLException { 37 | assertEquals(meta.getCatalogName(1), "People"); 38 | } 39 | 40 | @Test public void columns() throws SQLException { 41 | assertEquals(meta.getColumnCount(), 3); 42 | assertEquals(meta.getColumnName(1), "pid"); 43 | assertEquals(meta.getColumnName(2), "firstname"); 44 | assertEquals(meta.getColumnName(3), "surname"); 45 | assertEquals(meta.getColumnType(1), Types.INTEGER); 46 | assertEquals(meta.getColumnType(2), Types.VARCHAR); 47 | assertEquals(meta.getColumnType(3), Types.VARCHAR); 48 | assertEquals(meta.getColumnTypeName(1), "integer"); 49 | assertEquals(meta.getColumnTypeName(2), "text"); 50 | assertEquals(meta.getColumnTypeName(3), "text"); 51 | assertTrue(meta.isAutoIncrement(1)); 52 | assertFalse(meta.isAutoIncrement(2)); 53 | assertFalse(meta.isAutoIncrement(3)); 54 | assertEquals(meta.isNullable(1), meta.columnNoNulls); 55 | assertEquals(meta.isNullable(2), meta.columnNullable); 56 | assertEquals(meta.isNullable(3), meta.columnNullable); 57 | } 58 | 59 | @Test public void differentRS() throws SQLException { 60 | meta = stat.executeQuery("select * from people;").getMetaData(); 61 | assertEquals(meta.getColumnCount(), 4); 62 | assertEquals(meta.getColumnName(1), "pid"); 63 | assertEquals(meta.getColumnName(2), "firstname"); 64 | assertEquals(meta.getColumnName(3), "surname"); 65 | assertEquals(meta.getColumnName(4), "dob"); 66 | } 67 | 68 | @Test public void nullable() throws SQLException { 69 | meta = stat.executeQuery("select null;").getMetaData(); 70 | assertEquals(meta.isNullable(1), ResultSetMetaData.columnNullable); 71 | } 72 | 73 | @Test(expected= SQLException.class) 74 | public void badCatalogIndex() throws SQLException { meta.getCatalogName(4);} 75 | 76 | @Test(expected= SQLException.class) 77 | public void badColumnIndex() throws SQLException { meta.getColumnName(4); } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/StatementTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.sql.*; 4 | import org.junit.*; 5 | import static org.junit.Assert.*; 6 | 7 | /** These tests are designed to stress Statements on memory databases. */ 8 | public class StatementTest 9 | { 10 | private Connection conn; 11 | private Statement stat; 12 | 13 | @BeforeClass public static void forName() throws Exception { 14 | Class.forName("org.sqlite.JDBC"); 15 | } 16 | 17 | @Before public void connect() throws Exception { 18 | conn = DriverManager.getConnection("jdbc:sqlite:"); 19 | stat = conn.createStatement(); 20 | } 21 | 22 | @After public void close() throws SQLException { 23 | stat.close(); 24 | conn.close(); 25 | } 26 | 27 | @Test public void stmtUpdate() throws SQLException { 28 | assertEquals(stat.executeUpdate("create table s1 (c1);"), 0); 29 | assertEquals(stat.executeUpdate("insert into s1 values (0);"), 1); 30 | assertEquals(stat.executeUpdate("insert into s1 values (1);"), 1); 31 | assertEquals(stat.executeUpdate("insert into s1 values (2);"), 1); 32 | ResultSet rs = stat.executeQuery("select count(c1) from s1;"); 33 | assertTrue(rs.next()); 34 | assertEquals(rs.getInt(1), 3); 35 | rs.close(); 36 | assertEquals(stat.executeUpdate("update s1 set c1 = 5;"), 3); 37 | assertEquals(stat.executeUpdate("delete from s1;"), 3); 38 | // TODO: assertEquals(stat.executeUpdate("drop table s1;"), 0); 39 | } 40 | 41 | @Test public void emptyRS() throws SQLException { 42 | ResultSet rs = stat.executeQuery("select null limit 0;"); 43 | assertFalse(rs.next()); 44 | rs.close(); 45 | } 46 | 47 | @Test public void singleRowRS() throws SQLException { 48 | ResultSet rs = stat.executeQuery("select " + Integer.MAX_VALUE + ";"); 49 | assertTrue(rs.next()); 50 | assertEquals(rs.getInt(1), Integer.MAX_VALUE); 51 | assertEquals(rs.getString(1), Integer.toString(Integer.MAX_VALUE)); 52 | assertEquals(rs.getDouble(1), 53 | new Integer(Integer.MAX_VALUE).doubleValue()); 54 | assertFalse(rs.next()); 55 | rs.close(); 56 | } 57 | 58 | @Test public void twoRowRS() throws SQLException { 59 | ResultSet rs = stat.executeQuery("select 9 union all select 7;"); 60 | assertTrue(rs.next()); 61 | assertEquals(rs.getInt(1), 9); 62 | assertTrue(rs.next()); 63 | assertEquals(rs.getInt(1), 7); 64 | assertFalse(rs.next()); 65 | rs.close(); 66 | } 67 | 68 | @Test public void autoClose() throws SQLException { 69 | conn.createStatement().executeQuery("select 1;"); 70 | } 71 | 72 | @Test public void stringRS() throws SQLException { 73 | ResultSet rs = stat.executeQuery("select \"Russell\";"); 74 | assertTrue(rs.next()); 75 | assertEquals(rs.getString(1), "Russell"); 76 | assertFalse(rs.next()); 77 | rs.close(); 78 | } 79 | 80 | @Test public void execute() throws SQLException { 81 | assertTrue(stat.execute("select null;")); 82 | ResultSet rs = stat.getResultSet(); 83 | assertNotNull(rs); 84 | assertTrue(rs.next()); 85 | assertNull(rs.getString(1)); 86 | assertTrue(rs.wasNull()); 87 | assertFalse(stat.getMoreResults()); 88 | assertEquals(stat.getUpdateCount(), -1); 89 | 90 | assertTrue(stat.execute("select null;")); 91 | assertFalse(stat.getMoreResults()); 92 | assertEquals(stat.getUpdateCount(), -1); 93 | 94 | assertFalse(stat.execute("create table test (c1);")); 95 | assertEquals(stat.getUpdateCount(), 0); 96 | assertFalse(stat.getMoreResults()); 97 | assertEquals(stat.getUpdateCount(), -1); 98 | } 99 | 100 | @Test public void colNameAccess() throws SQLException { 101 | assertEquals(stat.executeUpdate( 102 | "create table tab (id, firstname, surname);"), 0); 103 | assertEquals(stat.executeUpdate( 104 | "insert into tab values (0, 'Bob', 'Builder');"), 1); 105 | assertEquals(stat.executeUpdate( 106 | "insert into tab values (1, 'Fred', 'Blogs');"), 1); 107 | assertEquals(stat.executeUpdate( 108 | "insert into tab values (2, 'John', 'Smith');"), 1); 109 | ResultSet rs = stat.executeQuery("select * from tab;"); 110 | assertTrue(rs.next()); 111 | assertEquals(rs.getInt("id"), 0); 112 | assertEquals(rs.getString("firstname"), "Bob"); 113 | assertEquals(rs.getString("surname"), "Builder"); 114 | assertTrue(rs.next()); 115 | assertEquals(rs.getInt("id"), 1); 116 | assertEquals(rs.getString("firstname"), "Fred"); 117 | assertEquals(rs.getString("surname"), "Blogs"); 118 | assertTrue(rs.next()); 119 | assertEquals(rs.getInt("id"), 2); 120 | assertEquals( rs.getString("id"), "2"); 121 | assertEquals(rs.getString("firstname"), "John"); 122 | assertEquals(rs.getString("surname"), "Smith"); 123 | assertFalse(rs.next()); 124 | rs.close(); 125 | assertEquals(stat.executeUpdate("drop table tab;"), 1); 126 | } 127 | 128 | @Test public void nulls() throws SQLException { 129 | ResultSet rs = stat.executeQuery("select null union all select null;"); 130 | assertTrue(rs.next()); 131 | assertNull(rs.getString(1)); 132 | assertTrue(rs.wasNull()); 133 | assertTrue(rs.next()); 134 | assertNull(rs.getString(1)); 135 | assertTrue(rs.wasNull()); 136 | assertFalse(rs.next()); 137 | rs.close(); 138 | } 139 | 140 | @Test public void tempTable() throws SQLException { 141 | assertEquals(stat.executeUpdate("create temp table myTemp (a);"), 0); 142 | assertEquals(stat.executeUpdate("insert into myTemp values (2);"), 1); 143 | } 144 | 145 | @Test public void insert1000() throws SQLException { 146 | assertEquals(stat.executeUpdate("create table in1000 (a);"), 0); 147 | conn.setAutoCommit(false); 148 | for (int i=0; i < 1000; i++) 149 | assertEquals(stat.executeUpdate( 150 | "insert into in1000 values ("+i+");"), 1); 151 | conn.commit(); 152 | 153 | ResultSet rs = stat.executeQuery("select count(a) from in1000;"); 154 | assertTrue(rs.next()); 155 | assertEquals(rs.getInt(1), 1000); 156 | rs.close(); 157 | 158 | assertEquals(stat.executeUpdate("drop table in1000;"), 1); 159 | } 160 | 161 | private void assertArrayEq(int[] a, int[] b) { 162 | assertNotNull(a); 163 | assertNotNull(b); 164 | assertEquals(a.length, b.length); 165 | for (int i=0; i < a.length; i++) 166 | assertEquals(a[i], b[i]); 167 | } 168 | 169 | @Test public void batch() throws SQLException { 170 | stat.addBatch("create table batch (c1);"); 171 | stat.addBatch("insert into batch values (1);"); 172 | stat.addBatch("insert into batch values (1);"); 173 | stat.addBatch("insert into batch values (2);"); 174 | stat.addBatch("insert into batch values (3);"); 175 | stat.addBatch("insert into batch values (4);"); 176 | stat.addBatch("insert into batch values (5);"); 177 | stat.addBatch("insert into batch values (6);"); 178 | stat.addBatch("insert into batch values (7);"); 179 | stat.addBatch("insert into batch values (8);"); 180 | stat.addBatch("insert into batch values (9);"); 181 | stat.addBatch("insert into batch values (10);"); 182 | stat.addBatch("insert into batch values (11);"); 183 | stat.addBatch("insert into batch values (12);"); 184 | assertArrayEq(new int[] { 0,1,1,1,1,1,1,1,1,1,1,1,1,1 }, 185 | stat.executeBatch()); 186 | assertArrayEq(new int[] { }, stat.executeBatch()); 187 | stat.clearBatch(); 188 | stat.addBatch("insert into batch values (9);"); 189 | assertArrayEq(new int[] { 1 }, stat.executeBatch()); 190 | assertArrayEq(new int[] {}, stat.executeBatch()); 191 | stat.clearBatch(); 192 | stat.addBatch("insert into batch values (7);"); 193 | stat.addBatch("insert into batch values (7);"); 194 | assertArrayEq(new int[] { 1, 1 }, stat.executeBatch()); 195 | stat.clearBatch(); 196 | 197 | ResultSet rs = stat.executeQuery("select count(*) from batch;"); 198 | assertTrue(rs.next()); 199 | assertEquals(16, rs.getInt(1)); 200 | rs.close(); 201 | } 202 | 203 | @Test public void closeOnFalseNext() throws SQLException { 204 | stat.executeUpdate("create table t1 (c1);"); 205 | conn.createStatement().executeQuery("select * from t1;").next(); 206 | stat.executeUpdate("drop table t1;"); 207 | } 208 | 209 | @Test public void getGeneratedKeys() throws SQLException { 210 | ResultSet rs; 211 | stat.executeUpdate("create table t1 (c1 integer primary key, v);"); 212 | stat.executeUpdate("insert into t1 (v) values ('red');"); 213 | rs = stat.getGeneratedKeys(); 214 | assertTrue(rs.next()); 215 | assertEquals(rs.getInt(1), 1); 216 | rs.close(); 217 | stat.executeUpdate("insert into t1 (v) values ('blue');"); 218 | rs = stat.getGeneratedKeys(); 219 | assertTrue(rs.next()); 220 | assertEquals(rs.getInt(1), 2); 221 | rs.close(); 222 | } 223 | 224 | @Test public void isBeforeFirst() throws SQLException { 225 | ResultSet rs = stat.executeQuery("select 1 union all select 2;"); 226 | assertTrue(rs.isBeforeFirst()); 227 | assertTrue(rs.next()); 228 | assertTrue(rs.isFirst()); 229 | assertEquals(rs.getInt(1), 1); 230 | assertTrue(rs.next()); 231 | assertFalse(rs.isBeforeFirst()); 232 | assertFalse(rs.isFirst()); 233 | assertEquals(rs.getInt(1), 2); 234 | assertFalse(rs.next()); 235 | assertFalse(rs.isBeforeFirst()); 236 | rs.close(); 237 | } 238 | 239 | @Test public void columnNaming() throws SQLException { 240 | stat.executeUpdate("create table t1 (c1 integer);"); 241 | stat.executeUpdate("create table t2 (c1 integer);"); 242 | stat.executeUpdate("insert into t1 values (1);"); 243 | stat.executeUpdate("insert into t2 values (1);"); 244 | ResultSet rs = stat.executeQuery( 245 | "select a.c1 AS c1 from t1 a, t2 where a.c1=t2.c1;"); 246 | assertTrue(rs.next()); 247 | assertEquals(rs.getInt("c1"), 1); 248 | rs.close(); 249 | } 250 | 251 | @Test public void maxRows() throws SQLException { 252 | stat.setMaxRows(1); 253 | assertEquals(stat.getMaxRows(), 1); 254 | ResultSet rs = stat.executeQuery("select 1 union select 2;"); 255 | assertTrue(rs.next()); 256 | assertEquals(rs.getInt(1), 1); 257 | assertFalse(rs.next()); 258 | rs.close(); 259 | } 260 | 261 | @Test public void nullDate() throws SQLException { 262 | ResultSet rs = stat.executeQuery("select null;"); 263 | assertTrue(rs.next()); 264 | assertEquals(rs.getDate(1), null); 265 | assertEquals(rs.getTime(1), null); 266 | assertEquals(rs.getTimestamp(1), null); 267 | rs.close(); 268 | } 269 | 270 | @Test(expected= SQLException.class) 271 | public void ambiguousColumnNaming() throws SQLException { 272 | stat.executeUpdate("create table t1 (c1 int);"); 273 | stat.executeUpdate("create table t2 (c1 int, c2 int);"); 274 | stat.executeUpdate("insert into t1 values (1);"); 275 | stat.executeUpdate("insert into t2 values (2, 1);"); 276 | ResultSet rs = stat.executeQuery( 277 | "select a.c1, b.c1 from t1 a, t2 b where a.c1=b.c2;"); 278 | assertTrue(rs.next()); 279 | assertEquals(rs.getInt("c1"), 1); 280 | rs.close(); 281 | } 282 | 283 | @Test(expected= SQLException.class) 284 | public void failToDropWhenRSOpen() throws SQLException { 285 | stat.executeUpdate("create table t1 (c1);"); 286 | stat.executeUpdate("insert into t1 values (4);"); 287 | stat.executeUpdate("insert into t1 values (4);"); 288 | conn.createStatement().executeQuery("select * from t1;").next(); 289 | stat.executeUpdate("drop table t1;"); 290 | } 291 | 292 | @Test(expected= SQLException.class) 293 | public void executeNoRS() throws SQLException { 294 | assertFalse(stat.execute("insert into test values (8);")); 295 | stat.getResultSet(); 296 | } 297 | 298 | @Test(expected= SQLException.class) 299 | public void executeClearRS() throws SQLException { 300 | assertTrue(stat.execute("select null;")); 301 | assertNotNull(stat.getResultSet()); 302 | assertFalse(stat.getMoreResults()); 303 | stat.getResultSet(); 304 | } 305 | 306 | @Test(expected= BatchUpdateException.class) 307 | public void batchReturnsResults() throws SQLException { 308 | stat.addBatch("select null;"); 309 | stat.executeBatch(); 310 | } 311 | 312 | @Test(expected= SQLException.class) 313 | public void noSuchTable() throws SQLException { 314 | stat.executeQuery("select * from doesnotexist;"); 315 | } 316 | 317 | @Test(expected= SQLException.class) 318 | public void noSuchCol() throws SQLException { 319 | stat.executeQuery("select notacol from (select 1);"); 320 | } 321 | 322 | @Test(expected= SQLException.class) 323 | public void noSuchColName() throws SQLException { 324 | ResultSet rs = stat.executeQuery("select 1;"); 325 | assertTrue(rs.next()); 326 | rs.getInt("noSuchColName"); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/test/TransactionTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.io.File; 4 | import java.sql.*; 5 | import org.junit.*; 6 | import static org.junit.Assert.*; 7 | 8 | /** These tests assume that Statements and PreparedStatements are working 9 | * as per normal and test the interactions of commit(), rollback() and 10 | * setAutoCommit(boolean) with multiple connections to the same db. */ 11 | public class TransactionTest 12 | { 13 | private Connection conn1, conn2, conn3; 14 | private Statement stat1, stat2, stat3; 15 | 16 | boolean done = false; 17 | 18 | @BeforeClass public static void forName() throws Exception { 19 | Class.forName("org.sqlite.JDBC"); 20 | } 21 | 22 | @Before public void connect() throws Exception { 23 | new File("test-trans.db").delete(); 24 | conn1 = DriverManager.getConnection("jdbc:sqlite:test-trans.db"); 25 | conn2 = DriverManager.getConnection("jdbc:sqlite:test-trans.db"); 26 | conn3 = DriverManager.getConnection("jdbc:sqlite:test-trans.db"); 27 | stat1 = conn1.createStatement(); 28 | stat2 = conn2.createStatement(); 29 | stat3 = conn3.createStatement(); 30 | } 31 | 32 | @After public void close() throws Exception { 33 | stat1.close(); stat2.close(); stat3.close(); 34 | conn1.close(); conn2.close(); conn3.close(); 35 | new File("test-trans.db").delete(); 36 | } 37 | 38 | @Test public void multiConn() throws SQLException { 39 | stat1.executeUpdate("create table test (c1);"); 40 | stat1.executeUpdate("insert into test values (1);"); 41 | stat2.executeUpdate("insert into test values (2);"); 42 | stat3.executeUpdate("insert into test values (3);"); 43 | 44 | ResultSet rs = stat1.executeQuery("select sum(c1) from test;"); 45 | assertTrue(rs.next()); 46 | assertEquals(rs.getInt(1), 6); 47 | rs.close(); 48 | 49 | rs = stat3.executeQuery("select sum(c1) from test;"); 50 | assertTrue(rs.next()); 51 | assertEquals(rs.getInt(1), 6); 52 | rs.close(); 53 | } 54 | 55 | @Test public void locking() throws SQLException { 56 | stat1.executeUpdate("create table test (c1);"); 57 | stat1.executeUpdate("begin immediate;"); 58 | stat2.executeUpdate("select * from test;"); 59 | } 60 | 61 | @Test public void insert() throws SQLException { 62 | ResultSet rs; 63 | String countSql = "select count(*) from trans;"; 64 | 65 | stat1.executeUpdate("create table trans (c1);"); 66 | conn1.setAutoCommit(false); 67 | 68 | assertEquals(1, stat1.executeUpdate("insert into trans values (4);")); 69 | 70 | // transaction not yet commited, conn1 can see, conn2 can not 71 | rs = stat1.executeQuery(countSql); 72 | assertTrue(rs.next()); 73 | assertEquals(1, rs.getInt(1)); 74 | rs.close(); 75 | rs = stat2.executeQuery(countSql); 76 | assertTrue(rs.next()); 77 | assertEquals(0, rs.getInt(1)); 78 | rs.close(); 79 | 80 | conn1.commit(); 81 | 82 | // all connects can see data 83 | rs = stat2.executeQuery(countSql); 84 | assertTrue(rs.next()); 85 | assertEquals(1, rs.getInt(1)); 86 | rs.close(); 87 | } 88 | 89 | @Test public void rollback() throws SQLException { 90 | String select = "select * from trans;"; 91 | ResultSet rs; 92 | 93 | stat1.executeUpdate("create table trans (c1);"); 94 | conn1.setAutoCommit(false); 95 | stat1.executeUpdate("insert into trans values (3);"); 96 | 97 | rs = stat1.executeQuery(select); 98 | assertTrue(rs.next()); 99 | rs.close(); 100 | 101 | conn1.rollback(); 102 | 103 | rs = stat1.executeQuery(select); 104 | assertFalse(rs.next()); 105 | rs.close(); 106 | } 107 | 108 | @Test public void multiRollback() throws SQLException { 109 | ResultSet rs; 110 | 111 | stat1.executeUpdate("create table t (c1);"); 112 | conn1.setAutoCommit(false); 113 | stat1.executeUpdate("insert into t values (1);"); 114 | conn1.commit(); 115 | stat1.executeUpdate("insert into t values (1);"); 116 | conn1.rollback(); 117 | stat1.addBatch("insert into t values (2);"); 118 | stat1.addBatch("insert into t values (3);"); 119 | stat1.executeBatch(); 120 | conn1.commit(); 121 | stat1.addBatch("insert into t values (7);"); 122 | stat1.executeBatch(); 123 | conn1.rollback(); 124 | stat1.executeUpdate("insert into t values (4);"); 125 | conn1.setAutoCommit(true); 126 | stat1.executeUpdate("insert into t values (5);"); 127 | conn1.setAutoCommit(false); 128 | PreparedStatement p = conn1.prepareStatement( 129 | "insert into t values (?);"); 130 | p.setInt(1, 6); 131 | p.executeUpdate(); 132 | p.setInt(1, 7); 133 | p.executeUpdate(); 134 | 135 | // conn1 can see (1+...+7), conn2 can see (1+...+5) 136 | rs = stat1.executeQuery("select sum(c1) from t;"); 137 | assertTrue(rs.next()); 138 | assertEquals(1+2+3+4+5+6+7, rs.getInt(1)); 139 | rs.close(); 140 | rs = stat2.executeQuery("select sum(c1) from t;"); 141 | assertTrue(rs.next()); 142 | assertEquals(1+2+3+4+5, rs.getInt(1)); 143 | rs.close(); 144 | } 145 | 146 | @Test 147 | public void transactionsDontMindReads() throws SQLException { 148 | stat1.executeUpdate("create table t (c1);"); 149 | stat1.executeUpdate("insert into t values (1);"); 150 | stat1.executeUpdate("insert into t values (2);"); 151 | ResultSet rs = stat1.executeQuery("select * from t;"); 152 | assertTrue(rs.next()); // select is open 153 | 154 | conn2.setAutoCommit(false); 155 | stat1.executeUpdate("insert into t values (2);"); 156 | 157 | rs.close(); 158 | conn2.commit(); 159 | } 160 | 161 | @Test 162 | public void secondConnWillWait() throws Exception { 163 | stat1.executeUpdate("create table t (c1);"); 164 | stat1.executeUpdate("insert into t values (1);"); 165 | stat1.executeUpdate("insert into t values (2);"); 166 | ResultSet rs = stat1.executeQuery("select * from t;"); 167 | assertTrue(rs.next()); 168 | 169 | final TransactionTest lock = this; 170 | lock.done = false; 171 | new Thread() { public void run() { 172 | try { 173 | stat2.executeUpdate("insert into t values (3);"); 174 | } catch (SQLException e) { 175 | e.printStackTrace(); 176 | return; 177 | } 178 | 179 | synchronized (lock) { 180 | lock.done = true; 181 | lock.notify(); 182 | } 183 | } }.start(); 184 | 185 | Thread.sleep(100); 186 | rs.close(); 187 | 188 | synchronized (lock) { 189 | lock.wait(5000); 190 | if (!lock.done) 191 | throw new Exception("should be done"); 192 | } 193 | } 194 | 195 | @Test(expected= SQLException.class) 196 | public void secondConnMustTimeout() throws SQLException { 197 | stat1.setQueryTimeout(1); 198 | stat1.executeUpdate("create table t (c1);"); 199 | stat1.executeUpdate("insert into t values (1);"); 200 | stat1.executeUpdate("insert into t values (2);"); 201 | ResultSet rs = stat1.executeQuery("select * from t;"); 202 | assertTrue(rs.next()); 203 | 204 | stat2.executeUpdate("insert into t values (3);"); // can't be done 205 | } 206 | 207 | @Test(expected= SQLException.class) 208 | public void cantCommit() throws SQLException { conn1.commit(); } 209 | 210 | @Test(expected= SQLException.class) 211 | public void cantRollback() throws SQLException { conn1.rollback(); } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/test/UDFTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.io.File; 4 | import java.sql.*; 5 | import java.util.*; 6 | import org.sqlite.Function; 7 | import org.junit.*; 8 | import static org.junit.Assert.*; 9 | 10 | /** Tests User Defined Functions. */ 11 | public class UDFTest 12 | { 13 | private static int val = 0; 14 | private static byte[] b1 = new byte[] { 2, 5, -4, 8, -1, 3, -5 }; 15 | private static int gotTrigger = 0; 16 | 17 | private Connection conn; 18 | private Statement stat; 19 | 20 | @BeforeClass public static void forName() throws Exception { 21 | Class.forName("org.sqlite.JDBC"); 22 | } 23 | 24 | @Before public void connect() throws Exception { 25 | conn = DriverManager.getConnection("jdbc:sqlite:"); 26 | stat = conn.createStatement(); 27 | } 28 | 29 | @After public void close() throws SQLException { 30 | stat.close(); 31 | conn.close(); 32 | } 33 | 34 | @Test public void calling() throws SQLException { 35 | Function.create(conn, "f1", new Function() { 36 | public void xFunc() throws SQLException { val = 4; } 37 | }); 38 | stat.executeQuery("select f1();").close(); 39 | assertEquals(val, 4); 40 | } 41 | 42 | @Test public void returning() throws SQLException { 43 | Function.create(conn, "f2", new Function() { 44 | public void xFunc() throws SQLException { result(4); } 45 | }); 46 | ResultSet rs = stat.executeQuery("select f2();"); 47 | assertTrue(rs.next()); 48 | assertEquals(rs.getInt(1), 4); 49 | rs.close(); 50 | 51 | for (int i=0; i < 20; i++) { 52 | rs = stat.executeQuery("select (f2() + " + i + ");"); 53 | assertTrue(rs.next()); 54 | assertEquals(rs.getInt(1), 4 + i); 55 | rs.close(); 56 | } 57 | } 58 | 59 | @Test public void accessArgs() throws SQLException { 60 | Function.create(conn, "f3", new Function() { 61 | public void xFunc() throws SQLException { result(value_int(0)); } 62 | }); 63 | for (int i=0; i < 15; i++) { 64 | ResultSet rs = stat.executeQuery("select f3("+i+");"); 65 | assertTrue(rs.next()); 66 | assertEquals(rs.getInt(1), i); 67 | rs.close(); 68 | } 69 | } 70 | 71 | @Test public void multipleArgs() throws SQLException { 72 | Function.create(conn, "f4", new Function() { 73 | public void xFunc() throws SQLException { 74 | int ret = 0; 75 | for (int i=0; i < args(); i++) ret += value_int(i); 76 | result(ret); 77 | } 78 | }); 79 | ResultSet rs = stat.executeQuery("select f4(2, 3, 9, -5);"); 80 | assertTrue(rs.next()); 81 | assertEquals(rs.getInt(1), 9); 82 | rs.close(); 83 | rs = stat.executeQuery("select f4(2);"); 84 | assertTrue(rs.next()); 85 | assertEquals(rs.getInt(1), 2); 86 | rs.close(); 87 | rs = stat.executeQuery("select f4(-3, -4, -5);"); 88 | assertTrue(rs.next()); 89 | assertEquals(rs.getInt(1), -12); 90 | } 91 | 92 | @Test public void returnTypes() throws SQLException { 93 | Function.create(conn, "f5", new Function() { 94 | public void xFunc() throws SQLException { result("Hello World"); } 95 | }); 96 | ResultSet rs = stat.executeQuery("select f5();"); 97 | assertTrue(rs.next()); 98 | assertEquals(rs.getString(1), "Hello World"); 99 | 100 | Function.create(conn, "f6", new Function() { 101 | public void xFunc() throws SQLException { result(Long.MAX_VALUE); } 102 | }); 103 | rs.close(); rs = stat.executeQuery("select f6();"); 104 | assertTrue(rs.next()); 105 | assertEquals(rs.getLong(1), Long.MAX_VALUE); 106 | 107 | Function.create(conn, "f7", new Function() { 108 | public void xFunc() throws SQLException {result(Double.MAX_VALUE);} 109 | }); 110 | rs.close(); rs = stat.executeQuery("select f7();"); 111 | assertTrue(rs.next()); 112 | assertEquals(rs.getDouble(1), Double.MAX_VALUE); 113 | 114 | Function.create(conn, "f8", new Function() { 115 | public void xFunc() throws SQLException { result(b1); } 116 | }); 117 | rs.close(); rs = stat.executeQuery("select f8();"); 118 | assertTrue(rs.next()); 119 | assertArrayEq(rs.getBytes(1), b1); 120 | } 121 | 122 | @Test public void returnArgInt() throws SQLException { 123 | Function.create(conn, "farg_int", new Function() { 124 | public void xFunc() throws SQLException { result(value_int(0)); } 125 | }); 126 | PreparedStatement prep = conn.prepareStatement("select farg_int(?);"); 127 | prep.setInt(1, Integer.MAX_VALUE); 128 | ResultSet rs = prep.executeQuery(); 129 | assertTrue(rs.next()); 130 | assertEquals(rs.getInt(1), Integer.MAX_VALUE); 131 | prep.close(); 132 | } 133 | 134 | @Test public void returnArgLong() throws SQLException { 135 | Function.create(conn, "farg_long", new Function() { 136 | public void xFunc() throws SQLException { result(value_long(0)); } 137 | }); 138 | PreparedStatement prep = conn.prepareStatement("select farg_long(?);"); 139 | prep.setLong(1, Long.MAX_VALUE); 140 | ResultSet rs = prep.executeQuery(); 141 | assertTrue(rs.next()); 142 | assertEquals(rs.getLong(1), Long.MAX_VALUE); 143 | prep.close(); 144 | } 145 | 146 | @Test public void returnArgDouble() throws SQLException { 147 | Function.create(conn, "farg_doub", new Function() { 148 | public void xFunc() throws SQLException { result(value_double(0)); } 149 | }); 150 | PreparedStatement prep = conn.prepareStatement("select farg_doub(?);"); 151 | prep.setDouble(1, Double.MAX_VALUE); 152 | ResultSet rs = prep.executeQuery(); 153 | assertTrue(rs.next()); 154 | assertEquals(rs.getDouble(1), Double.MAX_VALUE); 155 | prep.close(); 156 | } 157 | 158 | @Test public void returnArgBlob() throws SQLException { 159 | Function.create(conn, "farg_blob", new Function() { 160 | public void xFunc() throws SQLException { result(value_blob(0)); } 161 | }); 162 | PreparedStatement prep = conn.prepareStatement("select farg_blob(?);"); 163 | prep.setBytes(1, b1); 164 | ResultSet rs = prep.executeQuery(); 165 | assertTrue(rs.next()); 166 | assertArrayEq(rs.getBytes(1), b1); 167 | prep.close(); 168 | } 169 | 170 | @Test public void returnArgString() throws SQLException { 171 | Function.create(conn, "farg_str", new Function() { 172 | public void xFunc() throws SQLException { result(value_text(0)); } 173 | }); 174 | PreparedStatement prep = conn.prepareStatement("select farg_str(?);"); 175 | prep.setString(1, "Hello"); 176 | ResultSet rs = prep.executeQuery(); 177 | assertTrue(rs.next()); 178 | assertEquals(rs.getString(1), "Hello"); 179 | prep.close(); 180 | } 181 | 182 | @Test(expected= SQLException.class) 183 | public void customErr() throws SQLException { 184 | Function.create(conn, "f9", new Function() { 185 | public void xFunc() throws SQLException { 186 | throw new SQLException("myErr"); } 187 | }); 188 | stat.executeQuery("select f9();"); 189 | } 190 | 191 | @Test public void trigger() throws SQLException { 192 | Function.create(conn, "inform", new Function() { 193 | protected void xFunc() throws SQLException { 194 | gotTrigger = value_int(0); } 195 | }); 196 | stat.executeUpdate("create table trigtest (c1);"); 197 | stat.executeUpdate( 198 | "create trigger trigt after insert on trigtest" 199 | + " begin select inform(new.c1); end;" 200 | ); 201 | stat.executeUpdate("insert into trigtest values (5);"); 202 | assertEquals(gotTrigger, 5); 203 | } 204 | 205 | @Test public void aggregate() throws SQLException { 206 | Function.create(conn, "mySum", new Function.Aggregate() { 207 | private int val = 0; 208 | protected void xStep() throws SQLException { 209 | for (int i=0; i < args(); i++) val += value_int(i); 210 | } 211 | protected void xFinal() throws SQLException { 212 | result(val); 213 | } 214 | }); 215 | stat.executeUpdate("create table t (c1);"); 216 | stat.executeUpdate("insert into t values (5);"); 217 | stat.executeUpdate("insert into t values (3);"); 218 | stat.executeUpdate("insert into t values (8);"); 219 | stat.executeUpdate("insert into t values (2);"); 220 | stat.executeUpdate("insert into t values (7);"); 221 | ResultSet rs = stat.executeQuery("select mySum(c1), sum(c1) from t;"); 222 | assertTrue(rs.next()); 223 | assertEquals(rs.getInt(1), rs.getInt(2)); 224 | } 225 | 226 | @Test public void destroy() throws SQLException { 227 | Function.create(conn, "f1", new Function() { 228 | public void xFunc() throws SQLException { val = 9; } 229 | }); 230 | stat.executeQuery("select f1();").close(); 231 | assertEquals(val, 9); 232 | 233 | Function.destroy(conn, "f1"); 234 | Function.destroy(conn, "f1"); 235 | } 236 | 237 | @Test public void manyfunctions() throws SQLException { 238 | Function.create(conn, "f1", new Function() { 239 | public void xFunc() throws SQLException { result(1); } }); 240 | Function.create(conn, "f2", new Function() { 241 | public void xFunc() throws SQLException { result(2); } }); 242 | Function.create(conn, "f3", new Function() { 243 | public void xFunc() throws SQLException { result(3); } }); 244 | Function.create(conn, "f4", new Function() { 245 | public void xFunc() throws SQLException { result(4); } }); 246 | Function.create(conn, "f5", new Function() { 247 | public void xFunc() throws SQLException { result(5); } }); 248 | Function.create(conn, "f6", new Function() { 249 | public void xFunc() throws SQLException { result(6); } }); 250 | Function.create(conn, "f7", new Function() { 251 | public void xFunc() throws SQLException { result(7); } }); 252 | Function.create(conn, "f8", new Function() { 253 | public void xFunc() throws SQLException { result(8); } }); 254 | Function.create(conn, "f9", new Function() { 255 | public void xFunc() throws SQLException { result(9); } }); 256 | Function.create(conn, "f10", new Function() { 257 | public void xFunc() throws SQLException { result(10); } }); 258 | Function.create(conn, "f11", new Function() { 259 | public void xFunc() throws SQLException { result(11); } }); 260 | 261 | ResultSet rs = stat.executeQuery( 262 | "select f1() + f2() + f3() + f4() + f5() + f6()" 263 | + " + f7() + f8() + f9() + f10() + f11();"); 264 | assertTrue(rs.next()); 265 | assertEquals(rs.getInt(1), 1+2+3+4+5+6+7+8+9+10+11); 266 | rs.close(); 267 | } 268 | 269 | @Test public void multipleThreads() throws Exception { 270 | Function func = new Function() { 271 | int sum = 0; 272 | protected void xFunc() { try { 273 | sum += value_int(1); 274 | } catch (SQLException e) { e.printStackTrace(); } } 275 | public String toString() { return String.valueOf(sum); } 276 | }; 277 | Function.create(conn, "func", func); 278 | stat.executeUpdate("create table foo (col integer);"); 279 | stat.executeUpdate( 280 | "create trigger foo_trigger after insert on foo begin" 281 | + " select func(new.rowid, new.col); end;"); 282 | int times = 1000; 283 | List threads = new LinkedList(); 284 | for (int tn=0; tn < times; tn++) { 285 | threads.add(new Thread("func thread " + tn) { 286 | public void run() { try { 287 | Statement s = conn.createStatement(); 288 | s.executeUpdate("insert into foo values (1);"); 289 | s.close(); 290 | } catch (SQLException e) { e.printStackTrace(); } } 291 | }); 292 | } 293 | for (Thread thread: threads) thread.start(); 294 | for (Thread thread: threads) thread.join(); 295 | 296 | // check that all of the threads successfully executed 297 | ResultSet rs = stat.executeQuery("select sum(col) from foo;"); 298 | assertTrue(rs.next()); 299 | assertEquals(rs.getInt(1), times); 300 | rs.close(); 301 | 302 | // check that custom function was executed each time 303 | assertEquals(Integer.parseInt(func.toString()), times); 304 | } 305 | 306 | private void assertArrayEq(byte[] a, byte[] b) { 307 | assertNotNull(a); 308 | assertNotNull(b); 309 | assertEquals(a.length, b.length); 310 | for (int i=0; i < a.length; i++) 311 | assertEquals(a[i], b[i]); 312 | } 313 | } 314 | --------------------------------------------------------------------------------