Most efficient way to access/write data to SQLite on Android - android

Currently I'm using ContentProvider in my application. Because of "layers" and no actual need for provider - I'm working on optimizing data access as much as possible. Here is my attempt to do this:
public static String getPreferenceString(Context context, String key)
{
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getReadableDatabase();
SQLiteStatement statement = database.compileStatement("SELECT Value FROM Preferences WHERE Key='" + key + "' LIMIT 1");
try
{
return statement.simpleQueryForString();
}
catch (Exception ex)
{
return "";
}
finally
{
statement.close();
database.close();
helper.close();
}
}
public static void setPreferenceString(Context context, String key, String value)
{
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getReadableDatabase();
SQLiteStatement statement = database.compileStatement("INSERT OR REPLACE INTO Preferences (Key, UpdatedOn, Value) VALUES ('" +
key + "', '" +
Utility.getDateConvertedToUTCDBString(new Date()) + "', '" +
value + "'); ");
try
{
statement.execute();
}
finally
{
statement.close();
database.close();
helper.close();
}
}
Is that about as close as I can get to direct calls to SQLite?
Should I have all this .close() statements in my code?
In setPreferenceString I did copy/paste and called getReadableDatabase even though I write data and it works. Why?

Is that about as close as I can get to direct calls to SQLite?
AFAIK SQL queries are closest you can go against RDBs
Should I have all this .close() statements in my code?
Personally, I would not create a DatabaseHelper, an SQLiteDatabase, and an SQLiteStatement each time I call that method. I would create all this just before you need them, and close them when no needed anymore. Also centralizing this is a good idea IMHO (using a singleton, for example).
Also your SQL statement could be written like
SELECT Value FROM Preferences WHERE Key= ? LIMIT 1
This way you only have to prepare it once and bind parameters as you need the statement. Same goes for any SQL query.

Related

android: how do I replace SQLite execSQL() to avoid injection attack?

I have a method in my MainActivity resetSortIndexes that runs a save() in the model class that runs an SQLite database "execSQL()" method. Now I've read that I should not be using execSQL() to avoid SQL injection attacks and that I should not be using rawQuery() for any INSERT operation. So should I use ContentValues() and insert()?
MainActivity.java
...
public static void resetSortIndexes() {
int index = allList.size();
for (ListItem s : allList) {
s.setSortorder(index);
s.save(sqLiteDB);
index--;
}
}
ListItem.java
...
public void save(SQLiteDB helper){
String sql = "INSERT OR REPLACE INTO " + TABLE_NAME + "(_id,type,typecolor,todo,note1,note2," +
"duedatentime,timestamp,notiftime,notiftime2,randint,sortorder,listone,listtwo," +
"listthree,listfour,listfive,listsix,listseven,listeight,listnine,listten,listeleven," +
"listtwelve,listthirteen,listfourteen,listfifteen,listsixteen,listseventeen," +
"listeighteen,listnineteen,listtwenty) VALUES" +
"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
// The object parameters from the ListItem class.
Object[] params = new Object[]{_id,_type,_typecolor,_todo,_note1,_note2,_duedatentime,
_timestamp,_notiftime,_notiftime2,_randint,_sortorder,_listone,_listtwo,
_listthree,_listfour,_listfive,_listsix,_listseven,_listeight,_listnine,
_listten,_listeleven,_listtwelve,_listthirteen,_listfourteen,_listfifteen,
_listsixteen,_listseventeen,_listeighteen,_listnineteen,_listtwenty};
// A method in the SQLiteDB class.
helper.executeQuery(sql,params);
}
SQLiteDB.java
...
public void executeQuery(String sql, Object[] params) {
SQLiteDatabase db = getReadableDatabase();
db.beginTransaction();
try {
**db.execSQL(sql, params);**
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if(db.isOpen()) {
db.close();
}
}
You can use the method insertWithOnConflict(TABLE_NAME,null,contentvalues,SQLiteDatabase.CONFLICT_REPLACE);
Where contenvalues is a ContenValues populated using it's put(column_name,value) method for each value to be inserted.
The code would be along the lines of :-
ContentValues cv = new Contentvalues();
cv.put("_id",the_id);
cv.put("type",the_type);
..... etc
long result = helper.insertWithOnConflict(TABLE_NAME,null,cv,SQliteDatabase.CONFLICT_REPLACE);
result will be the rowid of the inserted row or -1.
insertWithOnConflict
CONFLICT_REPLACE
P.S. using execSQL as you have, would offer protection from SQL injection as the SQL itself is not subject to user input and the values are bound/passed as arguments.

Android SQLite check if table exist [duplicate]

I have an android app that needs to check if there's already a record in the database, and if not, process some things and eventually insert it, and simply read the data from the database if the data does exist. I'm using a subclass of SQLiteOpenHelper to create and get a rewritable instance of SQLiteDatabase, which I thought automatically took care of creating the table if it didn't already exist (since the code to do that is in the onCreate(...) method).
However, when the table does NOT yet exist, and the first method ran upon the SQLiteDatabase object I have is a call to query(...), my logcat shows an error of "I/Database(26434): sqlite returned: error code = 1, msg = no such table: appdata", and sure enough, the appdata table isn't being created.
Any ideas on why?
I'm looking for either a method to test if the table exists (because if it doesn't, the data's certainly not in it, and I don't need to read it until I write to it, which seems to create the table properly), or a way to make sure that it gets created, and is just empty, in time for that first call to query(...)
EDIT
This was posted after the two answers below:
I think I may have found the problem. I for some reason decided that a different SQLiteOpenHelper was supposed to be created for each table, even though both access the same database file. I think refactoring that code to only use one OpenHelper, and creating both tables inside it's onCreate may work better...
Try this one:
public boolean isTableExists(String tableName, boolean openDb) {
if(openDb) {
if(mDatabase == null || !mDatabase.isOpen()) {
mDatabase = getReadableDatabase();
}
if(!mDatabase.isReadOnly()) {
mDatabase.close();
mDatabase = getReadableDatabase();
}
}
String query = "select DISTINCT tbl_name from sqlite_master where tbl_name = '"+tableName+"'";
try (Cursor cursor = mDatabase.rawQuery(query, null)) {
if(cursor!=null) {
if(cursor.getCount()>0) {
return true;
}
}
return false;
}
}
I know nothing about the Android SQLite API, but if you're able to talk to it in SQL directly, you can do this:
create table if not exists mytable (col1 type, col2 type);
Which will ensure that the table is always created and not throw any errors if it already existed.
Although there are already a lot of good answers to this question, I came up with another solution that I think is more simple. Surround your query with a try block and the following catch:
catch (SQLiteException e){
if (e.getMessage().contains("no such table")){
Log.e(TAG, "Creating table " + TABLE_NAME + "because it doesn't exist!" );
// create table
// re-run query, etc.
}
}
It worked for me!
This is what I did:
/* open database, if doesn't exist, create it */
SQLiteDatabase mDatabase = openOrCreateDatabase("exampleDb.db", SQLiteDatabase.CREATE_IF_NECESSARY,null);
Cursor c = null;
boolean tableExists = false;
/* get cursor on it */
try
{
c = mDatabase.query("tbl_example", null,
null, null, null, null, null);
tableExists = true;
}
catch (Exception e) {
/* fail */
Log.d(TAG, tblNameIn+" doesn't exist :(((");
}
return tableExists;
Yep, turns out the theory in my edit was right: the problem that was causing the onCreate method not to run, was the fact that SQLiteOpenHelper objects should refer to databases, and not have a separate one for each table. Packing both tables into one SQLiteOpenHelper solved the problem.
// #param db, readable database from SQLiteOpenHelper
public boolean doesTableExist(SQLiteDatabase db, String tableName) {
Cursor cursor = db.rawQuery("select DISTINCT tbl_name from sqlite_master where tbl_name = '" + tableName + "'", null);
if (cursor != null) {
if (cursor.getCount() > 0) {
cursor.close();
return true;
}
cursor.close();
}
return false;
}
sqlite maintains sqlite_master table containing information of all tables and indexes in database.
So here we are simply running SELECT command on it, we'll get cursor having count 1 if table exists.
You mentioned that you've created an class that extends SQLiteOpenHelper and implemented the onCreate method. Are you making sure that you're performing all your database acquire calls with that class? You should only be getting SQLiteDatabase objects via the SQLiteOpenHelper#getWritableDatabase and getReadableDatabase otherwise the onCreate method will not be called when necessary. If you are doing that already check and see if th SQLiteOpenHelper#onUpgrade method is being called instead. If so, then the database version number was changed at some point in time but the table was never created properly when that happened.
As an aside, you can force the recreation of the database by making sure all connections to it are closed and calling Context#deleteDatabase and then using the SQLiteOpenHelper to give you a new db object.
Kotlin solution, based on what others wrote here:
fun isTableExists(database: SQLiteDatabase, tableName: String): Boolean {
database.rawQuery("select DISTINCT tbl_name from sqlite_master where tbl_name = '$tableName'", null)?.use {
return it.count > 0
} ?: return false
}
public boolean isTableExists(String tableName) {
boolean isExist = false;
Cursor cursor = db.rawQuery("select DISTINCT tbl_name from sqlite_master where tbl_name = '" + tableName + "'", null);
if (cursor != null) {
if (cursor.getCount() > 0) {
isExist = true;
}
cursor.close();
}
return isExist;
}
no such table exists: error is coming because once you create database with one table after that whenever you create table in same database it gives this error.
To solve this error you must have to create new database and inside the onCreate() method you can create multiple table in same database.
Important condition is IF NOT EXISTS to check table is already exist or not in database
like...
String query = "CREATE TABLE IF NOT EXISTS " + TABLE_PLAYER_PHOTO + "("
+ KEY_PLAYER_ID + " TEXT,"
+ KEY_PLAYER_IMAGE + " TEXT)";
db.execSQL(query);
i faced that and deal with it by try catch as simple as that i do what i want in table if it not exist will cause error so catch it by exceptions and create it :)
SQLiteDatabase db=this.getWritableDatabase();
try{
db.execSQL("INSERT INTO o_vacations SELECT * FROM vacations");
db.execSQL("DELETE FROM vacations");
}catch (SQLiteException e){
db.execSQL("create table o_vacations (id integer primary key ,name text ,vacation text,date text,MONTH text)");
db.execSQL("INSERT INTO o_vacations SELECT * FROM vacations");
db.execSQL("DELETE FROM vacations");
}
.....
Toast t = Toast.makeText(context, "try... " , Toast.LENGTH_SHORT);
t.show();
Cursor callInitCheck = db.rawQuery("select count(*) from call", null);
Toast t2a = Toast.makeText(context, "count rows " + callInitCheck.getCount() , Toast.LENGTH_SHORT);
t2a.show();
callInitCheck.moveToNext();
if( Integer.parseInt( callInitCheck.getString(0)) == 0) // if no rows then do
{
// if empty then insert into call
.....

SQLite Update / Replace Table Android

I have an app that gets JSON data from a server. I then put the parsed data into the android SQLite database and use the data as needed. This all works great, however, I am unable to find a method to update the whole table.
The scenario would be that this Json Data feed gets updated every week on the server. I have two Questions:
What am I missing or what is the method for updating the SQLite table? (currently this just duplicates the data)
public void updateTable(Product product){
SQLiteDatabase db = this.getWritableDatabase();
try{
ContentValues values = new ContentValues();
values.put(KEY_TYPE_NAME, product.getmProductTypeName());
// more columns here...
db.update(TABLE_NAME, values, null,null);
db.close();
}catch(Exception e){
Log.e("error:",e + "in updateData method")
}
What is an ideal system for updating the data? Would it be silly and bad practice to just call the method when connected to internet?
Related Code in "Main Activity":
handler = new DBHandler(this);
NetworkUtils utils = new NetworkUtils(MainActivity.this);
if (handler.getProductCount() == 0 && utils.isConnectingToInternet()) {
new JsonDataParse().execute();
}`
Related Code "DBhandler" Activity:
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DROP_TABLE);
onCreate(db);
}
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + KEY_TYPE_NAME + " TEXT" + ")"
That is basically my CREATE TABLE String format. I just condensed to because it has 16 columns.
This is the code I added to only delete the stored data only if there was data:
if(handler.getProductCount() == 0) {
}else{
handler.deleteData();
}
Then I just just added the delete the method as suggested:
public void deleteData() {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, "1", null);
}
I'm not sure what you mean by "update the whole table". It sounds to me like you just need to delete the data in the table and then use your current method to add the new data. To delete the contents you can use:
db.delete(TABLE_NAME, "1", null);
Then call your existing method to re-populate the table from the server.
What is an ideal system for updating the data? Would it be silly and bad practice to just call the method when connected to internet?
No it wouldn't be bad practice. That makes sense, as you'll only be able to reach the server if you're connected to the internet anyway.

Bulk Update Database

I've been looking on this site for a while but have not found the answer. I am trying to do a bulk update on data that I know is already in the table. I have one column that needs to be set when a certain condition comes back for the row ID. Here is the single method but I want to make this more efficient and do it as a bulk. Our database is not in a Provider so I just using a Helper class.
public void markUnavailable(int myId) {
SQLiteDatabase db = this.getWritableDatabase();
String sql = "UPDATE " + MYTABLE + " SET " + Col.IS_AVAILABLE + "= 0"+ " WHERE " + Col.MY_ID + "=" + myId;
db.execSQL(sql);
db.close();
}
I would like to pass in an array of myIds to do the bulk Update. I can't do a Insert or Replace statement because I don't have access to all the column data and don't want to pass this through due to too many codes changes.
public void markUnavailable(int[] myId) {
// ????
/// need some way to loop through and update in bulk
}
Try UPDATE tablename SET column=0 WHERE ID IN (...), where ... is a comma-delimited list of ID values.
I'm not an Android developer, but according to good database practices, you should:
public void markUnavailable(int[] myId) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
SQLiteStatement upd=db.compileStatement("UPDATE "+MYTABLE+" SET "+Col.IS_AVAILABLE+"=0 WHERE "+Col.MY_ID+"=?";
for (int i = 0; i < myId.length; i++) {
upd.bindLong(1, myId[i]);
upd.execute();
}
db.endTransaction();
}
Android has SQLiteDatabase.update would be very usefull in this case, but String [] whereArgs would not deal well with your int[] myId.
The fastest way to do a bulk update would be to do it as a single transaction,by using begin and end transactions. Also if the size of the database is large it will be a good idea to make myID as the primary key of the table as it will significantly increase the speed of the speed in fetching the rows for update when the WHERE clause is used.[It is said that indexing can reduce the speed of update and insert but when the where clause is used,indexing has always increased my speed by huge margins.
public void markUnavailable(int[] myId) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
SQLiteStatement upd=db.compileStatement("UPDATE "+MYTABLE+" SET "+Col.IS_AVAILABLE+"=0 WHERE "+Col.MY_ID+"=?");
for (int i = 0; i < myId.length; i++) {
upd.clearBindings();
upd.bindLong(1, myId[i]); // this matches the first "?"
upd.execute();
}
db.setTransactionSucessful();
db.endTransaction();
}

SQLiteOpenHelper onUpgrade() Confusion Android

I am doing my first app with a database and I am having a little trouble understanding the onUpgrade function. My database has a table with an items and a favorite column so that the user can favorite an item. Most implementations I see simply drop the table and reconstruct it but I don't want to do this. I want to be able to add more items to the table.
When the app is upgraded through the android marketplace does the database know its version number? So could I increment the version number in the code and then export it to the marketplace and when the user boots up the upgraded version for the first time then onUpgrade will be called?
If this is the case my onUpgrade would simply pull from a file and add the database items in. Is this a standard way of doing things or is there a better way of handling this in Android. I am trying to stay as standard as possible.
Thanks
Ok, before you run into bigger problems you should know that SQLite is limited on the ALTER TABLE command, it allows add and rename only no remove/drop which is done with recreation of the table.
You should always have the new table creation query at hand, and use that for upgrade and transfer any existing data. Note: that the onUpgrade methods runs one for your sqlite helper object and you need to handle all the tables in it.
So what is recommended onUpgrade:
beginTransaction
run a table creation with if not exists (we are doing an upgrade, so the table might not exists yet, it will fail alter and drop)
put in a list the existing columns List<String> columns = DBUtils.GetColumns(db, TableName);
backup table (ALTER table " + TableName + " RENAME TO 'temp_" + TableName)
create new table (the newest table creation schema)
get the intersection with the new columns, this time columns taken from the upgraded table (columns.retainAll(DBUtils.GetColumns(db, TableName));)
restore data (String cols = StringUtils.join(columns, ",");
db.execSQL(String.format(
"INSERT INTO %s (%s) SELECT %s from temp_%s",
TableName, cols, cols, TableName));
)
remove backup table (DROP table 'temp_" + TableName)
setTransactionSuccessful
(This doesn't handle table downgrade, if you rename a column, you don't get the existing data transfered as the column names do not match).
.
public static List<String> GetColumns(SQLiteDatabase db, String tableName) {
List<String> ar = null;
Cursor c = null;
try {
c = db.rawQuery("select * from " + tableName + " limit 1", null);
if (c != null) {
ar = new ArrayList<String>(Arrays.asList(c.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (c != null)
c.close();
}
return ar;
}
public static String join(List<String> list, String delim) {
StringBuilder buf = new StringBuilder();
int num = list.size();
for (int i = 0; i < num; i++) {
if (i != 0)
buf.append(delim);
buf.append((String) list.get(i));
}
return buf.toString();
}
Next to Pentium10's excellent answer, here are some good examples from living code:
Android AOSP: com.android.providers.calendar.CalendarDatabaseHelper.java
Android AOSP: com.android.browser.BrowserProvider.java
OpenIntents Notepad: org.openintents.notepad.NotePadProvider.java
Thank you for clarifying that onUpgrade() will not support Remove/Drop statements #Pentium 10
For those of you who would like to know the exact moment when onUpgrade() gets called, it is during a call to either getReadableDatabase() or getWriteableDatabase().
To those who are not clear how it ensure it gets triggered...the answer is: It is triggered when the database version provided to the constructor of SqLiteOpenHelper is updated. Here is a example
public class dbSchemaHelper extends SQLiteOpenHelper {
private String sql;
private final String D_TAG = "FundExpense";
//update this to get onUpgrade() method of sqliteopenhelper class called
static final int DB_VERSION = 2;
static final String DB_NAME = "fundExpenseManager";
public dbSchemaHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
// TODO Auto-generated constructor stub
}
now to...onUpgrade()
#Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
sql = "ALTER TABLE " + fundExpenseSchema.Expense.TABLE_NAME + " ADD COLUMN " + fundExpenseSchema.Expense.FUNDID + " INTEGER";
arg0.execSQL(sql);
}
I've been using the solution proposed by #Pentium10 for a long time but today i had a problem, after doing alter table, getColumns from the original table still returns the same columns (in the new version of the db the table suffer mayor structure changes, some columns added some others), really i don't know why select statement does not reflect the structure changes, more over before creating my table again, select statement still returns the columns! When the table is not re-created yet!
So i manage solving this issue updating getColumns method using pragma table_info, like this:
/**
* Get a list of column base_dictionary for the selected table
*
* #param db
* Database that contains the table
* #param tableName
* Table name to be used
* #return A List of column name
*/
public static List<String> getColumns(SQLiteDatabase db, String tableName) {
List<String> ar = null;
Cursor c = null;
try {
c = db.rawQuery("pragma table_info(" + tableName + ")", null);
ar = new ArrayList<String>();
if (c != null && c.moveToFirst()) {
do {
ar.add(c.getString(c.getColumnIndexOrThrow("name")));
} while (c.moveToNext());
c.close();
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (c != null) c.close();
}
return ar;
}

Categories

Resources