diff --git a/Makefile b/Makefile index df6d0c4bf..c834906d2 100644 --- a/Makefile +++ b/Makefile @@ -48,9 +48,14 @@ jni-header: $(TARGET)/common-lib/NativeDB.h $(TARGET)/common-lib/NativeDB.h: $(TARGET)/common-lib/org/sqlite/core/NativeDB.class $(JAVAH) -classpath $(TARGET)/common-lib -jni -o $@ org.sqlite.core.NativeDB -test: +test: test-natives mvn test +test-natives: + mkdir -p target/test-classes/ + $(CC) -I$(SQLITE_SOURCE) -fPIC -shared -o target/test-classes/libtest.so src/test/c/test.c + $(CC) -I$(SQLITE_SOURCE) -fPIC -shared -o target/test-classes/libtest2.so src/test/c/test2.c + clean: clean-native clean-java clean-tests @@ -110,44 +115,44 @@ $(NATIVE_DLL): $(SQLITE_OUT)/$(LIBNAME) DOCKER_RUN_OPTS=--rm -win32: $(SQLITE_UNPACKED) jni-header +win32: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-windows-x86 -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=i686-w64-mingw32.static- OS_NAME=Windows OS_ARCH=x86' -win64: $(SQLITE_UNPACKED) jni-header +win64: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-windows-x64 -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=x86_64-w64-mingw32.static- OS_NAME=Windows OS_ARCH=x86_64' -linux32: $(SQLITE_UNPACKED) jni-header +linux32: $(SQLITE_UNPACKED) jni-header test-natives docker run $(DOCKER_RUN_OPTS) -ti -v $$PWD:/work xerial/centos5-linux-x86 bash -c 'make clean-native native OS_NAME=Linux OS_ARCH=x86' -linux64: $(SQLITE_UNPACKED) jni-header +linux64: $(SQLITE_UNPACKED) jni-header test-natives docker run $(DOCKER_RUN_OPTS) -ti -v $$PWD:/work xerial/centos5-linux-x86_64 bash -c 'make clean-native native OS_NAME=Linux OS_ARCH=x86_64' -alpine-linux64: $(SQLITE_UNPACKED) jni-header +alpine-linux64: $(SQLITE_UNPACKED) jni-header test-natives docker run $(DOCKER_RUN_OPTS) -ti -v $$PWD:/work xerial/alpine-linux-x86_64 bash -c 'make clean-native native OS_NAME=Linux OS_ARCH=x86_64' -linux-arm: $(SQLITE_UNPACKED) jni-header +linux-arm: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-armv5 -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=arm-linux-gnueabi- OS_NAME=Linux OS_ARCH=arm' -linux-armv6: $(SQLITE_UNPACKED) jni-header +linux-armv6: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-armv6 -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=arm-linux-gnueabihf- OS_NAME=Linux OS_ARCH=armv6' -linux-armv7: $(SQLITE_UNPACKED) jni-header +linux-armv7: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-armv7 -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=arm-linux-gnueabihf- OS_NAME=Linux OS_ARCH=armv7' -linux-arm64: $(SQLITE_UNPACKED) jni-header +linux-arm64: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-arm64 -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=/usr/bin/aarch64-unknown-linux-gnueabi/bin/aarch64-unknown-linux-gnueabi- OS_NAME=Linux OS_ARCH=aarch64' -linux-android-arm: $(SQLITE_UNPACKED) jni-header +linux-android-arm: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-android-arm -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=/usr/arm-linux-androideabi/bin/arm-linux-androideabi- OS_NAME=Linux OS_ARCH=android-arm' -linux-ppc64: $(SQLITE_UNPACKED) jni-header +linux-ppc64: $(SQLITE_UNPACKED) jni-header test-natives ./docker/dockcross-ppc64 -a $(DOCKER_RUN_OPTS) bash -c 'make clean-native native CROSS_PREFIX=powerpc64le-linux-gnu- OS_NAME=Linux OS_ARCH=ppc64' -mac64: $(SQLITE_UNPACKED) jni-header +mac64: $(SQLITE_UNPACKED) jni-header test-natives docker run -it $(DOCKER_RUN_OPTS) -v $$PWD:/workdir -e CROSS_TRIPLE=x86_64-apple-darwin multiarch/crossbuild make clean-native native OS_NAME=Mac OS_ARCH=x86_64 # deprecated -mac32: $(SQLITE_UNPACKED) jni-header +mac32: $(SQLITE_UNPACKED) jni-header test-natives docker run -it $(DOCKER_RUN_OPTS) -v $$PWD:/workdir -e CROSS_TRIPLE=i386-apple-darwin multiarch/crossbuild make clean-native native OS_NAME=Mac OS_ARCH=x86 sparcv9: @@ -166,6 +171,7 @@ clean-java: clean-tests: rm -rf $(TARGET)/{surefire*,testdb.jar*} + rm -rf target/test-classes docker-linux64: docker build -f docker/Dockerfile.linux_x86_64 -t xerial/centos5-linux-x86_64 . diff --git a/src/main/java/org/sqlite/ExtensionInfo.java b/src/main/java/org/sqlite/ExtensionInfo.java new file mode 100644 index 000000000..70b5bec13 --- /dev/null +++ b/src/main/java/org/sqlite/ExtensionInfo.java @@ -0,0 +1,178 @@ +package org.sqlite; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.sqlite.util.StringUtils; + +/** + * File and entry point needed to load a SQLite extension. + * + * Also provides static methods to serialize and deserialize a + * collection of ExtensionInfos into a String. + * + * @see https://sqlite.org/loadext.html#loading_an_extension + * @see https://sqlite.org/lang_corefunc.html#load_extension + * @see https://sqlite.org/c3ref/load_extension.html + * @author Andy-2639 + */ +public class ExtensionInfo { + + private static final char SERIALIZE_ESCAPE_CHAR = '|'; + private static final char SERIALIZE_EXTENSION_INFO_SEPARATOR = '*'; + private static final char SERIALIZE_FILE_ENTRY_SEPARATOR = '"'; + + /** Must not be null. */ + private final String file; + /** May be null. */ + private final String entry; + + /* + * ::= ( SERIALIZE_EXTENSION_INFO_SEPARATOR )* + * ::= [ SERIALIZE_FILE_ENTRY_SEPARATOR ] + */ + /** + * @param extensionInfos must not be null. + * @return Serialized {@link ExtensionInfo}s. Never null. + */ + static String serialize(Collection extensionInfos) { + final char[] serializeSpecialChars = new char[] { + SERIALIZE_EXTENSION_INFO_SEPARATOR, + SERIALIZE_FILE_ENTRY_SEPARATOR + }; + StringBuilder builder = new StringBuilder(); + for (ExtensionInfo ei : extensionInfos) { + builder.append(StringUtils.escape(ei.getFile(), SERIALIZE_ESCAPE_CHAR, serializeSpecialChars)); + if (ei.getEntry() != null) { + builder.append(SERIALIZE_FILE_ENTRY_SEPARATOR); + builder.append(StringUtils.escape(ei.getEntry(), SERIALIZE_ESCAPE_CHAR, serializeSpecialChars)); + } + builder.append(SERIALIZE_EXTENSION_INFO_SEPARATOR); + } + return builder.toString(); + } + + /** + * @param extensionInfosSerialized must not be null. + * @return Deserialized {@link EntensionInfo}s. Never null. + */ + static Set deserialize(String extensionInfosSerialized) { + Set eis = new HashSet(); + StringBuilder file = new StringBuilder(); + StringBuilder entry = new StringBuilder(); + boolean parseFile = true; + boolean esc = false; + for (int i = 0; i < extensionInfosSerialized.length(); i++) { + char ch = extensionInfosSerialized.charAt(i); + if (esc) { + if (parseFile) { + file.append(ch); + } else { + entry.append(ch); + } + esc = false; + } else if (parseFile) { + assert (entry.length() == 0); + switch (ch) { + case SERIALIZE_ESCAPE_CHAR: + esc = true; + break; + case SERIALIZE_EXTENSION_INFO_SEPARATOR: + eis.add(new ExtensionInfo(file.toString(), null)); + file.setLength(0); + break; + case SERIALIZE_FILE_ENTRY_SEPARATOR: + parseFile = false; + break; + default: + file.append(ch); + break; + } + } else { + switch (ch) { + case SERIALIZE_ESCAPE_CHAR: + esc = true; + break; + case SERIALIZE_EXTENSION_INFO_SEPARATOR: + eis.add(new ExtensionInfo(file.toString(), entry.toString())); + file.setLength(0); + entry.setLength(0); + parseFile = true; + break; + case SERIALIZE_FILE_ENTRY_SEPARATOR: + throw new IllegalStateException("Unexpected SERIALIZE_FILE_ENTRY_SEPARATOR: " + extensionInfosSerialized); + default: + entry.append(ch); + break; + } + } + } + // assert last char was unescaped SERIALIZE_EXTENSION_INFO_SEPARATOR + assert (!esc && parseFile && (file.length() == 0)); + //System.err.println(extensionInfosSerialized); + //for (ExtensionInfo ei : eis) { + // System.err.println("file: " + ei.getFile() + " - entry: " + ei.getEntry()); + //} + return eis; + } + + /** + * @param file native library containing the SQLite extension. Must not be null. + * @param entry if null, SQLite determines the entry point. + * + * @see https://sqlite.org/lang_corefunc.html#load_extension + * @see https://sqlite.org/c3ref/load_extension.html + */ + ExtensionInfo(String file, String entry) { + if (file == null) { + throw new NullPointerException("file must not be null"); + } + this.file = file; + this.entry = entry; + } + + /** + * @return file native library containing the SQLite extension. Never null. + */ + public String getFile() { + return file; + } + + /** + * Entry point of the extension. + * + * @return may be null. + */ + public String getEntry() { + return entry; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + file.hashCode(); + result = prime * result + ((entry == null) ? 0 : entry.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || (getClass() != obj.getClass())) { + return false; + } + ExtensionInfo other = (ExtensionInfo)obj; + if (!file.equals(other.file)) { + return false; + } + if ((entry == null) != (other.entry == null)) { + return false; + } + if ((entry != null) && !(entry.equals(other.entry))) { + return false; + } + return true; + } + +} diff --git a/src/main/java/org/sqlite/SQLiteConfig.java b/src/main/java/org/sqlite/SQLiteConfig.java index b783bb7b8..705ba1ce5 100755 --- a/src/main/java/org/sqlite/SQLiteConfig.java +++ b/src/main/java/org/sqlite/SQLiteConfig.java @@ -54,6 +54,9 @@ public class SQLiteConfig private final int busyTimeout; + /** @author Andy-2639 */ + private final Set loadExtensions = new HashSet(); + private final SQLiteConnectionConfig defaultConnectionConfig; /** @@ -87,6 +90,11 @@ public SQLiteConfig(Properties prop) { this.busyTimeout = Integer.parseInt(pragmaTable.getProperty(Pragma.BUSY_TIMEOUT.pragmaName, "3000")); this.defaultConnectionConfig = SQLiteConnectionConfig.fromPragmaTable(pragmaTable); + + String loadExtsString = pragmaTable.getProperty(Pragma.LOAD_EXTENSIONS.pragmaName); + if (loadExtsString != null) { + loadExtensions.addAll(ExtensionInfo.deserialize(loadExtsString)); + } } public SQLiteConnectionConfig newConnectionConfig() @@ -94,6 +102,18 @@ public SQLiteConnectionConfig newConnectionConfig() return defaultConnectionConfig.copyConfig(); } + public void loadExtension(String file, String entry) { + loadExtensions.add(new ExtensionInfo(file, entry)); + } + + public void loadExtension(String file) { + this.loadExtension(file, null); + } + + public Set getLoadExtensions() { + return loadExtensions; + } + /** * Create a new JDBC connection using the current configuration * @return The connection. @@ -123,6 +143,7 @@ public void apply(Connection conn) throws SQLException { pragmaParams.remove(Pragma.DATE_STRING_FORMAT.pragmaName); pragmaParams.remove(Pragma.PASSWORD.pragmaName); pragmaParams.remove(Pragma.HEXKEY_MODE.pragmaName); + pragmaParams.remove(Pragma.LOAD_EXTENSIONS.pragmaName); Statement stat = conn.createStatement(); try { @@ -235,6 +256,14 @@ public Properties toProperties() { pragmaTable.setProperty(Pragma.DATE_PRECISION.pragmaName, defaultConnectionConfig.getDatePrecision().getValue()); pragmaTable.setProperty(Pragma.DATE_STRING_FORMAT.pragmaName, defaultConnectionConfig.getDateStringFormat()); + if (loadExtensions.isEmpty()) { + pragmaTable.remove(Pragma.LOAD_EXTENSIONS.pragmaName); + } else { + pragmaTable.setProperty( + Pragma.LOAD_EXTENSIONS.pragmaName, + ExtensionInfo.serialize(loadExtensions)); + } + return pragmaTable; } @@ -309,7 +338,10 @@ public static enum Pragma { DATE_STRING_FORMAT("date_string_format", "Format to store and retrieve dates stored as text. Defaults to \"yyyy-MM-dd HH:mm:ss.SSS\"", null), BUSY_TIMEOUT("busy_timeout", null), HEXKEY_MODE("hexkey_mode", toStringArray(HexKeyMode.values())), - PASSWORD("password", null); + PASSWORD("password", null), + + LOAD_EXTENSIONS("load_extensions"), + ; public final String pragmaName; public final String[] choices; diff --git a/src/main/java/org/sqlite/SQLiteConnection.java b/src/main/java/org/sqlite/SQLiteConnection.java index 2efa393b8..11d14e982 100644 --- a/src/main/java/org/sqlite/SQLiteConnection.java +++ b/src/main/java/org/sqlite/SQLiteConnection.java @@ -17,6 +17,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; +import java.util.Set; import java.util.concurrent.Executor; /** @@ -241,6 +242,17 @@ private static DB open(String url, String origFileName, Properties props) throws throw err; } db.open(fileName, config.getOpenModeFlags()); + + Set loadExtensions = config.getLoadExtensions(); + if (!loadExtensions.isEmpty()) { + db.dbconfig_enable_load_extension(true); + for (ExtensionInfo ei : loadExtensions) { + db.load_extension(ei.getFile(), ei.getEntry()); + } + db.dbconfig_enable_load_extension(false); + } + db.enable_load_extension(config.isEnabledLoadExtension()); + return db; } diff --git a/src/main/java/org/sqlite/core/DB.java b/src/main/java/org/sqlite/core/DB.java index cef887210..0393ac971 100644 --- a/src/main/java/org/sqlite/core/DB.java +++ b/src/main/java/org/sqlite/core/DB.java @@ -148,11 +148,42 @@ public SQLiteConfig getConfig() { public abstract int shared_cache(boolean enable) throws SQLException; /** - * Enables or disables loading of SQLite extensions. + * Enables (SQLite C-API only) / Disables (C-API and SQL load_extension function) + * the loading of SQLite extensions. + * + * @param enable + * @return success code. + * @see https://sqlite.org/c3ref/load_extension.html + * @see https://sqlite.org/c3ref/db_config.html + * @see https://sqlite.org/c3ref/c_dbconfig_enable_fkey.html SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION + */ + public abstract int dbconfig_enable_load_extension(boolean enable) throws SQLException; + + /** + * Loads the specified extension via the SQLite C-API. + * + *

+ * Loading of extensions has to be enabled: + *

    + *
  1. {@link #dbconfig_enable_load_extension(boolean)} (preferred)
  2. + *
  3. {@link #enable_load_extension(boolean)}
  4. + *
+ *

+ * + * @param file native library containing the SQLite extension. Must not be null. + * @param entry if null, SQLite determines the entry point. + * @throws SQLException + * @see https://sqlite.org/c3ref/load_extension.html + */ + public abstract void load_extension(String file, String entry) throws SQLException; + + /** + * Enables or disables loading of SQLite extensions (SQLite C-API and SQL function load_extension). * @param enable True to enable; false otherwise. * @return Result Codes * @throws SQLException * @see http://www.sqlite.org/c3ref/load_extension.html + * @see https://sqlite.org/c3ref/enable_load_extension.html */ public abstract int enable_load_extension(boolean enable) throws SQLException; diff --git a/src/main/java/org/sqlite/core/NativeDB.c b/src/main/java/org/sqlite/core/NativeDB.c index d05186da5..078746632 100644 --- a/src/main/java/org/sqlite/core/NativeDB.c +++ b/src/main/java/org/sqlite/core/NativeDB.c @@ -445,6 +445,51 @@ JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_shared_1cache( } +JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_dbconfig_1enable_1load_1extension + (JNIEnv *env, jobject this, jboolean enable) +{ + sqlite3 *db = gethandle(env, this); + if (!db) + { + throwex_db_closed(env); + return SQLITE_MISUSE; + } + return sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable ? 1 : 0, NULL); +} + + +JNIEXPORT void JNICALL Java_org_sqlite_core_NativeDB_load_1extension_1utf8 + (JNIEnv *env, jobject this, jbyteArray file, jbyteArray entry) +{ + sqlite3 *db = gethandle(env, this); + if (!db) + { + throwex_db_closed(env); + } + + char *file_bytes; + utf8JavaByteArrayToUtf8Bytes(env, file, &file_bytes, NULL); + if (!file_bytes) + { + return; + } + + char *entry_bytes; // may be null to let SQLite determine entry name + utf8JavaByteArrayToUtf8Bytes(env, entry, &entry_bytes, NULL); + + char *error_msg; + int result = sqlite3_load_extension(db, file_bytes, entry_bytes, &error_msg); + if (result != SQLITE_OK) + { + throwex_msg(env, error_msg); + } + sqlite3_free(error_msg); + + freeUtf8Bytes(entry_bytes); + freeUtf8Bytes(file_bytes); +} + + JNIEXPORT jint JNICALL Java_org_sqlite_core_NativeDB_enable_1load_1extension( JNIEnv *env, jobject this, jboolean enable) { diff --git a/src/main/java/org/sqlite/core/NativeDB.java b/src/main/java/org/sqlite/core/NativeDB.java index d686ba9ff..66b14623e 100644 --- a/src/main/java/org/sqlite/core/NativeDB.java +++ b/src/main/java/org/sqlite/core/NativeDB.java @@ -102,6 +102,23 @@ public synchronized int _exec(String sql) throws SQLException { @Override public native synchronized int shared_cache(boolean enable); + /** + * @see org.sqlite.core.B#dbconfig_enable_load_extension(boolean) + */ + @Override + public native synchronized int dbconfig_enable_load_extension(boolean enable) throws SQLException; + + /** + * @see org.sqlite.core.DB#load_extension(String, String) + */ + @Override + public void load_extension(String file, String entry) throws SQLException { + this.load_extension_utf8(NativeDB.stringToUtf8ByteArray(file), + NativeDB.stringToUtf8ByteArray(entry)); + } + + private native synchronized void load_extension_utf8(byte[] file, byte[] entry) throws SQLException; + /** * @see org.sqlite.core.DB#enable_load_extension(boolean) */ diff --git a/src/main/java/org/sqlite/util/StringUtils.java b/src/main/java/org/sqlite/util/StringUtils.java index ca6abde79..06466e20b 100644 --- a/src/main/java/org/sqlite/util/StringUtils.java +++ b/src/main/java/org/sqlite/util/StringUtils.java @@ -16,4 +16,42 @@ public static String join(List list, String separator) { } return sb.toString(); } + + /** + * @param needle + * @param haystack must NOT be null. + * @return + * @author Andy-2639 + */ + public static boolean inArray(char needle, char... haystack) { + for (int i = 0; i < haystack.length; i++) { + if (needle == haystack[i]) { + return true; + } + } + return false; + } + + /** + * @param s must NOT be null. + * @param esc + * @param specials must NOT be null. + * @return never null. + * @author Andy-2639 + */ + public static String escape(String s, char esc, char... specials) { + StringBuilder sb = new StringBuilder(2 * s.length()); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if ((ch == esc) || (StringUtils.inArray(ch, specials))) { + sb.append(esc); + } + sb.append(ch); + } + if (s.length() == sb.length()) { + return s; + } else { + return sb.toString(); + } + } } diff --git a/src/main/resources/org/sqlite/native/Linux/aarch64/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/aarch64/libsqlitejdbc.so index d5be6dd14..caa25f31e 100755 Binary files a/src/main/resources/org/sqlite/native/Linux/aarch64/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/aarch64/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Linux/android-arm/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/android-arm/libsqlitejdbc.so index a0a349467..c29884518 100755 Binary files a/src/main/resources/org/sqlite/native/Linux/android-arm/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/android-arm/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Linux/arm/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/arm/libsqlitejdbc.so index 4c88289cd..d8084fd2e 100755 Binary files a/src/main/resources/org/sqlite/native/Linux/arm/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/arm/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Linux/armv6/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/armv6/libsqlitejdbc.so index 115b86b6f..77d05c4de 100755 Binary files a/src/main/resources/org/sqlite/native/Linux/armv6/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/armv6/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Linux/armv7/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/armv7/libsqlitejdbc.so index 70a842a63..c9b4ba3af 100755 Binary files a/src/main/resources/org/sqlite/native/Linux/armv7/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/armv7/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Linux/ppc64/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/ppc64/libsqlitejdbc.so index 4ad134b3a..0c433b854 100755 Binary files a/src/main/resources/org/sqlite/native/Linux/ppc64/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/ppc64/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Linux/x86/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/x86/libsqlitejdbc.so index 5798d8645..f08999a48 100644 Binary files a/src/main/resources/org/sqlite/native/Linux/x86/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/x86/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Linux/x86_64/libsqlitejdbc.so b/src/main/resources/org/sqlite/native/Linux/x86_64/libsqlitejdbc.so index 6a51dc15d..a496ba355 100644 Binary files a/src/main/resources/org/sqlite/native/Linux/x86_64/libsqlitejdbc.so and b/src/main/resources/org/sqlite/native/Linux/x86_64/libsqlitejdbc.so differ diff --git a/src/main/resources/org/sqlite/native/Mac/x86_64/libsqlitejdbc.jnilib b/src/main/resources/org/sqlite/native/Mac/x86_64/libsqlitejdbc.jnilib index 5eaa716e3..d323c6501 100755 Binary files a/src/main/resources/org/sqlite/native/Mac/x86_64/libsqlitejdbc.jnilib and b/src/main/resources/org/sqlite/native/Mac/x86_64/libsqlitejdbc.jnilib differ diff --git a/src/main/resources/org/sqlite/native/Windows/x86/sqlitejdbc.dll b/src/main/resources/org/sqlite/native/Windows/x86/sqlitejdbc.dll index 3939d2114..b3f368596 100755 Binary files a/src/main/resources/org/sqlite/native/Windows/x86/sqlitejdbc.dll and b/src/main/resources/org/sqlite/native/Windows/x86/sqlitejdbc.dll differ diff --git a/src/main/resources/org/sqlite/native/Windows/x86_64/sqlitejdbc.dll b/src/main/resources/org/sqlite/native/Windows/x86_64/sqlitejdbc.dll index 687d284af..5a82f84ec 100755 Binary files a/src/main/resources/org/sqlite/native/Windows/x86_64/sqlitejdbc.dll and b/src/main/resources/org/sqlite/native/Windows/x86_64/sqlitejdbc.dll differ diff --git a/src/test/c/test.c b/src/test/c/test.c new file mode 100644 index 000000000..50fdd7139 --- /dev/null +++ b/src/test/c/test.c @@ -0,0 +1,46 @@ +#ifndef SQLITE_CORE + #include "sqlite3ext.h" + SQLITE_EXTENSION_INIT1 +#else + #include "sqlite3.h" +#endif + +static void test( + sqlite3_context *context, + int argc, + sqlite3_value **argv) +{ + sqlite3_result_int(context, 1); +} + +#if !SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_test_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi) +{ + SQLITE_EXTENSION_INIT2(pApi) + return sqlite3_create_function( + db, "test", 0, SQLITE_ANY, (void*)0, + test, 0, 0); +} +#endif + +#if !SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_testa_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi) +{ + SQLITE_EXTENSION_INIT2(pApi) + return sqlite3_create_function( + db, "testa", 0, SQLITE_ANY, (void*)0, + test, 0, 0); +} +#endif diff --git a/src/test/c/test2.c b/src/test/c/test2.c new file mode 100644 index 000000000..507472e14 --- /dev/null +++ b/src/test/c/test2.c @@ -0,0 +1,30 @@ +#ifndef SQLITE_CORE + #include "sqlite3ext.h" + SQLITE_EXTENSION_INIT1 +#else + #include "sqlite3.h" +#endif + +static void test2( + sqlite3_context *context, + int argc, + sqlite3_value **argv) +{ + sqlite3_result_int(context, 1); +} + +#if !SQLITE_CORE +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_test_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi) +{ + SQLITE_EXTENSION_INIT2(pApi) + return sqlite3_create_function( + db, "test2", 0, SQLITE_ANY, (void*)0, + test2, 0, 0); +} +#endif diff --git a/src/test/java/org/sqlite/AllTests.java b/src/test/java/org/sqlite/AllTests.java index 9e3ad4584..6ab88117b 100644 --- a/src/test/java/org/sqlite/AllTests.java +++ b/src/test/java/org/sqlite/AllTests.java @@ -29,7 +29,8 @@ UDFTest.class, JSON1Test.class, ProgressHandlerTest.class, - BusyHandlerTest.class + BusyHandlerTest.class, + LoadExtensionTest.class, }) public class AllTests { diff --git a/src/test/java/org/sqlite/LoadExtensionTest.java b/src/test/java/org/sqlite/LoadExtensionTest.java new file mode 100644 index 000000000..50d946527 --- /dev/null +++ b/src/test/java/org/sqlite/LoadExtensionTest.java @@ -0,0 +1,207 @@ +package org.sqlite; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +import java.io.File; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * @author Andy-2639 + */ +public class LoadExtensionTest { + + public static final String LIBTEST = "target/test-classes/libtest.so"; + public static final String LIBTEST2 = "target/test-classes/libtest2.so"; + + @BeforeClass + public static void beforeClass() { + assumeTrue((new File(LIBTEST)).exists()); + assumeTrue((new File(LIBTEST2)).exists()); + } + + public SQLException execute(Connection conn, String sql) throws SQLException { + Statement stmt = null; + try { + stmt = conn.createStatement(); + try { + stmt.execute(sql); + return null; + } catch (SQLException se) { + return se; + } + } finally { + if (stmt != null) { + stmt.close(); + stmt = null; + } + } + } + + @Test + public void testFunctionsNotDefined() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + conn = config.createConnection("jdbc:sqlite:"); + assertNotNull(this.execute(conn, "SELECT test()")); + assertNotNull(this.execute(conn, "SELECT testa()")); + assertNotNull(this.execute(conn, "SELECT test2()")); + assertNotNull(this.execute(conn, "SELECT test3()")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadSqlNotAllowed() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + conn = config.createConnection("jdbc:sqlite:"); + assertNotNull(this.execute(conn, "SELECT load_extension('" + LIBTEST + "');")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadSqlAllowed() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + config.enableLoadExtension(true); + conn = config.createConnection("jdbc:sqlite:"); + assertNull(this.execute(conn, "SELECT load_extension('" + LIBTEST + "');")); + assertNull(this.execute(conn, "SELECT test();")); + assertNotNull(this.execute(conn, "SELECT testa();")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadSqlAllowed2() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + config.enableLoadExtension(true); + conn = config.createConnection("jdbc:sqlite:"); + assertNull(this.execute(conn, "SELECT load_extension('" + LIBTEST + "', 'sqlite3_testa_init');")); + assertNull(this.execute(conn, "SELECT testa();")); + assertNotNull(this.execute(conn, "SELECT test();")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadCApi() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + config.loadExtension(LIBTEST); + conn = config.createConnection("jdbc:sqlite:"); + assertNull(this.execute(conn, "SELECT test();")); + assertNotNull(this.execute(conn, "SELECT testa();")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadCApi2() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + config.loadExtension(LIBTEST, "sqlite3_testa_init"); + conn = config.createConnection("jdbc:sqlite:"); + assertNull(this.execute(conn, "SELECT testa();")); + assertNotNull(this.execute(conn, "SELECT test();")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadCApiForbiddenSql() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + config.loadExtension(LIBTEST); + conn = config.createConnection("jdbc:sqlite:"); + assertNull(this.execute(conn, "SELECT test();")); + assertNotNull(this.execute(conn, "SELECT load_extension('" + LIBTEST2 + "');")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadCApiSql() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + config.enableLoadExtension(true); + config.loadExtension(LIBTEST); + conn = config.createConnection("jdbc:sqlite:"); + assertNull(this.execute(conn, "SELECT test();")); + assertNull(this.execute(conn, "SELECT load_extension('" + LIBTEST2 + "');")); + assertNull(this.execute(conn, "SELECT test2();")); + assertNotNull(this.execute(conn, "SELECT test3();")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + + @Test + public void testLoadCApiMultiple() throws SQLException { + Connection conn = null; + try { + SQLiteConfig config = new SQLiteConfig(); + config.loadExtension(LIBTEST); + config.loadExtension(LIBTEST, "sqlite3_testa_init"); + config.loadExtension(LIBTEST2); + conn = config.createConnection("jdbc:sqlite:"); + assertNull(this.execute(conn, "SELECT test();")); + assertNull(this.execute(conn, "SELECT testa();")); + assertNull(this.execute(conn, "SELECT test2();")); + assertNotNull(this.execute(conn, "SELECT test3();")); + } finally { + if (conn != null) { + conn.close(); + conn = null; + } + } + } + +}