Sqlite-Plugin, DB-Copy plugin added und integriert

This commit is contained in:
2016-01-15 00:59:23 +01:00
parent 337215938a
commit 7c683d7450
243 changed files with 308639 additions and 253 deletions

View File

@@ -0,0 +1,509 @@
/*
* Copyright (c) 2012-2015: Christopher J. Brody (aka Chris Brody)
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
*/
package io.liteglue;
import android.annotation.SuppressLint;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.util.Base64;
import android.util.Log;
import java.io.File;
import java.lang.IllegalArgumentException;
import java.lang.Number;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Android Database helper class
*/
class SQLiteAndroidDatabase
{
private static final Pattern FIRST_WORD = Pattern.compile("^\\s*(\\S+)",
Pattern.CASE_INSENSITIVE);
private static final Pattern WHERE_CLAUSE = Pattern.compile("\\s+WHERE\\s+(.+)$",
Pattern.CASE_INSENSITIVE);
private static final Pattern UPDATE_TABLE_NAME = Pattern.compile("^\\s*UPDATE\\s+(\\S+)",
Pattern.CASE_INSENSITIVE);
private static final Pattern DELETE_TABLE_NAME = Pattern.compile("^\\s*DELETE\\s+FROM\\s+(\\S+)",
Pattern.CASE_INSENSITIVE);
File dbFile;
SQLiteDatabase mydb;
/**
* NOTE: Using default constructor, no explicit constructor.
*/
/**
* Open a database.
*
* @param dbfile The database File specification
*/
void open(File dbfile) throws Exception {
dbFile = dbfile; // for possible bug workaround
mydb = SQLiteDatabase.openOrCreateDatabase(dbfile, null);
}
/**
* Close a database (in the current thread).
*/
void closeDatabaseNow() {
if (mydb != null) {
mydb.close();
mydb = null;
}
}
void bugWorkaround() throws Exception {
this.closeDatabaseNow();
this.open(dbFile);
}
/**
* Executes a batch request and sends the results via cbc.
*
* @param dbname The name of the database.
* @param queryarr Array of query strings
* @param jsonparams Array of JSON query parameters
* @param queryIDs Array of query ids
* @param cbc Callback context from Cordova API
*/
@SuppressLint("NewApi")
void executeSqlBatch(String[] queryarr, JSONArray[] jsonparams,
String[] queryIDs, CallbackContext cbc) {
if (mydb == null) {
// not allowed - can only happen if someone has closed (and possibly deleted) a database and then re-used the database
cbc.error("database has been closed");
return;
}
String query = "";
String query_id = "";
int len = queryarr.length;
JSONArray batchResults = new JSONArray();
for (int i = 0; i < len; i++) {
int rowsAffectedCompat = 0;
boolean needRowsAffectedCompat = false;
query_id = queryIDs[i];
JSONObject queryResult = null;
String errorMessage = "unknown";
try {
boolean needRawQuery = true;
query = queryarr[i];
QueryType queryType = getQueryType(query);
if (queryType == QueryType.update || queryType == queryType.delete) {
if (android.os.Build.VERSION.SDK_INT >= 11) {
SQLiteStatement myStatement = mydb.compileStatement(query);
if (jsonparams != null) {
bindArgsToStatement(myStatement, jsonparams[i]);
}
int rowsAffected = -1; // (assuming invalid)
// Use try & catch just in case android.os.Build.VERSION.SDK_INT >= 11 is lying:
try {
rowsAffected = myStatement.executeUpdateDelete();
// Indicate valid results:
needRawQuery = false;
} catch (SQLiteException ex) {
// Indicate problem & stop this query:
ex.printStackTrace();
errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLiteStatement.executeUpdateDelete(): Error=" + errorMessage);
needRawQuery = false;
} catch (Exception ex) {
// Assuming SDK_INT was lying & method not found:
// do nothing here & try again with raw query.
}
if (rowsAffected != -1) {
queryResult = new JSONObject();
queryResult.put("rowsAffected", rowsAffected);
}
} else { // pre-honeycomb
rowsAffectedCompat = countRowsAffectedCompat(queryType, query, jsonparams, mydb, i);
needRowsAffectedCompat = true;
}
}
// INSERT:
if (queryType == QueryType.insert && jsonparams != null) {
needRawQuery = false;
SQLiteStatement myStatement = mydb.compileStatement(query);
bindArgsToStatement(myStatement, jsonparams[i]);
long insertId = -1; // (invalid)
try {
insertId = myStatement.executeInsert();
// statement has finished with no constraint violation:
queryResult = new JSONObject();
if (insertId != -1) {
queryResult.put("insertId", insertId);
queryResult.put("rowsAffected", 1);
} else {
queryResult.put("rowsAffected", 0);
}
} catch (SQLiteException ex) {
// report error result with the error message
// could be constraint violation or some other error
ex.printStackTrace();
errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLiteDatabase.executeInsert(): Error=" + errorMessage);
}
}
if (queryType == QueryType.begin) {
needRawQuery = false;
try {
mydb.beginTransaction();
queryResult = new JSONObject();
queryResult.put("rowsAffected", 0);
} catch (SQLiteException ex) {
ex.printStackTrace();
errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLiteDatabase.beginTransaction(): Error=" + errorMessage);
}
}
if (queryType == QueryType.commit) {
needRawQuery = false;
try {
mydb.setTransactionSuccessful();
mydb.endTransaction();
queryResult = new JSONObject();
queryResult.put("rowsAffected", 0);
} catch (SQLiteException ex) {
ex.printStackTrace();
errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLiteDatabase.setTransactionSuccessful/endTransaction(): Error=" + errorMessage);
}
}
if (queryType == QueryType.rollback) {
needRawQuery = false;
try {
mydb.endTransaction();
queryResult = new JSONObject();
queryResult.put("rowsAffected", 0);
} catch (SQLiteException ex) {
ex.printStackTrace();
errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLiteDatabase.endTransaction(): Error=" + errorMessage);
}
}
// raw query for other statements:
if (needRawQuery) {
queryResult = this.executeSqlStatementQuery(mydb, query, jsonparams[i], cbc);
if (needRowsAffectedCompat) {
queryResult.put("rowsAffected", rowsAffectedCompat);
}
}
} catch (Exception ex) {
ex.printStackTrace();
errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLiteAndroidDatabase.executeSql[Batch](): Error=" + errorMessage);
}
try {
if (queryResult != null) {
JSONObject r = new JSONObject();
r.put("qid", query_id);
r.put("type", "success");
r.put("result", queryResult);
batchResults.put(r);
} else {
JSONObject r = new JSONObject();
r.put("qid", query_id);
r.put("type", "error");
JSONObject er = new JSONObject();
er.put("message", errorMessage);
r.put("result", er);
batchResults.put(r);
}
} catch (JSONException ex) {
ex.printStackTrace();
Log.v("executeSqlBatch", "SQLiteAndroidDatabase.executeSql[Batch](): Error=" + ex.getMessage());
// TODO what to do?
}
}
cbc.success(batchResults);
}
private int countRowsAffectedCompat(QueryType queryType, String query, JSONArray[] jsonparams,
SQLiteDatabase mydb, int i) throws JSONException {
// quick and dirty way to calculate the rowsAffected in pre-Honeycomb. just do a SELECT
// beforehand using the same WHERE clause. might not be perfect, but it's better than nothing
Matcher whereMatcher = WHERE_CLAUSE.matcher(query);
String where = "";
int pos = 0;
while (whereMatcher.find(pos)) {
where = " WHERE " + whereMatcher.group(1);
pos = whereMatcher.start(1);
}
// WHERE clause may be omitted, and also be sure to find the last one,
// e.g. for cases where there's a subquery
// bindings may be in the update clause, so only take the last n
int numQuestionMarks = 0;
for (int j = 0; j < where.length(); j++) {
if (where.charAt(j) == '?') {
numQuestionMarks++;
}
}
JSONArray subParams = null;
if (jsonparams != null) {
// only take the last n of every array of sqlArgs
JSONArray origArray = jsonparams[i];
subParams = new JSONArray();
int startPos = origArray.length() - numQuestionMarks;
for (int j = startPos; j < origArray.length(); j++) {
subParams.put(j - startPos, origArray.get(j));
}
}
if (queryType == QueryType.update) {
Matcher tableMatcher = UPDATE_TABLE_NAME.matcher(query);
if (tableMatcher.find()) {
String table = tableMatcher.group(1);
try {
SQLiteStatement statement = mydb.compileStatement(
"SELECT count(*) FROM " + table + where);
if (subParams != null) {
bindArgsToStatement(statement, subParams);
}
return (int)statement.simpleQueryForLong();
} catch (Exception e) {
// assume we couldn't count for whatever reason, keep going
Log.e(SQLiteAndroidDatabase.class.getSimpleName(), "uncaught", e);
}
}
} else { // delete
Matcher tableMatcher = DELETE_TABLE_NAME.matcher(query);
if (tableMatcher.find()) {
String table = tableMatcher.group(1);
try {
SQLiteStatement statement = mydb.compileStatement(
"SELECT count(*) FROM " + table + where);
bindArgsToStatement(statement, subParams);
return (int)statement.simpleQueryForLong();
} catch (Exception e) {
// assume we couldn't count for whatever reason, keep going
Log.e(SQLiteAndroidDatabase.class.getSimpleName(), "uncaught", e);
}
}
}
return 0;
}
private void bindArgsToStatement(SQLiteStatement myStatement, JSONArray sqlArgs) throws JSONException {
for (int i = 0; i < sqlArgs.length(); i++) {
if (sqlArgs.get(i) instanceof Float || sqlArgs.get(i) instanceof Double) {
myStatement.bindDouble(i + 1, sqlArgs.getDouble(i));
} else if (sqlArgs.get(i) instanceof Number) {
myStatement.bindLong(i + 1, sqlArgs.getLong(i));
} else if (sqlArgs.isNull(i)) {
myStatement.bindNull(i + 1);
} else {
myStatement.bindString(i + 1, sqlArgs.getString(i));
}
}
}
/**
* Get rows results from query cursor.
*
* @param cur Cursor into query results
* @return results in string form
*/
private JSONObject executeSqlStatementQuery(SQLiteDatabase mydb,
String query, JSONArray paramsAsJson,
CallbackContext cbc) throws Exception {
JSONObject rowsResult = new JSONObject();
Cursor cur = null;
try {
String[] params = null;
params = new String[paramsAsJson.length()];
for (int j = 0; j < paramsAsJson.length(); j++) {
if (paramsAsJson.isNull(j))
params[j] = "";
else
params[j] = paramsAsJson.getString(j);
}
cur = mydb.rawQuery(query, params);
} catch (Exception ex) {
ex.printStackTrace();
String errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLiteAndroidDatabase.executeSql[Batch](): Error=" + errorMessage);
throw ex;
}
// If query result has rows
if (cur != null && cur.moveToFirst()) {
JSONArray rowsArrayResult = new JSONArray();
String key = "";
int colCount = cur.getColumnCount();
// Build up JSON result object for each row
do {
JSONObject row = new JSONObject();
try {
for (int i = 0; i < colCount; ++i) {
key = cur.getColumnName(i);
if (android.os.Build.VERSION.SDK_INT >= 11) {
// Use try & catch just in case android.os.Build.VERSION.SDK_INT >= 11 is lying:
try {
bindPostHoneycomb(row, key, cur, i);
} catch (Exception ex) {
bindPreHoneycomb(row, key, cur, i);
}
} else {
bindPreHoneycomb(row, key, cur, i);
}
}
rowsArrayResult.put(row);
} catch (JSONException e) {
e.printStackTrace();
}
} while (cur.moveToNext());
try {
rowsResult.put("rows", rowsArrayResult);
} catch (JSONException e) {
e.printStackTrace();
}
}
if (cur != null) {
cur.close();
}
return rowsResult;
}
@SuppressLint("NewApi")
private void bindPostHoneycomb(JSONObject row, String key, Cursor cur, int i) throws JSONException {
int curType = cur.getType(i);
switch (curType) {
case Cursor.FIELD_TYPE_NULL:
row.put(key, JSONObject.NULL);
break;
case Cursor.FIELD_TYPE_INTEGER:
row.put(key, cur.getLong(i));
break;
case Cursor.FIELD_TYPE_FLOAT:
row.put(key, cur.getDouble(i));
break;
case Cursor.FIELD_TYPE_BLOB:
row.put(key, new String(Base64.encode(cur.getBlob(i), Base64.DEFAULT)));
break;
case Cursor.FIELD_TYPE_STRING:
default: /* (not expected) */
row.put(key, cur.getString(i));
break;
}
}
private void bindPreHoneycomb(JSONObject row, String key, Cursor cursor, int i) throws JSONException {
// Since cursor.getType() is not available pre-honeycomb, this is
// a workaround so we don't have to bind everything as a string
// Details here: http://stackoverflow.com/q/11658239
SQLiteCursor sqLiteCursor = (SQLiteCursor) cursor;
CursorWindow cursorWindow = sqLiteCursor.getWindow();
int pos = cursor.getPosition();
if (cursorWindow.isNull(pos, i)) {
row.put(key, JSONObject.NULL);
} else if (cursorWindow.isLong(pos, i)) {
row.put(key, cursor.getLong(i));
} else if (cursorWindow.isFloat(pos, i)) {
row.put(key, cursor.getDouble(i));
} else if (cursorWindow.isBlob(pos, i)) {
row.put(key, new String(Base64.encode(cursor.getBlob(i), Base64.DEFAULT)));
} else { // string
row.put(key, cursor.getString(i));
}
}
static QueryType getQueryType(String query) {
Matcher matcher = FIRST_WORD.matcher(query);
if (matcher.find()) {
try {
return QueryType.valueOf(matcher.group(1).toLowerCase());
} catch (IllegalArgumentException ignore) {
// unknown verb
}
}
return QueryType.other;
}
static enum QueryType {
update,
insert,
delete,
select,
begin,
commit,
rollback,
other
}
} /* vim: set expandtab : */

View File

@@ -0,0 +1,660 @@
/*
* Copyright (c) 2012-2015: Christopher J. Brody (aka Chris Brody)
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
*/
package io.liteglue;
import android.annotation.SuppressLint;
import android.util.Base64;
import android.util.Log;
import java.io.File;
import java.lang.IllegalArgumentException;
import java.lang.Number;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
public class SQLitePlugin extends CordovaPlugin {
/**
* Multiple database runner map (static).
* NOTE: no public static accessor to db (runner) map since it would not work with db threading.
* FUTURE put DBRunner into a public class that can provide external accessor.
*/
static ConcurrentHashMap<String, DBRunner> dbrmap = new ConcurrentHashMap<String, DBRunner>();
/**
* SQLiteGlueConnector (instance of SQLiteConnector) for NDK version:
*/
static SQLiteConnector connector = new SQLiteConnector();
/**
* NOTE: Using default constructor, no explicit constructor.
*/
/**
* Executes the request and returns PluginResult.
*
* @param actionAsString The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param cbc Callback context from Cordova API
* @return Whether the action was valid.
*/
@Override
public boolean execute(String actionAsString, JSONArray args, CallbackContext cbc) {
Action action;
try {
action = Action.valueOf(actionAsString);
} catch (IllegalArgumentException e) {
// shouldn't ever happen
Log.e(SQLitePlugin.class.getSimpleName(), "unexpected error", e);
return false;
}
try {
return executeAndPossiblyThrow(action, args, cbc);
} catch (JSONException e) {
// TODO: signal JSON problem to JS
Log.e(SQLitePlugin.class.getSimpleName(), "unexpected error", e);
return false;
}
}
private boolean executeAndPossiblyThrow(Action action, JSONArray args, CallbackContext cbc)
throws JSONException {
boolean status = true;
JSONObject o;
String dbname;
switch (action) {
case open:
o = args.getJSONObject(0);
dbname = o.getString("name");
// open database and start reading its queue
this.startDatabase(dbname, o, cbc);
break;
case close:
o = args.getJSONObject(0);
dbname = o.getString("path");
// put request in the q to close the db
this.closeDatabase(dbname, cbc);
break;
case delete:
o = args.getJSONObject(0);
dbname = o.getString("path");
deleteDatabase(dbname, cbc);
break;
case executeSqlBatch:
case backgroundExecuteSqlBatch:
String[] queries = null;
String[] queryIDs = null;
JSONArray jsonArr = null;
int paramLen = 0;
JSONArray[] jsonparams = null;
JSONObject allargs = args.getJSONObject(0);
JSONObject dbargs = allargs.getJSONObject("dbargs");
dbname = dbargs.getString("dbname");
JSONArray txargs = allargs.getJSONArray("executes");
if (txargs.isNull(0)) {
queries = new String[0];
} else {
int len = txargs.length();
queries = new String[len];
queryIDs = new String[len];
jsonparams = new JSONArray[len];
for (int i = 0; i < len; i++) {
JSONObject a = txargs.getJSONObject(i);
queries[i] = a.getString("sql");
queryIDs[i] = a.getString("qid");
jsonArr = a.getJSONArray("params");
paramLen = jsonArr.length();
jsonparams[i] = jsonArr;
}
}
// put db query in the queue to be executed in the db thread:
DBQuery q = new DBQuery(queries, queryIDs, jsonparams, cbc);
DBRunner r = dbrmap.get(dbname);
if (r != null) {
try {
r.q.put(q);
} catch(Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't add to queue", e);
cbc.error("couldn't add to queue");
}
} else {
cbc.error("database not open");
}
break;
}
return status;
}
/**
* Clean up and close all open databases.
*/
@Override
public void onDestroy() {
while (!dbrmap.isEmpty()) {
String dbname = dbrmap.keySet().iterator().next();
this.closeDatabaseNow(dbname);
DBRunner r = dbrmap.get(dbname);
try {
// stop the db runner thread:
r.q.put(new DBQuery());
} catch(Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't stop db thread", e);
}
dbrmap.remove(dbname);
}
}
// --------------------------------------------------------------------------
// LOCAL METHODS
// --------------------------------------------------------------------------
private void startDatabase(String dbname, JSONObject options, CallbackContext cbc) {
// TODO: is it an issue that we can orphan an existing thread? What should we do here?
// If we re-use the existing DBRunner it might be in the process of closing...
DBRunner r = dbrmap.get(dbname);
// Brody TODO: It may be better to terminate the existing db thread here & start a new one, instead.
if (r != null) {
// don't orphan the existing thread; just re-open the existing database.
// In the worst case it might be in the process of closing, but even that's less serious
// than orphaning the old DBRunner.
cbc.success();
} else {
r = new DBRunner(dbname, options, cbc);
dbrmap.put(dbname, r);
this.cordova.getThreadPool().execute(r);
}
}
/**
* Open a database.
*
* @param dbName The name of the database file
*/
private SQLiteAndroidDatabase openDatabase(String dbname, CallbackContext cbc, boolean old_impl) throws Exception {
try {
// ASSUMPTION: no db (connection/handle) is already stored in the map
// [should be true according to the code in DBRunner.run()]
File dbfile = this.cordova.getActivity().getDatabasePath(dbname);
if (!dbfile.exists()) {
dbfile.getParentFile().mkdirs();
}
Log.v("info", "Open sqlite db: " + dbfile.getAbsolutePath());
SQLiteAndroidDatabase mydb = old_impl ? new SQLiteAndroidDatabase() : new SQLiteDatabaseNDK();
mydb.open(dbfile);
if (cbc != null) // XXX Android locking/closing BUG workaround
cbc.success();
return mydb;
} catch (Exception e) {
if (cbc != null) // XXX Android locking/closing BUG workaround
cbc.error("can't open database " + e);
throw e;
}
}
/**
* Close a database (in another thread).
*
* @param dbName The name of the database file
*/
private void closeDatabase(String dbname, CallbackContext cbc) {
DBRunner r = dbrmap.get(dbname);
if (r != null) {
try {
r.q.put(new DBQuery(false, cbc));
} catch(Exception e) {
if (cbc != null) {
cbc.error("couldn't close database" + e);
}
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't close database", e);
}
} else {
if (cbc != null) {
cbc.success();
}
}
}
/**
* Close a database (in the current thread).
*
* @param dbname The name of the database file
*/
private void closeDatabaseNow(String dbname) {
DBRunner r = dbrmap.get(dbname);
if (r != null) {
SQLiteAndroidDatabase mydb = r.mydb;
if (mydb != null)
mydb.closeDatabaseNow();
}
}
private void deleteDatabase(String dbname, CallbackContext cbc) {
DBRunner r = dbrmap.get(dbname);
if (r != null) {
try {
r.q.put(new DBQuery(true, cbc));
} catch(Exception e) {
if (cbc != null) {
cbc.error("couldn't close database" + e);
}
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't close database", e);
}
} else {
boolean deleteResult = this.deleteDatabaseNow(dbname);
if (deleteResult) {
cbc.success();
} else {
cbc.error("couldn't delete database");
}
}
}
/**
* Delete a database.
*
* @param dbName The name of the database file
*
* @return true if successful or false if an exception was encountered
*/
private boolean deleteDatabaseNow(String dbname) {
File dbfile = this.cordova.getActivity().getDatabasePath(dbname);
try {
return cordova.getActivity().deleteDatabase(dbfile.getAbsolutePath());
} catch (Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't delete database", e);
return false;
}
}
// NOTE: class hierarchy is ugly, done to reduce number of modules for manual installation.
// FUTURE TBD SQLiteDatabaseNDK class belongs in its own module.
class SQLiteDatabaseNDK extends SQLiteAndroidDatabase {
SQLiteConnection mydb;
/**
* Open a database.
*
* @param dbFile The database File specification
*/
@Override
void open(File dbFile) throws Exception {
mydb = connector.newSQLiteConnection(dbFile.getAbsolutePath(),
SQLiteOpenFlags.READWRITE | SQLiteOpenFlags.CREATE);
}
/**
* Close a database (in the current thread).
*/
@Override
void closeDatabaseNow() {
try {
if (mydb != null)
mydb.dispose();
} catch (Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't close database, ignoring", e);
}
}
/**
* Ignore Android bug workaround for NDK version
*/
@Override
void bugWorkaround() { }
/**
* Executes a batch request and sends the results via cbc.
*
* @param dbname The name of the database.
* @param queryarr Array of query strings
* @param jsonparams Array of JSON query parameters
* @param queryIDs Array of query ids
* @param cbc Callback context from Cordova API
*/
@Override
void executeSqlBatch( String[] queryarr, JSONArray[] jsonparams,
String[] queryIDs, CallbackContext cbc) {
if (mydb == null) {
// not allowed - can only happen if someone has closed (and possibly deleted) a database and then re-used the database
cbc.error("database has been closed");
return;
}
int len = queryarr.length;
JSONArray batchResults = new JSONArray();
for (int i = 0; i < len; i++) {
int rowsAffectedCompat = 0;
boolean needRowsAffectedCompat = false;
String query_id = queryIDs[i];
JSONObject queryResult = null;
String errorMessage = "unknown";
try {
String query = queryarr[i];
long lastTotal = mydb.getTotalChanges();
queryResult = this.executeSqlStatementNDK(query, jsonparams[i], cbc);
long newTotal = mydb.getTotalChanges();
long rowsAffected = newTotal - lastTotal;
queryResult.put("rowsAffected", rowsAffected);
if (rowsAffected > 0) {
long insertId = mydb.getLastInsertRowid();
if (insertId > 0) {
queryResult.put("insertId", insertId);
}
}
} catch (Exception ex) {
ex.printStackTrace();
errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLitePlugin.executeSql[Batch](): Error=" + errorMessage);
}
try {
if (queryResult != null) {
JSONObject r = new JSONObject();
r.put("qid", query_id);
r.put("type", "success");
r.put("result", queryResult);
batchResults.put(r);
} else {
JSONObject r = new JSONObject();
r.put("qid", query_id);
r.put("type", "error");
JSONObject er = new JSONObject();
er.put("message", errorMessage);
r.put("result", er);
batchResults.put(r);
}
} catch (JSONException ex) {
ex.printStackTrace();
Log.v("executeSqlBatch", "SQLitePlugin.executeSql[Batch](): Error=" + ex.getMessage());
// TODO what to do?
}
}
cbc.success(batchResults);
}
/**
* Get rows results from query cursor.
*
* @param cur Cursor into query results
* @return results in string form
*/
private JSONObject executeSqlStatementNDK(String query, JSONArray paramsAsJson,
CallbackContext cbc) throws Exception {
JSONObject rowsResult = new JSONObject();
boolean hasRows = false;
SQLiteStatement myStatement = mydb.prepareStatement(query);
try {
String[] params = null;
params = new String[paramsAsJson.length()];
for (int i = 0; i < paramsAsJson.length(); ++i) {
if (paramsAsJson.isNull(i)) {
myStatement.bindNull(i + 1);
} else {
Object p = paramsAsJson.get(i);
if (p instanceof Float || p instanceof Double)
myStatement.bindDouble(i + 1, paramsAsJson.getDouble(i));
else if (p instanceof Number)
myStatement.bindLong(i + 1, paramsAsJson.getLong(i));
else
myStatement.bindTextNativeString(i + 1, paramsAsJson.getString(i));
}
}
hasRows = myStatement.step();
} catch (Exception ex) {
ex.printStackTrace();
String errorMessage = ex.getMessage();
Log.v("executeSqlBatch", "SQLitePlugin.executeSql[Batch](): Error=" + errorMessage);
// cleanup statement and throw the exception:
myStatement.dispose();
throw ex;
}
// If query result has rows
if (hasRows) {
JSONArray rowsArrayResult = new JSONArray();
String key = "";
int colCount = myStatement.getColumnCount();
// Build up JSON result object for each row
do {
JSONObject row = new JSONObject();
try {
for (int i = 0; i < colCount; ++i) {
key = myStatement.getColumnName(i);
switch (myStatement.getColumnType(i)) {
case SQLColumnType.NULL:
row.put(key, JSONObject.NULL);
break;
case SQLColumnType.REAL:
row.put(key, myStatement.getColumnDouble(i));
break;
case SQLColumnType.INTEGER:
row.put(key, myStatement.getColumnLong(i));
break;
case SQLColumnType.BLOB:
case SQLColumnType.TEXT:
default: // (just in case)
row.put(key, myStatement.getColumnTextNativeString(i));
}
}
rowsArrayResult.put(row);
} catch (JSONException e) {
e.printStackTrace();
}
} while (myStatement.step());
try {
rowsResult.put("rows", rowsArrayResult);
} catch (JSONException e) {
e.printStackTrace();
}
}
myStatement.dispose();
return rowsResult;
}
}
private class DBRunner implements Runnable {
final String dbname;
private boolean oldImpl;
private boolean bugWorkaround;
final BlockingQueue<DBQuery> q;
final CallbackContext openCbc;
SQLiteAndroidDatabase mydb;
DBRunner(final String dbname, JSONObject options, CallbackContext cbc) {
this.dbname = dbname;
this.oldImpl = options.has("androidOldDatabaseImplementation");
Log.v(SQLitePlugin.class.getSimpleName(), "Android db implementation: " + (oldImpl ? "OLD" : "default-NDK"));
this.bugWorkaround = this.oldImpl && options.has("androidBugWorkaround");
if (this.bugWorkaround)
Log.v(SQLitePlugin.class.getSimpleName(), "Android db closing/locking workaround applied");
this.q = new LinkedBlockingQueue<DBQuery>();
this.openCbc = cbc;
}
public void run() {
try {
this.mydb = openDatabase(dbname, this.openCbc, this.oldImpl);
} catch (Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "unexpected error, stopping db thread", e);
dbrmap.remove(dbname);
return;
}
DBQuery dbq = null;
try {
dbq = q.take();
while (!dbq.stop) {
mydb.executeSqlBatch(dbq.queries, dbq.jsonparams, dbq.queryIDs, dbq.cbc);
// NOTE: androidLock[Bug]Workaround is not necessary and IGNORED for default-NDK version.
if (this.bugWorkaround && dbq.queries.length == 1 && dbq.queries[0] == "COMMIT")
mydb.bugWorkaround();
dbq = q.take();
}
} catch (Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "unexpected error", e);
}
if (dbq != null && dbq.close) {
try {
closeDatabaseNow(dbname);
dbrmap.remove(dbname); // (should) remove ourself
if (!dbq.delete) {
dbq.cbc.success();
} else {
try {
boolean deleteResult = deleteDatabaseNow(dbname);
if (deleteResult) {
dbq.cbc.success();
} else {
dbq.cbc.error("couldn't delete database");
}
} catch (Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't delete database", e);
dbq.cbc.error("couldn't delete database: " + e);
}
}
} catch (Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "couldn't close database", e);
if (dbq.cbc != null) {
dbq.cbc.error("couldn't close database: " + e);
}
}
}
}
}
private final class DBQuery {
// XXX TODO replace with DBRunner action enum:
final boolean stop;
final boolean close;
final boolean delete;
final String[] queries;
final String[] queryIDs;
final JSONArray[] jsonparams;
final CallbackContext cbc;
DBQuery(String[] myqueries, String[] qids, JSONArray[] params, CallbackContext c) {
this.stop = false;
this.close = false;
this.delete = false;
this.queries = myqueries;
this.queryIDs = qids;
this.jsonparams = params;
this.cbc = c;
}
DBQuery(boolean delete, CallbackContext cbc) {
this.stop = true;
this.close = true;
this.delete = delete;
this.queries = null;
this.queryIDs = null;
this.jsonparams = null;
this.cbc = cbc;
}
// signal the DBRunner thread to stop:
DBQuery() {
this.stop = true;
this.close = false;
this.delete = false;
this.queries = null;
this.queryIDs = null;
this.jsonparams = null;
this.cbc = null;
}
}
private static enum Action {
open,
close,
delete,
executeSqlBatch,
backgroundExecuteSqlBatch,
}
}
/* vim: set expandtab : */

View File

@@ -0,0 +1,63 @@
package me.rahul.plugins.sqlDB;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
private Context myContext;
public DatabaseHelper(Context context) {
super(context, sqlDB.dbname, null, 1);
this.myContext = context;
// TODO Auto-generated constructor stub
}
public void createdatabase(File dbPath) throws IOException {
// Log.d("CordovaLog","Inside CreateDatabase = "+dbPath);
this.getReadableDatabase();
try {
copyDatabase(dbPath);
} catch (IOException e) {
throw new Error(
"Create Database Exception ============================ "
+ e);
}
}
private void copyDatabase(File database) throws IOException {
InputStream myInput = myContext.getAssets().open("www/"+sqlDB.dbname);
OutputStream myOutput = new FileOutputStream(database);
byte[] buffer = new byte[1024];
while ((myInput.read(buffer)) > -1) {
myOutput.write(buffer);
}
myOutput.flush();
myOutput.close();
myInput.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,102 @@
package me.rahul.plugins.sqlDB;
import java.io.File;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
/**
* This class echoes a string called from JavaScript.
*/
public class sqlDB extends CordovaPlugin {
public static String dbname = "dbname";
PluginResult plresult = new PluginResult(PluginResult.Status.NO_RESULT);
@Override
public boolean execute(String action, JSONArray args,
CallbackContext callbackContext) throws JSONException {
if (action.equalsIgnoreCase("copy")) {
this.copyDB(args.getString(0), callbackContext);
return true;
} else if (action.equalsIgnoreCase("remove")) {
String db = args.getString(0);
File path = cordova.getActivity().getDatabasePath(db);
Boolean fileExists = path.exists();
if (fileExists) {
boolean deleted = path.delete();
if (deleted) {
plresult = new PluginResult(PluginResult.Status.OK, deleted);
callbackContext.sendPluginResult(plresult);
} else {
plresult = new PluginResult(PluginResult.Status.ERROR,
deleted);
callbackContext.sendPluginResult(plresult);
}
} else {
plresult = new PluginResult(PluginResult.Status.ERROR,
"File Doesn't Exists");
callbackContext.sendPluginResult(plresult);
}
return true;
} else {
plresult = new PluginResult(PluginResult.Status.INVALID_ACTION);
callbackContext.sendPluginResult(plresult);
return false;
}
}
private void copyDB(String dbName, final CallbackContext callbackContext) {
final File dbpath;
dbname = dbName;
JSONObject error = new JSONObject();
final DatabaseHelper dbhelper = new DatabaseHelper(this.cordova
.getActivity().getApplicationContext());
dbpath = this.cordova.getActivity().getDatabasePath(dbname);
Boolean dbexists = dbpath.exists();
//Log.d("CordovaLog", "DatabasePath = " + dbpath + "&&&& dbname = " + dbname + "&&&&DB Exists =" + dbexists);
if (dbexists) {
try {
error.put("message", "File already exists");
error.put("code", 516);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
plresult = new PluginResult(PluginResult.Status.ERROR, error);
callbackContext.sendPluginResult(plresult);
} else {
cordova.getThreadPool().execute(new Runnable() {
@Override
public void run() {
PluginResult plResult = new PluginResult(
PluginResult.Status.NO_RESULT);
// TODO Auto-generated method stub
try {
dbhelper.createdatabase(dbpath);
plResult = new PluginResult(PluginResult.Status.OK);
callbackContext.sendPluginResult(plResult);
} catch (Exception e) {
plResult = new PluginResult(PluginResult.Status.ERROR,
e.getMessage());
callbackContext.sendPluginResult(plResult);
}
}
});
}
}
}