/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
import android.database.CursorWindow;
import android.os.SystemClock;
import android.util.Log;
/**
* A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
* This class is used by SQLiteCursor and isn't useful itself.
*
* SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple
* threads should perform its own synchronization when using the SQLiteQuery.
*/
public class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "Cursor";
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex;
/** Args to bind on requery */
private String[] mBindArgs;
private boolean mClosed = false;
private static final int SQLITE_TEXT = 0;
private static final int SQLITE_INTEGER = 1;
private static final int SQLITE_FLOAT = 2;
private static final int SQLITE_BLOB = 3;
private static final int SQLITE_NULL = 4;
private int column_count = 0;
/**
* @j2sNative
* this.datas = null;
* this.keys = null;
* this.types = null;
*/{}
/**
* Create a persistent query object.
*
* @param db The database that this query object is associated with
* @param query The SQL string for this query.
* @param offsetIndex The 1-based index to the OFFSET parameter,
*/
/* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
super(db, query);
mOffsetIndex = offsetIndex;
mBindArgs = bindArgs;
}
/**
* Reads rows into a buffer. This method acquires the database lock.
*
* @param window The window to fill into
* @return number of total rows in the query
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
long timeStart = SystemClock.uptimeMillis();
// mDatabase.lock();
// mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
try {
acquireReference();
try {
window.acquireReference();
// if the start pos is not equal to 0, then most likely window is
// too small for the data set, loading by another thread
// is not safe in this situation. the native code will ignore maxRead
int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
maxRead, lastPos);
return numRows;
} catch (IllegalStateException e){
// simply ignore it
return 0;
} catch (SQLiteDatabaseCorruptException e) {
// mDatabase.onCorruption();
throw e;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
// mDatabase.unlock();
}
}
/**
* Get the column count for the statement. Only valid on query based
* statements. The database must be locked
* when calling this method.
*
* @return The number of column in the statement's result set.
*/
/* package */ int columnCountLocked() {
acquireReference();
try {
return native_column_count();
} finally {
releaseReference();
}
}
/**
* Retrieves the column name for the given column index. The database must be locked
* when calling this method.
*
* @param columnIndex the index of the column to get the name for
* @return The requested column's name
*/
/* package */ String columnNameLocked(int columnIndex) {
acquireReference();
try {
return native_column_name(columnIndex);
} finally {
releaseReference();
}
}
@Override
public String toString() {
return "SQLiteQuery: " + mSql;
}
@Override
public void close() {
super.close();
mClosed = true;
}
/**
* Called by SQLiteCursor when it is requeried.
*/
/* package */ void requery() {
query();
if (mBindArgs != null) {
int len = mBindArgs.length;
try {
for (int i = 0; i < len; i++) {
super.bindString(i + 1, mBindArgs[i]);
}
} catch (SQLiteMisuseException e) {
StringBuilder errMsg = new StringBuilder("mSql " + mSql);
for (int i = 0; i < len; i++) {
errMsg.append(" ");
errMsg.append(mBindArgs[i]);
}
errMsg.append(" ");
IllegalStateException leakProgram = new IllegalStateException(
errMsg.toString(), e);
throw leakProgram;
}
}
}
@Override
public void bindNull(int index) {
mBindArgs[index - 1] = null;
if (!mClosed) super.bindNull(index);
}
@Override
public void bindLong(int index, long value) {
mBindArgs[index - 1] = Long.toString(value);
if (!mClosed) super.bindLong(index, value);
}
@Override
public void bindDouble(int index, double value) {
mBindArgs[index - 1] = Double.toString(value);
if (!mClosed) super.bindDouble(index, value);
}
@Override
public void bindString(int index, String value) {
mBindArgs[index - 1] = value;
if (!mClosed) super.bindString(index, value);
}
//TQI3:Add method to get the mSql and mBindArgs
public String[] getBindArgs()
{
return mBindArgs;
}
public String getmSql()
{
return mSql;
}
private final int native_fill_window(CursorWindow javaWindow,
int startPos, int offsetParam, int maxRead, int lastPos) {
int err = 0;
// sqlite3_stmt * statement = GET_STATEMENT(env, object);
int numRows = lastPos;
maxRead += lastPos;
int numColumns;
// int retryCount;
// int boundParams;
CursorWindow window;
query();
// if (statement == NULL) {
// LOGE("Invalid statement in fillWindow()");
// jniThrowException(env, "java/lang/IllegalStateException",
// "Attempting to access a deactivated, closed, or empty cursor");
// return 0;
// }
// Only do the binding if there is a valid offsetParam. If no binding needs to be done
// offsetParam will be set to 0, an invliad value.
// if(offsetParam > 0) {
// // Bind the offset parameter, telling the program which row to start with
// err = sqlite3_bind_int(statement, offsetParam, startPos);
// if (err != SQLITE_OK) {
// LOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
// jniThrowException(env, "java/lang/IllegalArgumentException",
// sqlite3_errmsg(GET_HANDLE(env, object)));
// return 0;
// }
// } else {
// }
window = javaWindow;
/**
* @j2sNative
* if (this.datas) {
* window.QueryResult = this.datas;
* window.rowNum = this.datas.length;
* }
*/{}
// Get the native window
// window = get_window_from_object(env, javaWindow);
// if (null == window) {
// return 0;
// }
numColumns = column_count;
if (!window.setNumColumns(numColumns)) {
return 0;
}
// retryCount = 0;
if (startPos > 0) {
// int num = skip_rows(statement, startPos);
// if (num < 0) {
// throw_sqlite3_exception(env, GET_HANDLE(env, object));
// return 0;
// } else if (num < startPos) {
// LOGE("startPos %d > actual rows %d", startPos, num);
// return num;
// }
}
while(startPos != 0 || numRows < maxRead) {
// err = sqlite3_step(statement);
/**
* @j2sNative
* if (this.datas && this.datas.length > 0) {
* if (numRows < this.datas.length) {
* err = 1;
* } else {
* err = 2;
* }
* } else {
* err = 3;
* }
*/{}
if (err == 1) {//SQLITE_ROW
// Log.i(TAG, "\nStepped statement %p to row %d", statement, startPos + numRows);
// retryCount = 0;
// Allocate a new field directory for the row. This pointer is not reused
// since it mey be possible for it to be relocated on a call to alloc() when
// the field data is being allocated.
// {
// field_slot_t * fieldDir = window->allocRow();
// if (!fieldDir) {
// LOGE("Failed allocating fieldDir at startPos %d row %d", startPos, numRows);
// return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
// }
// }
// Pack the row into the window
int i;
/**
* @j2sNative
* if (this.types == null) {
* numRows++;
* continue;
* }
*/{}
for (i = 0; i < numColumns; i++) {
// int type = sqlite3_column_type(statement, i);
int type = -1;
/**
* @j2sNative
* var type_value = this.types[numRows][i].value;
* if (type_value == 'text') {
* type = 0;//TEXT
* } else if (type_value == 'integer') {
* type = 1;//INTEGER
* } else if (type_value == 'real') {
* type = 2;//FLOAT
* } else if (type_value == 'blob') {
* type = 3;//BLOB
* } else if (type_value == 'null') {
* type = 4;//NULL
* }
*/{}
if (type == SQLITE_TEXT) {
// TEXT data
// #if WINDOW_STORAGE_UTF8
// uint8_t const * text = (uint8_t const *)sqlite3_column_text(statement, i);
// SQLite does not include the NULL terminator in size, but does
// ensure all strings are NULL terminated, so increase size by
// one to make sure we store the terminator.
// size_t size = sqlite3_column_bytes(statement, i) + 1;
// #else
// uint8_t const * text = (uint8_t const *)sqlite3_column_text16(statement, i);
// size_t size = sqlite3_column_bytes16(statement, i);
// #endif
// int offset = window->alloc(size);
// if (!offset) {
// window->freeLastRow();
// return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
// }
// window->copyIn(offset, text, size);
// This must be updated after the call to alloc(), since that
// may move the field around in the window
// field_slot_t * fieldSlot = window->getFieldSlot(numRows, i);
// fieldSlot->type = FIELD_TYPE_STRING;
// fieldSlot->data.buffer.offset = offset;
// fieldSlot->data.buffer.size = size;
// Log.i(TAG,"%d,%d is TEXT with %u bytes", startPos + numRows, i, size);
String value;
/**
* @j2sNative
* value = this.datas[numRows][i].value;
* window.putString(value, numRows, i);
*/{}
} else if (type == SQLITE_INTEGER) {
// INTEGER data
// int64_t value = sqlite3_column_int64(statement, i);
int value;
/**
* @j2sNative
* value = this.datas[numRows][i].value;
* window.putLong(value, numRows, i);
*/{}
// if (!window.putLong(value, numRows, i)) {
// window.freeLastRow();
// return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
// }
} else if (type == SQLITE_FLOAT) {
// FLOAT data
// double value = sqlite3_column_double(statement, i);
double value;
/**
* @j2sNative
* value = this.datas[numRows][i].value;
* window.putDouble(value, numRows, i);
*/{}
// if(!window.putDouble(value, numRows, i)) {
// window.freeLastRow();
// return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
// }
} else if (type == SQLITE_BLOB) {
// BLOB data
// uint8_t const * blob = (uint8_t const *)sqlite3_column_blob(statement, i);
// size_t size = sqlite3_column_bytes16(statement, i);
// int offset = window->alloc(size);
// if (!offset) {
// window->freeLastRow();
// return startPos + numRows + finish_program_and_get_row_count(statement) + 1;
// }
// window->copyIn(offset, blob, size);
// This must be updated after the call to alloc(), since that
// may move the field around in the window
// field_slot_t * fieldSlot = window->getFieldSlot(numRows, i);
// fieldSlot->type = FIELD_TYPE_BLOB;
// fieldSlot->data.buffer.offset = offset;
// fieldSlot->data.buffer.size = size;
// Log.i(TAG, "%d,%d is Blob with %u bytes @ %d", startPos + numRows, i, size, offset);
} else if (type == SQLITE_NULL) {
// NULL field
window.putNull(numRows, i);
} else {
// Unknown data
//throw new SQLiteException("Unknown column type when filling window");
break;
}
}
if (i < numColumns) {
// Not all the fields fit in the window
// Unknown data error happened
break;
}
// Mark the row as complete in the window
numRows++;
} else if (err == 2) {//SQLITE_DONE
// All rows processed, bail
Log.i(TAG, "Processed all rows");
break;
// } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
// The table is locked, retry
// Log.i(TAG, "Database locked, retrying");
// if (retryCount > 50) {
// LOGE("Bailing on database busy rety");
// break;
// }
// Sleep to give the thread holding the lock a chance to finish
// usleep(1000);
// retryCount++;
// continue;
} else {
// throw new SQLiteException(env, GET_HANDLE(env, object));
break;
}
}
// if (err == SQLITE_ROW) {
// return -1;
// } else {
// //sqlite3_reset(statement);
// return startPos + numRows;
// }
return startPos + numRows;
}
public void query () {
String tableName = null;
String sql = getmSql();
String[] BindArgs = getBindArgs();
if (BindArgs != null) {
for (int i=0; i<BindArgs.length; i++) {
sql = sql.replaceFirst("\\?", "'" + BindArgs[i] + "'");
}
}
/**
* @j2sNative
* try {
* var key = new Array();
* var db = this.mDatabase.getSQLDB();
* var data = db.exec(sql);
* if(!data||data.length==0) {
* var DISTINCT_index = sql.indexOf("DISTINCT");
* var FROM_index = sql.indexOf("FROM");
* if(DISTINCT_index >= 0) {
* var temp_columns = sql.substring(DISTINCT_index+8, FROM_index-1);
* var sql_left = sql.slice(FROM_index+5);
* if(sql_left.indexOf(" ")>=0) {
* var sql_left_parts = sql_left.split(" ");
* tableName = sql_left_parts[0];
* } else {
* tableName = sql_left;
* }
* } else {
* var temp_columns = sql.substring(7, FROM_index-1);
* var sql_left = sql.slice(FROM_index+5);
* if(sql_left.indexOf(" ")>=0) {
* var sql_left_parts = sql_left.split(" ");
* tableName = sql_left_parts[0];
* } else {
* tableName = sql_left;
* }
* }
* if(temp_columns.indexOf("*")<0) {
* this.keys = temp_columns.split(", ");
* } else {
* this.keys = this.getColumnFromTable(tableName);
* }
* this.column_count = this.keys.length;
* this.datas = null;
* this.types = null;
* } else {
* var typeSql = "SELECT ";
* for (var i=0;i<data[0].length;i++) {
* key[i] = data[0][i].column;
* if (i != data[0].length-1) {
* typeSql += " typeof(" + key[i] + "), ";
* } else {
* typeSql += " typeof(" + key[i] + ") ";
* }
* }
* var FROM_index = sql.indexOf("FROM");
* typeSql += sql.substring(FROM_index);
* try {
* this.types = db.exec(typeSql);
* } catch (e) {
* this.types = null;
* }
* this.column_count = key.length;
* this.datas = data;
* this.keys = key;
* }
* } catch (e) {
* throw(e.message);
* this.column_count = null;
* }
*/{}
}
private final int native_column_count() {
query();
return column_count;
}
public String[] getColumnFromTable(String tableName) {
String q = "SELECT sql FROM sqlite_master WHERE tbl_name = '" + tableName + "'";
/**
* @j2sNative
* try {
* var db = this.mDatabase.getSQLDB();
* var data = db.exec(q);
* var sql_create = data[0][0].value;
* var position1 = sql_create.indexOf("(");
* var position2 = sql_create.lastIndexOf(")");
* var sql_key = sql_create.substring(position1+1, position2);
* var arr = new Array();
* var column = new Array();
* var flag = 0;
* var count = 0;
* var pre = 0;
* var length = sql_key.length;
* var i = 0;
* for(i=0;i<length;i++) {
* if(flag==0&&sql_key.charAt(i)==",") {
* arr[count] = sql_key.substring(pre,i);
* pre = i + 1;
* count++;
* }
* if(sql_key.charAt(i)=="(")
* flag++;
* if(sql_key.charAt(i)==")")
* flag--;
* }
* arr[count] = sql_key.slice(pre);
* i = 0;
* var length_column = 0;
* while(i<=count) {
* while(arr[i].charAt(0) == " ")
* arr[i] = arr[i].slice(1);
* var temp_sql = arr[i].split(" ");
* if(temp_sql[0]!="PRIMARY KEY"&&temp_sql[0]!="UNIQUE"&&temp_sql[0]!="CHECK"&&temp_sql[0]!="DEFAUL"&&temp_sql[0]!="COLLATE") {
* column[length_column] = temp_sql[0];
* length_column++;
* }
* i++;
* }
* return column;
* //Notice! There might be a Bug **** mColumns and column get the real values ****
* } catch (e) {
* throw(e.message);
* return null;
* }
*/{return null;}
}
private final String native_column_name(int columnIndex) {
/**
* @j2sNative
* for (var i=0; i<this.keys.length; i++) {
* return this.keys[columnIndex];
* }
*/{return null;}
}
}