/* * Copyright (c) 2007 David Crawshaw * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package org.sqlite; import java.io.PrintWriter; import java.sql.SQLException; import org.ibex.nestedvm.Runtime; // FEATURE: strdup is wasteful, SQLite interface will take unterminated char* /** Communicates with the Java version of SQLite provided by NestedVM. */ final class NestedDB extends DB implements Runtime.CallJavaCB { /** database pointer */ int handle = 0; /** sqlite binary embedded in nestedvm */ private Runtime rt = null; /** user defined functions referenced by position (stored in used data) */ private Function[] functions = null; private String[] funcNames = null; // WRAPPER FUNCTIONS //////////////////////////////////////////// @Override protected synchronized void _open(String filename, int openFlags) throws SQLException { if (handle != 0) throw new SQLException("DB already open"); // handle silly windows drive letter mapping if (filename.length() > 2) { char drive = Character.toLowerCase(filename.charAt(0)); if (filename.charAt(1) == ':' && drive >= 'a' && drive <= 'z') { // convert to nestedvm's "/c:/file" format filename = filename.substring(2); filename = filename.replace('\\', '/'); filename = "/" + drive + ":" + filename; } } // start the nestedvm runtime try { rt = (Runtime) Class.forName("org.sqlite.SQLite").newInstance(); rt.start(); } catch (Exception e) { throw new CausedSQLException(e); } // callback for user defined functions rt.setCallJavaCB(this); // open the db and retrieve sqlite3_db* pointer int passback = rt.xmalloc(4); int str = rt.strdup(filename); // if (call("sqlite3_open_v2", str, openFlags, 0, passback) != SQLITE_OK) if (call("sqlite3_open_v2", str, passback, openFlags, 0) != SQLITE_OK) throwex(); handle = deref(passback); rt.free(str); rt.free(passback); } /* callback for Runtime.CallJavaCB above */ public int call(int xType, int context, int args, int value) { xUDF(xType, context, args, value); return 0; } @Override protected synchronized void _close() throws SQLException { if (handle == 0) return; try { if (call("sqlite3_close", handle) != SQLITE_OK) throwex(); } finally { handle = 0; rt.stop(); rt = null; } } @Override int shared_cache(boolean enable) throws SQLException { // The shared cache is per-process, so it is useless as // each nested connection is its own process. return -1; } @Override int enable_load_extension(boolean enable) throws SQLException { // TODO enable_load_extension is not supported in pure-java mode //return call("sqlite3_enable_load_extension", handle, enable ? 1 : 0); return 1; } @Override synchronized void interrupt() throws SQLException { call("sqlite3_interrupt", handle); } @Override synchronized void busy_timeout(int ms) throws SQLException { call("sqlite3_busy_timeout", handle, ms); } @Override protected synchronized long prepare(String sql) throws SQLException { int passback = rt.xmalloc(4); int str = rt.strdup(sql); int ret = call("sqlite3_prepare_v2", handle, str, -1, passback, 0); rt.free(str); if (ret != SQLITE_OK) { rt.free(passback); throwex(ret); } int pointer = deref(passback); rt.free(passback); return pointer; } @Override synchronized String errmsg() throws SQLException { return cstring(call("sqlite3_errmsg", handle)); } @Override synchronized String libversion() throws SQLException { return cstring(call("sqlite3_libversion", handle)); } @Override synchronized int changes() throws SQLException { return call("sqlite3_changes", handle); } @Override protected synchronized int _exec(String sql) throws SQLException { if (rt == null) throw DB.newSQLException(SQLiteErrorCode.SQLITE_MISUSE.code, "attempt to use the closed conection"); int passback = rt.xmalloc(4); int str = rt.strdup(sql); int status = call("sqlite3_exec", handle, str, 0, 0, passback); if (status != SQLITE_OK) { String errorMessage = cstring(passback); call("sqlite3_free", deref(passback)); rt.free(passback); throwex(status, errorMessage); } rt.free(passback); return status; } @Override protected synchronized int finalize(long stmt) throws SQLException { return call("sqlite3_finalize", (int) stmt); } @Override protected synchronized int step(long stmt) throws SQLException { return call("sqlite3_step", (int) stmt); } @Override protected synchronized int reset(long stmt) throws SQLException { return call("sqlite3_reset", (int) stmt); } @Override synchronized int clear_bindings(long stmt) throws SQLException { return call("sqlite3_clear_bindings", (int) stmt); } @Override synchronized int bind_parameter_count(long stmt) throws SQLException { return call("sqlite3_bind_parameter_count", (int) stmt); } @Override synchronized int column_count(long stmt) throws SQLException { return call("sqlite3_column_count", (int) stmt); } @Override synchronized int column_type(long stmt, int col) throws SQLException { return call("sqlite3_column_type", (int) stmt, col); } @Override synchronized String column_name(long stmt, int col) throws SQLException { return utfstring(call("sqlite3_column_name", (int) stmt, col)); } @Override synchronized String column_text(long stmt, int col) throws SQLException { return utfstring(call("sqlite3_column_text", (int) stmt, col)); } @Override synchronized byte[] column_blob(long stmt, int col) throws SQLException { int addr = call("sqlite3_column_blob", (int) stmt, col); if (addr == 0) return null; byte[] blob = new byte[call("sqlite3_column_bytes", (int) stmt, col)]; copyin(addr, blob, blob.length); return blob; } @Override synchronized double column_double(long stmt, int col) throws SQLException { try { return Double.parseDouble(column_text(stmt, col)); } catch (NumberFormatException e) { return Double.NaN; } // TODO } @Override synchronized long column_long(long stmt, int col) throws SQLException { try { return Long.parseLong(column_text(stmt, col)); } catch (NumberFormatException e) { return 0; } // TODO } @Override synchronized int column_int(long stmt, int col) throws SQLException { return call("sqlite3_column_int", (int) stmt, col); } @Override synchronized String column_decltype(long stmt, int col) throws SQLException { return utfstring(call("sqlite3_column_decltype", (int) stmt, col)); } @Override synchronized String column_table_name(long stmt, int col) throws SQLException { return utfstring(call("sqlite3_column_table_name", (int) stmt, col)); } @Override synchronized int bind_null(long stmt, int pos) throws SQLException { return call("sqlite3_bind_null", (int) stmt, pos); } @Override synchronized int bind_int(long stmt, int pos, int v) throws SQLException { return call("sqlite3_bind_int", (int) stmt, pos, v); } @Override synchronized int bind_long(long stmt, int pos, long v) throws SQLException { return bind_text(stmt, pos, Long.toString(v)); // TODO } @Override synchronized int bind_double(long stmt, int pos, double v) throws SQLException { return bind_text(stmt, pos, Double.toString(v)); // TODO } @Override synchronized int bind_text(long stmt, int pos, String v) throws SQLException { if (v == null) return bind_null(stmt, pos); return call("sqlite3_bind_text", (int) stmt, pos, rt.strdup(v), -1, rt.lookupSymbol("free")); } @Override synchronized int bind_blob(long stmt, int pos, byte[] buf) throws SQLException { if (buf == null || buf.length < 1) return bind_null(stmt, pos); int len = buf.length; int blob = rt.xmalloc(len); // free()ed by sqlite3_bind_blob copyout(buf, blob, len); return call("sqlite3_bind_blob", (int) stmt, pos, blob, len, rt.lookupSymbol("free")); } @Override synchronized void result_null(long cxt) throws SQLException { call("sqlite3_result_null", (int) cxt); } @Override synchronized void result_text(long cxt, String val) throws SQLException { call("sqlite3_result_text", (int) cxt, rt.strdup(val), -1, rt.lookupSymbol("free")); } @Override synchronized void result_blob(long cxt, byte[] val) throws SQLException { if (val == null || val.length == 0) { result_null(cxt); return; } int blob = rt.xmalloc(val.length); copyout(val, blob, val.length); call("sqlite3_result_blob", (int) cxt, blob, val.length, rt.lookupSymbol("free")); } @Override synchronized void result_double(long cxt, double val) throws SQLException { result_text(cxt, Double.toString(val)); } // TODO @Override synchronized void result_long(long cxt, long val) throws SQLException { result_text(cxt, Long.toString(val)); } // TODO @Override synchronized void result_int(long cxt, int val) throws SQLException { call("sqlite3_result_int", (int) cxt, val); } @Override synchronized void result_error(long cxt, String err) throws SQLException { int str = rt.strdup(err); call("sqlite3_result_error", (int) cxt, str, -1); rt.free(str); } @Override synchronized int value_bytes(Function f, int arg) throws SQLException { return call("sqlite3_value_bytes", value(f, arg)); } @Override synchronized String value_text(Function f, int arg) throws SQLException { return utfstring(call("sqlite3_value_text", value(f, arg))); } @Override synchronized byte[] value_blob(Function f, int arg) throws SQLException { int addr = call("sqlite3_value_blob", value(f, arg)); if (addr == 0) return null; byte[] blob = new byte[value_bytes(f, arg)]; copyin(addr, blob, blob.length); return blob; } @Override synchronized double value_double(Function f, int arg) throws SQLException { return Double.parseDouble(value_text(f, arg)); // TODO } @Override synchronized long value_long(Function f, int arg) throws SQLException { return Long.parseLong(value_text(f, arg)); // TODO } @Override synchronized int value_int(Function f, int arg) throws SQLException { return call("sqlite3_value_int", value(f, arg)); } @Override synchronized int value_type(Function f, int arg) throws SQLException { return call("sqlite3_value_type", value(f, arg)); } private int value(Function f, int arg) throws SQLException { return deref((int) f.value + (arg * 4)); } @Override synchronized int create_function(String name, Function func) throws SQLException { if (functions == null) { functions = new Function[10]; funcNames = new String[10]; } // find a position int pos; for (pos = 0; pos < functions.length; pos++) if (functions[pos] == null) break; if (pos == functions.length) { // expand function arrays Function[] fnew = new Function[functions.length * 2]; String[] nnew = new String[funcNames.length * 2]; System.arraycopy(functions, 0, fnew, 0, functions.length); System.arraycopy(funcNames, 0, nnew, 0, funcNames.length); functions = fnew; funcNames = nnew; } // register function functions[pos] = func; funcNames[pos] = name; int rc; int str = rt.strdup(name); rc = call("create_function_helper", handle, str, pos, func instanceof Function.Aggregate ? 1 : 0); rt.free(str); return rc; } @Override synchronized int destroy_function(String name) throws SQLException { if (name == null) return 0; // find function position number int pos; for (pos = 0; pos < funcNames.length; pos++) if (name.equals(funcNames[pos])) break; if (pos == funcNames.length) return 0; functions[pos] = null; funcNames[pos] = null; // deregister function int rc; int str = rt.strdup(name); rc = call("create_function_helper", handle, str, -1, 0); rt.free(str); return rc; } /* unused as we use the user_data pointer to store a single word */ @Override synchronized void free_functions() {} /** Callback used by xFunc (1), xStep (2) and xFinal (3). */ synchronized void xUDF(int xType, int context, int args, int value) { Function func = null; try { int pos = call("sqlite3_user_data", context); func = functions[pos]; if (func == null) throw new SQLException("function state inconsistent"); func.context = context; func.value = value; func.args = args; switch (xType) { case 1: func.xFunc(); break; case 2: ((Function.Aggregate) func).xStep(); break; case 3: ((Function.Aggregate) func).xFinal(); break; } } catch (SQLException e) { try { String err = e.toString(); if (err == null) err = "unknown error"; int str = rt.strdup(err); call("sqlite3_result_error", context, str, -1); rt.free(str); } catch (SQLException exp) { exp.printStackTrace();//TODO } } finally { if (func != null) { func.context = 0; func.value = 0; func.args = 0; } } } /** Calls support function found in upstream/sqlite-metadata.patch */ @Override synchronized boolean[][] column_metadata(long stmt) throws SQLException { int colCount = call("sqlite3_column_count", (int) stmt); boolean[][] meta = new boolean[colCount][3]; int pass; pass = rt.xmalloc(12); // struct metadata for (int i = 0; i < colCount; i++) { call("column_metadata_helper", handle, (int) stmt, i, pass); meta[i][0] = deref(pass) == 1; meta[i][1] = deref(pass + 4) == 1; meta[i][2] = deref(pass + 8) == 1; } rt.free(pass); return meta; } @Override int backup(String dbName, String destFileName, ProgressObserver observer) throws SQLException { throw new SQLException("backup command is not supported in pure-java mode"); } @Override int restore(String dbName, String sourceFileName, ProgressObserver observer) throws SQLException { throw new SQLException("restore command is not supported in pure-java mode"); } // HELPER FUNCTIONS ///////////////////////////////////////////// /** safe to reuse parameter arrays as all functions are syncrhonized */ private final int[] p0 = new int[] {}, p1 = new int[] { 0 }, p2 = new int[] { 0, 0 }, p3 = new int[] { 0, 0, 0 }, p4 = new int[] { 0, 0, 0, 0 }, p5 = new int[] { 0, 0, 0, 0, 0 }; private int call(String addr, int a0) throws SQLException { p1[0] = a0; return call(addr, p1); } private int call(String addr, int a0, int a1) throws SQLException { p2[0] = a0; p2[1] = a1; return call(addr, p2); } private int call(String addr, int a0, int a1, int a2) throws SQLException { p3[0] = a0; p3[1] = a1; p3[2] = a2; return call(addr, p3); } private int call(String addr, int a0, int a1, int a2, int a3) throws SQLException { p4[0] = a0; p4[1] = a1; p4[2] = a2; p4[3] = a3; return call(addr, p4); } private int call(String addr, int a0, int a1, int a2, int a3, int a4) throws SQLException { p5[0] = a0; p5[1] = a1; p5[2] = a2; p5[3] = a3; p5[4] = a4; return call(addr, p5); } private int call(String func, int[] args) throws SQLException { try { return rt.call(func, args); } catch (Runtime.CallException e) { throw new CausedSQLException(e); } } /** Dereferences a pointer, returning the word it points to. */ private int deref(int pointer) throws SQLException { try { return rt.memRead(pointer); } catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e); } } private String utfstring(int str) throws SQLException { try { return rt.utfstring(str); } catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e); } } private String cstring(int str) throws SQLException { try { return rt.cstring(str); } catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e); } } private void copyin(int addr, byte[] buf, int count) throws SQLException { try { rt.copyin(addr, buf, count); } catch (Runtime.ReadFaultException e) { throw new CausedSQLException(e); } } private void copyout(byte[] buf, int addr, int count) throws SQLException { try { rt.copyout(buf, addr, count); } catch (Runtime.FaultException e) { throw new CausedSQLException(e); } } /** Maps any exception onto an SQLException. */ private static final class CausedSQLException extends SQLException { private final Exception cause; CausedSQLException(Exception e) { if (e == null) throw new RuntimeException("null exception cause"); cause = e; } @Override public Throwable getCause() { return cause; } @Override public void printStackTrace() { cause.printStackTrace(); } @Override public void printStackTrace(PrintWriter s) { cause.printStackTrace(s); } @Override public Throwable fillInStackTrace() { return cause.fillInStackTrace(); } @Override public StackTraceElement[] getStackTrace() { return cause.getStackTrace(); } @Override public String getMessage() { return cause.getMessage(); } } }