I'd like to be able to estimate the NVM size each row in a given Room database is likely to take; the values are all Strings in Java, so I need to know the encoding format that will be used for the underlying SQLite database by Room -- how can I tell Room which encoding to use (e.g. UTF-8) or check programmatically which encoding it is using for text? Assuming the encoding is not configurable, what would it be hard-coded to?
I expected something like
#Database(entities = {TestEntity.class}, version = 1, encoding = "UTF-8")
public abstract class AppDatabase extends RoomDatabase
to work, but the encoding param is not recognized.
add create callback where your room database builder and then execute PRAGMA encoding='UTF-8';
Room.databaseBuilder(context, AppDatabase.class, DB_NAME)
.addCallback(new Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
// add this code
db.execSQL("PRAGMA encoding='UTF-8';");
}
}).allowMainThreadQueries().build();
Related
I'm trying to migrate a Room database from versions 2 and 3 to 4 like so:
private static final Migration MIGRATION_2_4 = new Migration(2, 4) {
#Override
public void migrate(#NonNull #NotNull SupportSQLiteDatabase database) {
database.execSQL(
"ALTER TABLE 'market_data' ADD COLUMN 'watch_list_boolean' TEXT NOT NULL DEFAULT '0'");
database.execSQL("DROP TABLE 'developer_data'");
}
};
but it's not working, what is wrong here?
The problem, most likely (post your stack trace to help future readers), is that your DB won't be able to perform migrations 2->3 and 3->4
So, your code will only work if your db is upgraded from 2 directly to 4 and will throw an exception (that indicates what migration is missing) if db is upgraded from 2 to 3 or from 3 to 4.
Best practice is to create separate migrations - 2 to 3, and 3 to 4.
Room will know to execute the correct migrations and in the right order (2->3 or 3->4 or 2->3->4):
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
#Override
public void migrate(#NonNull #NotNull SupportSQLiteDatabase database) {
database.execSQL(
"ALTER TABLE 'market_data' ADD COLUMN 'watch_list_boolean' TEXT NOT NULL DEFAULT '0'");
}
};
private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
#Override
public void migrate(#NonNull #NotNull SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE 'developer_data'");
}
};
Don't forget to update DB version :)
With all due respect, you are taking a conservative approach.
Since the Room database uses Gradle to set its version number, it's very easy to change it.
So, instead of relying on the tools of Gradle and SQLiteDatabase to do this job for you, use the in-memory version of your database and just create the column using plain SQL.
I have this OColumn partner_name = new OColumn("Partner", OVarchar.class).setLocalColumn(); in my sale order model class with odoo functional method that depends on partner_id column. I would like to search the partner_name in my list using that column partner_name, but I'm a little confused on how to achieve this. Please needed some help.
This is what I've tried:
BaseFragment
#Override
public void onViewBind(View view, Cursor cursor, ODataRow row) {
getPartnerIds(row);
OControls.setText(view, R.id.partner_name, row.getString("partner_name")); // displays false
....
}
}
private void getPartnerIds(ODataRow row){
OValues oValues = new OValues();
oValues.put("partner_id", row.get("partner_id"));
saleOrder.storeManyToOne(oValues);
}
updated:
I noticed that even though I created
#Odoo.Functional(method = "storeManyToOne", store = true, depends = {"partner_id"})
OColumn partner_name = new OColumn("Partner", OVarchar.class).setLocalColumn();
no column was created.
Updated:
partner_name column with odoo functional
Edit: Just place the 'if (type.isAssignableFrom(Odoo.Functional.class)'
before the 'if (type.getDeclaringClass().isAssignableFrom(Odoo.api.class))' to have the correct values.
Define the partner_name field like below:
#Odoo.Functional(method="storePartnerName", store=true, depends={"partner_id"})
OColumn partner_name = new OColumn("Partner name", OVarchar.class)
.setLocalColumn();
public String storePartnerName(OValues values) {
try {
if (!values.getString("partner_id").equals("false")) {
JSONArray partner_id = new JSONArray(values.getString("partner_id"));
return partner_id.getString(1);
}
} catch (Exception e) {
e.printStackTrace();
}
return "false";
}
You can simply get the partner_name using:
row.getString("partner_name")
EDIT:
Note that database is created when you first time run your application, or when you clean your data from app
setting. You need to clean application data everytime when you update your database column.
If the column was added after the database creation, it will not be added to the corresponding table. This is because the database is not upgraded. To fix this issue you can:
Clean application data to update your database column
Remove user account (This will delete database) or reinstall the application to recreate the database.
Or you can change DATABASE_VERSION in odoo/datas/OConstants then override onModelUpgrade method in sale order model and upgrade the table manually (alter sale order table and add the partner name column using SQL query: ALTER TABLE sale_order ADD partner_name VARCHAR(100)).
When a new sale order is created and synchronized, the partner name should be computed and stored automaticaly.
I noticed that the partner name was not set for existing records after synchrinization so I added another SQL query to compute and set the value of partner name for old records.
Example:
#Override
public void onModelUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("ALTER TABLE sale_order ADD partner_name VARCHAR(100)");
db.execSQL("UPDATE sale_order SET partner_name = (SELECT name from res_partner WHERE _id=partner_id) WHERE partner_name IS NULL AND partner_id IS NOT NULL");
}
Edit (config):
using the new configuration you will get the following error (which will prevent creating fields using annotations):
W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)' on a null object reference
W/System.err: at com.odoo.core.orm.OModel.compatibleField(OModel.java:349)
CODE:
if (type.getDeclaringClass().isAssignableFrom(Odoo.api.class)) {
Try to remove .getDeclaringClass()
Edit: not all partner names are shown
There is a org.json.JSONException error that happens when it try to convert partner_id string to a JSON array.
W/System.err: org.json.JSONException: Unterminated array at character 12 of [114.0, UH PARTNER]
The error happens when it try to convert names containing spaces. To avoid that you can cast partner_id string to a list of objects.
In partnerName method, replace the following code:
JSONArray partner_id = new JSONArray(values.getString("partner_id"));
return partner_id.getString(1);
With:
List<Object> partner_id = (ArrayList<Object>) values.get("partner_id");
return partner_id.get(1) + "";
Currently, we have the following database table
#Entity(
tableName = "note"
)
public class Note {
#ColumnInfo(name = "body")
private String body;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
The length of the body string, can be from 0 to a very large number.
In certain circumstance, we need to
Load the all notes into memory.
A LiveData which is able to inform observers, if there's any changes made in the SQLite note table.
We just need the first 256 characters of body. We do not need entire body. Loading entire body string for all notes might cause OutOfMemoryException.
We have the following Room Database Dao
#Dao
public abstract class NoteDao {
#Query("SELECT * FROM note")
public abstract LiveData<List<Note>> getAllNotes();
}
getAllNotes able to fulfill requirements (1) and (2), but not (3).
The following getAllNotesWithShortBody is a failed solution.
#Dao
public abstract class NoteDao {
#Query("SELECT * FROM note")
public abstract LiveData<List<Note>> getAllNotes();
#Query("SELECT * FROM note")
public abstract List<Note> getAllNotesSync();
public LiveData<List<Note>> getAllNotesWithShortBody() {
MutableLiveData<List<Note>> notesLiveData = new MutableLiveData<>();
//
// Problem 1: Still can cause OutOfMemoryException by loading
// List of notes with complete body string.
//
List<Note> notes = getAllNotesSync();
for (Note note : notes) {
String body = note.getBody();
// Extract first 256 characters from body string.
body = body.substring(0, Math.min(body.length(), 256));
note.setBody(body);
}
notesLiveData.postValue(notes);
//
// Problem 2: The returned LiveData unable to inform observers,
// if there's any changes made in the SQLite `note` table.
//
return notesLiveData;
}
}
I was wondering, is there any way to tell Room database Dao: Before returning List of Notes as LiveData, please perform transformation on every Note's body column, by trimming the string to maximum 256 characters?
Examining the source code generated by Room Dao
If we look at the source code generated by Room Dao
#Override
public LiveData<List<Note>> getAllNotes() {
final String _sql = "SELECT * FROM note";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
...
...
final String _tmpBody;
_tmpBody = _cursor.getString(_cursorIndexOfBody);
_tmpPlainNote.setBody(_tmpBody);
It will be great, if there is a way to supply transformation function during runtime, so that we can have
final String _tmpBody;
_tmpBody = transform_function(_cursor.getString(_cursorIndexOfBody));
_tmpPlainNote.setBody(_tmpBody);
p/s Please do not counter recommend Paging library at this moment, as some of our features require entire List of Notes (with trimmed body String) in memory.
You can use SUBSTR, one of SQLite's built-in functions.
You need a primary key in your #Entity. Assuming that you call it id, you can write a SQL like below.
#Query("SELECT id, SUBSTR(body, 0, 257) AS body FROM note")
public abstract LiveData<List<Note>> getAllNotes();
This will return the body trimmed to 256 chars.
With that being said, you should consider segmenting your rows. If you have too many rows, they will eventually use up your memory at some point. Using Paging is one way to do it. You can also use LIMIT and OFFSET to manually go through segments of rows.
We try to update sqlite_sequence with the following code.
WeNoteRoomDatabase weNoteRoomDatabase = WeNoteRoomDatabase.instance();
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'attachment'"));
However, it has no effect at all. I exam the sqlite_sequence table content using SQLite browser. The counter is not reset to 0.
If we try to run the same query manually using SQLite browser on same SQLite file, it works just fine.
Our Room database is pretty straightforward.
#Database(
entities = {Attachment.class},
version = 6
)
public abstract class WeNoteRoomDatabase extends RoomDatabase {
private volatile static WeNoteRoomDatabase INSTANCE;
private static final String NAME = "wenote";
public abstract AttachmentDao attachmentDao();
public static WeNoteRoomDatabase instance() {
if (INSTANCE == null) {
synchronized (WeNoteRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
WeNoteApplication.instance(),
WeNoteRoomDatabase.class,
NAME
)
.build();
}
}
}
return INSTANCE;
}
}
Any idea what we had missed out?
Additional information : clearing sqlite_sequence is not working in android room
Room doesn't use SQLiteDatabase - but it uses SupportSQLiteDatabase, while it's source code states, that it delegates all calls to an implementation of {#link SQLiteDatabase}... I could even dig further - but I'm convinced, that this is a consistency feature and not a bug.
SQLiteDatabase.execSQL() still works fine, but with SupportSQLiteDatabase.execSQL() the same UPDATE or DELETE queries against internal tables have no effect and do not throw errors.
my MaintenanceHelper is available on GitHub. it is important that one initially lets Room create the database - then one can manipulate the internal tables with SQLiteDatabase.execSQL(). while researching I've came across annotation #SkipQueryVerification, which could possibly permit UPDATE or DELETE on table sqlite_sequence; I've only managed to perform a SELECT with Dao... which in general all hints for the internal tables are being treated as read-only, from the perspective of the publicly available API; all manipulation attempts are being silently ignored.
i think query is wrong, you should try below query
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = attachment"));
I'm using room database version 2.2.5
Here I'm unable to execute this query using Room Dao structure, so make one simple class and access method as like this and I got successful outcomes so this one is tested result. I'm using RxJava and RxAndroid for same.
public class SqlHelper {
private static SqlHelper helper = null;
public static SqlHelper getInstance() {
if (helper == null) {
helper = new SqlHelper();
}
return helper;
}
public Completable resetSequence(Context context) {
return Completable.create(emitter -> {
try {
AppDatabase.getDatabase(context)
.getOpenHelper()
.getWritableDatabase()
.execSQL("DELETE FROM sqlite_sequence WHERE name='<YOUR_TABLE_NAME>'");
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
}
}
Execute:
SqlHelper.getInstance()
.resetQuizSequence(context)
.subscribeOn(Schedulers.io()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {}, error -> {});
Table sql_sequence is not managed by Room, so you need to edit it using a SupportSQLiteDatabase.
Try this:
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
weNoteRoomDatabase().getOpenHelper().getWritableDatabase().execSQL(sqlQuery);
This works for me - Room 2.2.6
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
<YourDatabase>.getInstance(mContext).getOpenHelper().getWritableDatabase().execSQL(sqlQuery);
I use in my project "ActiveAndroid". I have a model which I retain. here is a model:
#Table(name="Params")
public class Params extends Model {
public Params() {
super();
}
#Expose
#Column(name="height")
#SerializedName("heigth")
public int heigth;
#Expose
#Column(name="weight")
#SerializedName("weight")
public int weight;
}
here all save:
public void success(User user, Response response) {
user.params.save();
user.save();
ActiveAndroid.clearCache();
}
Everything works fine! But if I want to add another field in the model:
#Expose
#Column(name="strong")
#SerializedName("strong")
public int strong;
I get an error:
android.database.sqlite.SQLiteException: table Params has no column named strong (code 1): , while compiling: INSERT INTO Params(Id,weight,height,strong) VALUES (?,?,?,?)
of course I know why this error. Because the table is no such column. That's the question, how to add this column ???
Now that I've tried:
remove programs completely, again to compile, run, and everything works fine! because it creates a new table from this column.
Tried to change the version in the manifest database, but this has not led to success.
I know that with normal use of the database in the method of upgrade you can change the version of the database and restructure table. but how to do it in ORM "ActiveAndroid" ?
There is a thing known as Migration, in Active Android you can do that like this
Add the field in your model (which you already did)
Change the database version the the AndroidManifest.xml's metadata
Write your migration script. Name your script [newDatabaseVersion].sql, and place it in the directory [YourApp’sName]/app/src/main/assets/migrations. In my specific example, I’ll create the file [MyAppName]/app/src/main/assets/migrations/2.sql.
E.G. ALTER TABLE Items ADD COLUMN Priority TEXT;