Drop a column when the schema changes in ORMLite - android

I am working with Android and ORMLite. I am newbie and I upgrading the database to the Version 2. I have the class “Auto” and need to drop the column Alias.
#DatabaseTable(tableName = "Auto")
public class Auto {
public static final String ALIAS = "alias";
public static final String PLACA = "placa";
#DatabaseField(generatedId = true, columnName = "ID")
private int id;
#DatabaseField(canBeNull = false, columnName = ALIAS)
private String alias;
#DatabaseField(canBeNull = false, columnName = PLACA)
public Auto()
{
//ORMLite needs a no-arg constructor
}
public Auto(String alias, String placa) {
this.alias = alias;
this.placa = placa;
}
…..
…..
}
I change the class Auto to…
#DatabaseTable(tableName = "Auto")
public class Auto {
public static final String PLACA = "placa";
#DatabaseField(generatedId = true, columnName = "ID")
private int id;
#DatabaseField(canBeNull = false, columnName = PLACA)
public Auto()
{
//ORMLite needs a no-arg constructor
}
public Auto(String alias, String placa) { this.placa = placa;
}
…..
…..
}
I need to know if ORMLite automatically drops the column Alias when execute “onUpgradeMethod” or I have to do manually like this.
#Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
if(oldVersion == 1) {
try {
Dao dao;
dao = getAutoDao();
dao.executeRaw("ALTER TABLE `Auto` DROP COLUMN alias;");
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Error ", e);
}
}
}

This links help me to solve it.
Drop column in SQLite
FAQ SQLite 11
Delete column from SQL
save data before table upgrade
Drop table if it already exists and then re-create it?
SQLite has limited ALTER TABLE support that you can use to add a column to the end of a table or to change the name of a table. If you want to make more complex changes in the structure of a table, you will have to recreate the table. You can save existing data to a temporary table, drop the old table, create the new table, then copy the data back in from the temporary table.
For example, suppose you have a table named "t1" with columns names
"a", "b", and "c" and that you want to delete column "c" from this
table. The following steps illustrate how this could be done:
BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1; CREATE TABLE t1(a,b); INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT;
The possible solution according to Phil's comments is this. 4
So your choices are:
leave things as they are,
add a new column, copying all the data from old to new as part of the upgrade, and just ignore the old column altogether, or
Use a drop/create strategy to upgrade: back up the data from table into a temporary table, drop the table, re-create it as you'd like it
to be, copy all the data back into it, and finally drop the temporary
table.
Database upgrades are always nervous affairs (need lots of error
handling, lots of testing), frankly if the name is the only thing that
concerns you, I'd leave it alone (option 1). If you really need to
change it then option 2 is low risk but leaves a 'dead' column and
data lying around. Option 3 may be your choice if, for example, the
data is a significant percentage of the overall database size.
I decided to delete the data from the column but don't drop the column, that is similar to solution 2.

Related

How to delete database metadata via room in android?

I have a table in room database with a field set as index and it's autoincrement. Sqlite will save some meta data in its master table to keep count of the last auto-generated value.
Based on my app logic I will clear database and keep the structure; suppose I have inserted 3 items to the database and the mentioned action takes place so I clear items, but when I insert a new item its auto-generated field will be 4 which will cause overflow and app crash in the long run. I worked it around by removing autoincrement and setting the field manually!
Now my question is how can I reset the auto-incremented field value to be set to 1 after each database clearance (I will prefer room only way)?
The way that autoincrement works is that 2 values are used when determining a new value :-
the first is equivalent to using max(the_autoincrement_column) (i.e the column that aliases the rowid column that has AUTOINCREMENT coded),
the second is obtained from the table sqlite_sequence from the seq column of the row that has the table name in the name column.
Note that the value(s) are not stored in THE master table, sqlite_master (the schema) but in the sqlite_sequence table.
The sqlite_sequence table will only exist if AUTOINCREMENT has been used.
1 is added to the greater value.
To reset, in theory, you should delete all rows from the table and delete the respective row from the sqlite_sequence table.
However, room protects system tables. So in short there appears to be no way of using room to do the latter and hence the issue. Here is answer is an example that does the above BUT it has to be run outside of (before) room and is thus limited.
Note in the answer there is additional code that is used to start numbering from 0 (the Trigger).
However in regards to overflow then it's basically highly unlikely as per :-
Maximum Number Of Rows In A Table
The theoretical maximum number of rows in a table is 2 to the power of 64
(18446744073709551616 or about 1.8e+19). This limit is unreachable
since the maximum database size of 140 terabytes will be reached
first. A 140 terabytes database can hold no more than approximately
1e+13 rows, and then only if there are no indices and if each row
contains very little data.
Limits In SQLite
With autoincrement it is 2 to power of 63 (9,223,372,036,854,775,808‬) (without autoincrement you can use negative values(java) so you can utilise the 64th bit hence the thoerectical maximum) as such the limitation would likely be disk capacity rather than the highest id being reached.
Additional
After some playing around, the following does reset the sequence whilst Room has the database.
That is the following builds the Room Database inserts two rows, resets the sequence (including deleting the recently added rows)
by opening the database as a standard SQLiteDatabase
Note the use of both OPENREADWRITE and ENABLEWRITEAHEADLOGGING
(if not the latter then a warning message saying that WAL can't be turned off as the db is open, so this just opens it in WAL mode)
deleting the existing rows in the table and
deleting the respective row from sqlite_sequence and finally
closing this other database.
:-
public class MainActivity extends AppCompatActivity {
public static final String DBNAME = "mydatabase";
public static final String MYTABLENAME = "mytable";
MyDatabase mydb,mydb2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mydb = Room.databaseBuilder(this,MyDatabase.class,DBNAME).allowMainThreadQueries().build();
MyTableDAO mytabledao = mydb.getMyTableDAO();
MyTable mt1 = new MyTable();
mt1.setName("Test001");
mytabledao.insert(mt1);
MyTable mt2 = new MyTable();
mt2.setName("Test002");
mytabledao.insert(mt2);
for (MyTable mt: mytabledao.getAllMyTables()) {
Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
}
/*
while (mydb.isOpen()) {
mydb.close();
}
Ouch if used :-
E/ROOM: Invalidation tracker is initialized twice :/. (ignored)
E/ROOM: Cannot run invalidation tracker. Is the db closed?
java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
*/
resetSequencedTable(MYTABLENAME);
//mydb2 = Room.databaseBuilder(this,MyDatabase.class,DBNAME).allowMainThreadQueries().build(); // No Good
/*
Works even though :-
05-12 12:31:40.112 28585-28585/? D/MYTABLEROW: ID=1 Name=Test001
05-12 12:31:40.112 28585-28585/? D/MYTABLEROW: ID=2 Name=Test002
05-12 12:31:40.114 28585-28585/? E/SQLiteLog: (5) statement aborts at 2: [PRAGMA journal_mode=PERSIST]
05-12 12:31:40.115 28585-28585/? W/SQLiteConnection: Could not change the database journal mode of '/data/user/0/soa.myapplication/databases/mydatabase' from 'wal' to 'PERSIST' because the database is locked. This usually means that there are other open connections to the database which prevents the database from enabling or disabling write-ahead logging mode. Proceeding without changing the journal mode.
05-12 12:31:40.126 28585-28585/? D/MYTABLEROW: ID=1 Name=Test003
05-12 12:31:40.126 28585-28585/? D/MYTABLEROW: ID=2 Name=Test004
*/
for (MyTable mt: mytabledao.getAllMyTables()) {
Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
}
MyTable mt3 = new MyTable();
mt3.setName("Test003");
mytabledao.insert(mt3);
MyTable mt4 = new MyTable();
mt4.setName("Test004");
mytabledao.insert(mt4);
for (MyTable mt: mytabledao.getAllMyTables()) {
Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
}
}
private void resetSequencedTable(String table) {
Log.d("RESETSEQ","Initiating sequence reset");
SQLiteDatabase db = SQLiteDatabase.openDatabase(this.getDatabasePath(DBNAME).toString(),null,SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING);
db.delete(table,null,null);
String whereclause = "name=?";
String[] whereargs = new String[]{table};
db.delete("sqlite_sequence",whereclause,whereargs);
db.close();
Log.d("RESETSEQ", "Terminating sequence reset");
}
}
The Entity for the table is :-
#Entity(tableName = MainActivity.MYTABLENAME)
public class MyTable {
#PrimaryKey(autoGenerate = true)
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

ORMLite dropping data when adding a column to the table

In my Android app, I'm using ORMLite to store data into local database, and when adding a column to that database using onUpgrade() method, I'm getting all the data of that table to be erased.
I'm using upgrade schema way mentioned in ORMLite documentation:
private static final int DATABASE_VERSION = 10;
#Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int oldVer, int newVer) {
try {
if(oldVer < 10){
getGuardiansDao().executeRaw("ALTER TABLE `messages` ADD COLUMN updated_by TEXT;");
getGuardiansDao().executeRaw("ALTER TABLE `messages` ADD COLUMN updated_by_time TEXT;");
}
} catch (SQLException e) {
Log.e(DBManager.class.getName(), "Unable to upgrade database from version " + oldVer + " to new "
+ newVer, e);
}
}
And in the class assigned to that table:
#DatabaseTable(tableName = "messages")
public class Message extends Object implements Parcelable{
.
.
.
#DatabaseField
private String updatedBy;
#DatabaseField
private String updatedByTime;
}
Now after executing that it erases all the data stored in the table messages and I don't have any clue why that is happening.
Now after executing that it erases all the data stored in the table messages and I don't have any clue why that is happening.
Certainly the code that you've posted won't erase the table and ORMLite doesn't do that automatically or anything. Is there any chance the onCreate(...) method is being called instead for some reason?
There are many ways to debug this but you could log the contents of the table using raw statements in the onUpgrade(...) method to ensure that the table is being correctly built. Then you might be able to detect when it has been cleared.

Obtaining table names for sqlite SQL join in android

I have a need to join standard android's tables (like contacts and call log) using SQL. It is possible using the rawQuery or query methods of SQLiteDatabase class. But for the methods to work properly I need to know table names that I can provide in a raw SQL query.
Example. I want to execute query like this:
SELECT * FROM Contacts as c INNER JOIN Call_Log as l ON c.number=l.number
I know how to get field names (like CallLog.Calls.NUMBER), but I don't know how to get the name of a standard table that every android has. It is possible to hardcode the name, but the way with something like CallLog.TABLE_NAME looks much more reliable. So, where can I find an analogue of CallLog.TABLE_NAME?
Your asking for a lot of info, but this is a good summation of how to access the contacts table and how to create your own SQL table and update it with information you get from other tables.
To do any type of search of the Contacts Provider, your app must have READ_CONTACTS permission. To request this, add this element to your manifest file as a child element of :
<uses-permission android:name="android.permission.READ_CONTACTS" />
To do any type of search of the Call Log, your app must have READ_CALL_LOG permission. To request this, add this element to your manifest file as a child element of :
<uses-permission android:name="android.permission.READ_CALL_LOG" />
Code below on how to access Phone Call History
Uri allCalls = Uri.parse("content://call_log/calls");
Cursor c = managedQuery(allCalls, null, null, null, null);
String num= c.getString(c.getColumnIndex(CallLog.Calls.NUMBER));// for number
String name= c.getString(c.getColumnIndex(CallLog.Calls.CACHED_NAME));// for name
String duration = c.getString(c.getColumnIndex(CallLog.Calls.DURATION));// for duration
int type = Integer.parseInt(c.getString(c.getColumnIndex(CallLog.Calls.TYPE)));// for call type, Incoming or out going.
This technique tries to match a search string to the name of a contact or contacts in the Contact Provider's ContactsContract.Contacts table. You usually want to display the results in a ListView, to allow the user to choose among the matched contacts.
Saving data to a database is ideal for repeating or structured data, such as contact information. This class assumes that you are familiar with SQL databases in general and helps you get started with SQLite databases on Android. The APIs you'll need to use a database on Android are available in the android.database.sqlite package.
One of the main principles of SQL databases is the schema: a formal declaration of how the database is organized. The schema is reflected in the SQL statements that you use to create your database. You may find it helpful to create a companion class, known as a contract class, which explicitly specifies the layout of your schema in a systematic and self-documenting way.
A contract class is a container for constants that define names for URIs, tables, and columns. The contract class allows you to use the same constants across all the other classes in the same package. This lets you change a column name in one place and have it propagate throughout your code.
A good way to organize a contract class is to put definitions that are global to your whole database in the root level of the class. Then create an inner class for each table that enumerates its columns.
public final class FeedReaderContract {
// To prevent someone from accidentally instantiating the contract class,
// make the constructor private.
private FeedReaderContract() {}
/* Inner class that defines the table contents */
public static class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
}
}
Once you have defined how your database looks, you should implement methods that create and maintain the database and tables. Here are some typical statements that create and delete a table:
private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
FeedEntry._ID + " INTEGER PRIMARY KEY," +
FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
FeedEntry.COLUMN_NAME_SUBTITLE + TEXT_TYPE + " )";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
Just like files that you save on the device's internal storage, Android stores your database in private disk space that's associated application. Your data is secure, because by default this area is not accessible to other applications.
A useful set of APIs is available in the SQLiteOpenHelper class. When you use this class to obtain references to your database, the system performs the potentially long-running operations of creating and updating the database only when needed and not during app startup. All you need to do is call getWritableDatabase() or getReadableDatabase().
To use SQLiteOpenHelper, create a subclass that overrides the onCreate(), onUpgrade() and onOpen() callback methods. You may also want to implement onDowngrade(), but it's not required.
For example, here's an implementation of SQLiteOpenHelper that uses some of the commands shown above:
public class FeedReaderDbHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "FeedReader.db";
public FeedReaderDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
db.execSQL(SQL_DELETE_ENTRIES);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
}
To access your database, instantiate your subclass of SQLiteOpenHelper:
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
Put Information into a Database
Insert data into the database by passing a ContentValues object to the insert() method:
// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);
// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);
The first argument for insert() is simply the table name.
The second argument tells the framework what to do in the event that the ContentValues is empty (i.e., you did not put any values). If you specify the name of a column, the framework inserts a row and sets the value of that column to null. If you specify null, like in this code sample, the framework does not insert a row when there are no values.
To read from a database, use the query() method, passing it your selection criteria and desired columns. The method combines elements of insert() and update(), except the column list defines the data you want to fetch, rather than the data to insert. The results of the query are returned to you in a Cursor object.
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor c = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
To look at a row in the cursor, use one of the Cursor move methods, which you must always call before you begin reading values. Generally, you should start by calling moveToFirst(), which places the "read position" on the first entry in the results. For each row, you can read a column's value by calling one of the Cursor get methods, such as getString() or getLong(). For each of the get methods, you must pass the index position of the column you desire, which you can get by calling getColumnIndex() or getColumnIndexOrThrow(). For example:
cursor.moveToFirst();
long itemId = cursor.getLong(
cursor.getColumnIndexOrThrow(FeedEntry._ID)
);

can i dynamically add new column to sqlite table by calling a method from another class?

I have a table in database, each record of this table needs to store
multiple Strings, i dont know how many Strings because its decided at runtime.
I want to add image uri's in database table dynamically, user
dynamically add images in my app as many as he want so i need to save
uri of them, what is the right approach to do it?
I am trying something like this by follow this Insert new column into table in sqlite ?
String ColumnName=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString() + "/" + "image1.jpg");
addNewColumn(ColumnName);
i used below method for this (not worked):-
First i am adding new column in table :-
public Cursor addColumn(String name){
db=dbhelper.getWritableDatabase();
return db.rawQuery("alter table info add column " + name + " text", null);
}
Then insert uri into this
public Boolean setUri(String columnName,String uri) {
ContentValues cv= new ContentValues();
cv.put(columnName,uri);
SQLiteDatabase db =dbhelper.getWritableDatabase();
long id=db.insert("info",null,cv);
if(id>-1)
return true;
else
return false;
}
is the above approach correct?
also i searched and fine below code :-
private static final String ALTER = "ALTER TABLE user_table ADD user_street1 TEXT";
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
db.execSQL(ALTER);
}
can i call onUpgrade() method dynamically and add new column or any other way to do it..?
Thanks in advance :)
It's very bad to have an arbitrary number of columns in a table. You should instead use a second table with a foreign key referencing the ID of a row in the first table.
table user
_id username ...
------------------------
1 abc
2 xyz
table photoInfo
userId photoUri
-------------------------------
1 /path/to/image1.jpg
1 /path/to/image2.jpg
2 /path/to/image3.jpg
1 /path/to/image4.jpg
To show photos for a particular user, use a JOIN.

Upgrading Android application data with multiple tables (without destroying unaffected tables)

In my Android application; I have a single database with multiple tables.
Each table is more-or-less separate from each other, but figured (for best practice?) to just have one DB file.
When it comes to Upgrades, it's currently an all or nothing affair. On upgrade, it "DROP"'s all the tables and re-creates them. However, this is rather harsh if only one of the tables has changed as all the other tables' data is also lost.
Is there a built-in way to auto-upgrade just the tables that have changed?
(e.g. using a version number per/table?)
If not, I guess I can see two options:
Use separate databases/files for each table, to use built-in version upgrade functionality.
Use the database version number to know when the "schema" has changed, but have a separate table to store the current TABLE_VERSIONS and manage my own upgrade by checking the version number of each table against the current build and DROP/CREATE Tables where needed.
(I'd rather not re-invent the wheel here, so I'm hoping I'm missing something simple...)
You need an abstract class that implements the upgrade process described here. Then you extend this abstract class for each of your tables. In your abstract class you must store you tables in a way(list, hardcoded) so when the onUpgrade fires you iterate over the table items and for each table item you do the described steps. They will be self upgraded, keeping all their existing details. Please note that the onUpgrade event fires only once per database, that's why you need to iterate over all your tables to do the upgrade of all of them. You maintain only 1 version number over all the database.
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();
}
If you're using the Android SQLite helper classes (i.e. SQLiteOpenHelper) then you only have one version number representing the database schema. Personally, I put all the schema creation code in my instance of SQLiteOpenHelper and keep the upgrade logic simple:
#Override
public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) {
// Alter all the tables so the schema is brought up-to-date.
if (oldVersion < newVersion) {
db.execSQL("ALTER TABLE foo ADD COLUMN new_column INTEGER NOT NULL");
}
}

Categories

Resources