android.database.sqlite.SQLiteException: near "?": syntax error (code 1): - android

I'm using Room library (MVVM Pattern), and one of the Dao functions returns this error message:
android.database.sqlite.SQLiteException: near "?": syntax error (code 1): , while compiling: UPDATE parcel_table SET possibleDeliveryPersonsList = ?,? WHERE id = ?
This is the Dao code:
#Dao
public interface ParcelDao {
#Insert
void insert(Parcel parcel);
#Delete
void delete(Parcel parcel);
#Query("UPDATE parcel_table SET shippingDate=:shippingDate WHERE id = :id")
void updateShippingDate(String shippingDate, int id);
#Query("UPDATE parcel_table SET parcelStatus=:status WHERE id = :id")
void updatePackageStatus(Enums.ParcelStatus status, int id);
#Query("UPDATE parcel_table SET deliveryPersonName=:deliveryPersonName WHERE id = :id")
void updateDeliveryPersonName(String deliveryPersonName, int id);
#Query("UPDATE parcel_table SET possibleDeliveryPersonsList = :possibleList WHERE id = :tid")
void updatePossibleDeliveryPersonsList(List<String> possibleList, int tid);
#Query("DELETE FROM parcel_table")
void deleteAllParcels();
#Query("SELECT * from parcel_table")
LiveData<List<Parcel>> getParcels();
}
And this is part of the Parcel class:
#Entity(tableName = "parcel_table")
public class Parcel {
private Enums.ParcelType parcelType;
private boolean isFragile;
private Enums.ParcelWeight parcelWeight;
private LatLng warehouseLocation;
private String recipientName;
private LatLng recipientAddress;
private String recipientEmail;
private String recipientPhone;
private String dateReceived;
private String shippingDate;
private Enums.ParcelStatus parcelStatus;
private String deliveryPersonName;
private String fireBasePushId;
private List<String> possibleDeliveryPersonsList;
#PrimaryKey(autoGenerate = true)
#NonNull
private int id;
//and more...
}
The List<String> Type Converter:
#TypeConverter
public String listToString(List<String> list) {
String joined = TextUtils.join(", ", list);
return joined;
}
#TypeConverter
public List<String> stringToList(String string) {
List<String> myList = new ArrayList<String>(Arrays.asList(string.split(",")));
return myList;
}
I have no idea what to do, because the SQLite code is supposedly automatically generated by the Dao and I have no effect on it ...

Two more workarounds in addition to Bob Snyder' answer (but they need to be thoroughfully tested):
To "imitate" TypeConverter (from List to String) by yourself (it's a tricky thing, I've not tried it in practice!):
In DAO change the type of possibleList to String:
#Query("UPDATE parcel_table SET possibleDeliveryPersonsList = :possibleList WHERE id = :tid")
void updatePossibleDeliveryPersonsList(String possibleList, int tid);
add auxiliary method for conversion (you can place it at DAO as well):
void updatePossibleDeliveryPersonsList(List<String> possibleList, int tid) {
String listToString = TextUtils.join(", ", possibleList);
// copied from your converter, it could be put in some common function to follow DRY
updatePossibleDeliveryPersonsList(listToString, tid);
}
and call it from Repository/ViewModel:
db.ParcelDao().updatePossibleDeliveryPersonsList(possibleList, tid);
To replace your multiple updateXXX methods in DAO with single update (you have a lot of fields in your table, may be it would be better to try some universal way to update any combinations of them?):
#Update
void update(Parcel parcel);
Add to your DAO method for searching parcel by id:
#Query("SELECT * from parcel_table where id = :id")
Parcel getParcel(int id);
And in your Repository/ViewModel at first get Parcel, then change it (status, name whatever) and then update database:
Parcel parcel = db.ParcelDao().getParcel(id); // let's say it can't be null
parcel.shippingDate = yourShippingDate; // or change here any of your other fields, including list
db.ParcelDao().update(parcel);

The documentation for the Query annotation explains a feature of Room argument binding:
As an extension over SQLite bind arguments, Room supports binding a
list of parameters to the query. At runtime, Room will build the
correct query to have matching number of bind arguments depending on
the number of items in the method parameter.
This feature was likely intended to be used in the where-clause (as shown in the documentation example), but appears to be applied everywhere in the query statement.
In your case, the desired behavior is to have Room apply your type converter, but instead Room is ignoring the type converter and generating the special list binding.
I think you are going to have to work around this limitation of the current Room implementation. You might want to write a Room bug-report to get confirmation that the explanation provided here is correct.
One option for workaround that will use the type converters is to define this class:
public class DeliveryPersonsUpdate {
public int id;
public List<String> deliveryPersons;
public DeliveryPersonsUpdate(int id, List<String> deliveryPersons) {
this.id = id;
this.deliveryPersons = deliveryPersons;
}
}
then add this method to your Dao:
#Update(entity = Parcel.class)
void update(DeliveryPersonsUpdate update);
Example invocation:
db.ParcelDao().update(new DeliveryPersonsUpdate(id, personsList);

Related

Is it possible to enable/ disable transformation on selected Room Database Dao column?

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.

How to get specific column using Android Room

I'm trying to get id column from my database, ad it to ArrayList and to each id add "\t0",
My database is created using Room, i have a lot of column which one of them is
#PrimaryKey(autoGenerate = true)
private int id;
I am operating using ItemDAO and i have there function
#Query("SELECT * FROM item")
List<Item> getItems();
Which writes to ArrayList<Items> all of contents
I was thinking of running it trough the loop getting id and adding to ArrayList<String> but this doesn't seems to be eficient.
Your DAO:
#Query("SELECT Id FROM item")
List<Integer> getAllIds();
Your model:
#ColumnInfo(name = "Id")
#PrimaryKey(autoGenerate = true)
private int id;
In you query SELECT * FROM item * means select All, put there your column name and you will get list of objects from that column
Example: Select all items in id column SELECT id FROM item
I tried to modify and test #Valgaal 's solution. It turns out that Room can also return other type of values, more than just id (or integer).
For example, you can write an item class like this:
#Entity(tableName = Item.TABLE_NAME)
public class Item {
public static final String TABLE_NAME = "ItemsTable";
public static final String COL_DESC = "Description";
#PrimaryKey(autoGenerate = true)
private int id;
#ColumnInfo(name = COL_DESC)
private String description;
// getter & setter...
}
And then, you can write Dao like this:
#Dao
public interface ItemDao {
#Query("SELECT * FROM " + Item.TABLE_NAME)
List<Item> getItems();
#Query("SELECT " + Item.COL_DESC + " FROM " + Item.TABLE_NAME)
List<String> getItemDescriptions();
}
And it's functional as it should be.
I guess all of the other data types that Room can save (including custom types?) can be queried (and returned lists of specific column data) by the same logic above. Hope this would help someone in the future!
For returning multiple columns, create a pojo class that can be set as a return type for your DAO function
Note the select query should contain the Pojo class variable name (can be done via AS keyword)
Detailed answer here
https://stackoverflow.com/a/50802209/1029110
I landed on this question for my issue...but didnt find answer. So this may help others.

How to make many-to-many relation query in GreenDAO with source property other than primary key?

Let's assume we have following entities:
Item:
class Item {
...
#Index(unique=true)
private String guid;
...
#ToMany
#JoinEntity(entity = JoinItemsWithTags.class, sourceProperty = "itemGuid", targetProperty = "tagName")
private List<Tag> tagsWithThisItem;
...
}
Tag:
class Tag {
#Id
private Long localId;
#Index(unique = true)
private String name;
...
}
and we need to join them. Here is my join entity class:
#Entity(nameInDb = "item_tag_relations")
class JoinItemsWithTags {
#Id
private Long id;
private String itemGuid;
private String tagName;
...
}
I want to use tag name as a join property instead of Long id, because it's easier to support consistency when syncing with server.
But currently tags getter in Item class always return an empty list. I've looked into log and found generated query which using internally in that getter:
SELECT * <<-- there were a long sequence of fields
FROM "tags" T JOIN item_tag_relations J1
ON T."_id"=J1."TAG_NAME" <<-- here is the problem, must be `T."NAME"=J1."TAG_NAME"`
WHERE J1."ITEM_GUID"=?
So the problem is that join is base on tag's _id field. Generated List<Tag> _queryItem_TagsWithThisItem(String itemGuid) method implicitly uses that id to make a join:
// this `join` nethod is overloaded and pass tag's id as source property
queryBuilder.join(JoinItemsWithTags.class, JoinItemsWithTagsDao.Properties.TagName)
.where(JoinItemsWithTagsDao.Properties.ItemGuid.eq(itemGuid));
Correct approach is this case might be following, I suppose:
// source property is passed explicitly
queryBuilder.join(/* Desired first parameter -->> */ TagDao.Properties.Name,
JoinItemsWithTags.class, JoinItemsWithTagsDao.Properties.TagName)
.where(JoinItemsWithTagsDao.Properties.ItemGuid.eq(itemGuid));
But this code is in generated dao, and I don't know how to do anything with it. Is there any way to workaround this?

How to filter a nested relation in Room?

Let's take this example: I have a form, which has several sections, each having questions. Sideways, I have answers that are mapped to questions and they have another column that I want to filter on when querying:
So I have the following entities:
#Entity(tableName = "sections")
public class Section {
#PrimaryKey
public long id;
public String title;
}
#Entity(tableName = "questions")
public class Question {
#PrimaryKey
public long id;
public String title;
public long sectionId;
}
#Entity(tableName = "answers")
public class Answer {
#PrimaryKey
public long id;
public long questionId;
public int otherColumn;
}
In the section DAO I want to retrieve all of them.
Here's the POJO that I want filled by this query:
class SectionWithQuestions {
#Embedded
public Section section;
#Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
public List<QuestionWithAnswer> questions;
public static class QuestionWithAnswer {
#Embedded
public Question question;
#Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
List<Answer> answers;
}
}
In another application, the query would be:
SELECT s.*, q.*, a.*
FROM sections s
LEFT JOIN questions q ON q.sectionId = s.id
LEFT JOIN answers a ON a.questionId = q.id
WHERE s.id = :sectionId and a.otherColumn = :otherColumn
However in Room I have found out that if you want an object and their relations (like a user and its pets in the example), you only select the object, and the relations are queried in a second query. That would be:
#Query("SELECT * FROM sections WHERE id = :sectionId")
Then in the generated code there would be (pseudo code):
sql = "SELECT * FROM sections WHERE id = :sectionId" // what's inside #Query
cursor = query(sql)
int indexColumn1 = cursor.getColumnIndex(col1)
int indexColumn2
... etc
while (cursor.moveToNext) {
masterObject = new object()
masterObject.property1 = cursor.get(indexColumn1)
... etc
__fetchRelationshipXXXAsYYY(masterObject.relations) // fetch the child objects
}
and this __fetch XXX as YYY method is as follows:
sql = "SELECT field1, field2, ... FROM a WHERE foreignId IN (...)"
similar algo as previously: fetch column indices, and loop through the cursor
So basically it creates 2 queries: one for the master object and one for the relations. The 2nd query is automatically created and we have no control over it.
To get back to my problem where I want relations but also filter on the child column, I'm stuck:
in the 1st query I can't reference the otherColumn column because it doesn't exist
in the #Relation I can't either because the only properties of this annotation are the join column and entity definition
Is this possible in Room or do I have to make the subqueries myself?
Bonus question: why don't they join tables in a single query but create 2 queries instead? Is this for performance reasons?
Edit to clarify what I expected:
That's what I expected to write:
#Query("SELECT s.*, q.*, a.* " +
"FROM sections s " +
"LEFT JOIN questions q ON q.sectionId = s.id " +
"LEFT JOIN answers a ON a.questionId = q.id " +
"WHERE s.id = :sectionId and a.otherColumn = :additionalIntegerFilter")
SectionWithQuestionsAndAnswers fetchFullSectionData(long sectionId);
static class SectionWithQuestionsAndAnswers {
#Embedded Section section;
#Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
List<QuestionWithAnswers> questions;
}
static class QuestionWithAnswers {
#Embedded Question question;
#Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
Answer answer; // I already know that #Relation expects List<> or Set<> which is
// not useful if I know I have zero or one relation (ensured
// through unique keys)
}
That's pseudo code that I imagined to be implemented by Room as the generated code:
function fetchFullSectionData(long sectionId, long additionalIntegerFilter) {
query = prepare(sql); // from #Query
query.bindLong("sectionId", sectionId);
query.bindLong("additionalIntegerFilter", additionalIntegerFilter);
cursor = query.execute();
Section section = null;
long prevQuestionId = 0;
Question question = null;
while (cursor.hasNext()) {
if (section == null) {
section = new Section();
section.questions = new ArrayList<>();
section.field1 = cursor.get(col1); // etc for all fields
}
if (prevQuestionId != cursor.get(questionIdColId)) {
if (question != null) {
section.questions.add(question);
}
question = new Question();
question.fiedl1 = cursor.get(col1); // etc for all fields
prevQuestionId = question.id;
}
if (cursor.get(answerIdColId) != null) { // has answer
Answer answer = new Answer();
answer.field1 = cursor.get(col1); // etc for all fields
question.answer = answer;
}
}
if (section !=null && question != null) {
section.questions.add(question);
}
return section;
}
That's one query, and all my objects fetched.
I find Room Relations hard to work with, not very flexible and much of the work is done under the hood in a way that is hard to really be sure how.
In my projects, most of the time I just create presentation objects - objects dedicated for some UI presentation that can be filled with a custom select.
That way I have much more control over what I want to fetch from DB (i.e. what I really need), and I fill that into that custom presentation object.
I'm just pasting the information provided on the feature request I posted (see my comment on my question):
Hi there - we have recently released a new feature where relational query methods can be defined with Multimap return types. With this new feature, you should be able to achieve the results discussed in this thread. For more info on this new feature, you can check out the following resources:
Define relationships between objects: https://developer.android.com/training/data-storage/room/relationships
Relational Query Methods in ADS 2021: https://youtu.be/i5coKoVy1g4?t=344
The new MapInfo annotation: https://developer.android.com/reference/androidx/room/MapInfo
I know link-only answers aren't great, but I didn't have the opportunity to test this. If someone has a better answer, I'll accept it.
I found a better solution for this. Instead of aliasing all columns you can use #RawQuery annotation.
First of all, add a prefix for embedded table annotation using table name or its alias like #Embedded(prefix = "P.") or #Embedded(prefix = "Post."):
public class UserPost {
#Embedded
private User user;
#Embedded(prefix = "P.")
private Post post;
}
Then in your Dao, create a function to run a raw query, and create another function to run a raw query:
#Dao
public interface UserDao {
String USER_POST_QUERY = "SELECT U.*, P.* FROM User as U " +
"INNER JOIN Post as P ON U.id = P.userId " +
"WHERE P.status = 1";
#RawQuery
LiveData<List<UserPost>> rawQuery(SimpleSQLiteQuery query);
default LiveData<List<UserPost>> getAlertViolationsAsync() {
return rawQuery(new SimpleSQLiteQuery(USER_POST_QUERY));
}
}

Persisting array of strings with greenDao

I'm trying to map an object to database with greenDao. But when it comes to arrays, I don't know how to do it. After receiving JSON from network and deserializing it with GSON, I have objects defined by this class:
public class Car {
Long carId;
String name;
ArrayList<String> listOfLinks;
}
In case of a a different architecture, like this:
public class Car {
Long carId;
String name;
ArrayList<Link> listOfLinks;
}
public class Link {
Long carId;
String link;
}
----
Entity cars = schema.addEntity("Car");
cars.addLongProperty("carId").primaryKey();
cars.addStringProperty("name");
Entity links = schema.addEntity("Link");
links.addStringProperty("name");
links.addIdProperty().primaryKey().notNull().autoincrement();
Property linkProperty = links.addLongProperty("carId").getProperty();
ToMany carToLinks = cars.addToMany(link, linkProperty);
It would is easy. Define some relations, define properties, add foreign key and your done. With arrays I have no clue what to do. Ideas?
That approach is not common when using relational databases.
This is commonly done using to-many relations : instead of using a list of String, you can create a Link entity and then use a list of Link.
Relation toMany is useful when you have a list of your not primitive object, that you can declare like entity that have its own id etc etc etc, and make list of entities (with toMeny). By doing that greenDao makes another table in the base for you new entity with the foreign key of the base entity that contains list. When you have list of primitive type the only way to do is to make converter that converts List into one of the primitive types that greenDao works naturally. You have to do something like this `
import org.greenrobot.greendao.converter.PropertyConverter;
import java.util.Arrays;
import java.util.List;
/**
*DOLE BREEE SQLITE BREEEEEE!!!**
*i choosed to convert List into one string
*that is going to be saved in database, and vice versa
*/
public class GreenConverter implements PropertyConverter, String> {
#Override
public List convertToEntityProperty(String databaseValue) {
if (databaseValue == null) {
return null;
}
else {
List<String> lista = Arrays.asList(databaseValue.split(","));
return lista;
}
}
#Override
public String convertToDatabaseValue(List<String> entityProperty) {
if(entityProperty==null){
return null;
}
else{
StringBuilder sb= new StringBuilder();
for(String link:entityProperty){
sb.append(link);
sb.append(",");
}
return sb.toString();
}
}
}
now above all the properties that are List you have to put
#Convert(converter=yourconverterclass.class, columnType = String.class)
#Entity
public class ShipEntry {
#Id(autoincrement = true)
private long ship_id;
private String name;
private String model;
private String manufacturer;
private String starship_class;
#Convert(converter = GreenConverter.class, columnType = String.class)
private List<String> pilots;
#Convert(converter = GreenConverter.class, columnType = String.class)
private List<String> films ;
}
you can create Converter as a inner class of entitiy, and in that case it has to be declared as staticthat is the only way i have found, but the bad side is that you can not use property that you are converting into query. There might me some typo, but i hope this helps to solve your problem
I also have the same issue, and there no answer (not in official docs, not in google). Please explain how to map List to Entity?
public class Car {
Long carId;
String name;
ArrayList<String> listOfLinks;
}
Can I do something like this?
#Entity(active = true, nameInDb = "CARS")
public class Car {
#Id
private Long id;
#NotNull
#Unique
private String remoteId;
#ToMany(joinProperties = {
#JoinProperty(name = "remoteId", referencedName = "carRemoteId")
})
private List<Links> listOfLinks;
}
#Entity(active = true, nameInDb = "LISTOFLINKS")
public class Links{
#Id
private Long id;
#NotNull
#Unique
private String remoteId;
#SerializedName("listOfLinks")
#Expose
private String listOfLinks;//is it possible?????
private String carRemoteId;
}
Since JPA 2.0, you can use an element collection to persist a Collection of value types. You just need to annotate the attribute with #ElementCollection and the persistence provider will persist the elements of the Collection in an additional database table.
#Entity
public class Author {
#ElementCollection
private List<String> phoneNumbers = new ArrayList<String>();
}
The element collection might seem easier to use than an entity with a one-to-many association. But it has one major drawback: The elements of the collection have no id and Hibernate can’t address them individually.
When you add a new Object to the List or remove an existing one, Hibernate deletes all elements and inserts a new record for each item in the List.
Let’s take a quick look at an example. The following code snippet selects an Author entity and adds a second phoneNumber to the element collection.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author a = em.find(Author.class, 1L);
a.getPhoneNumbers().add("42424242");
em.getTransaction().commit();
em.close();
an element collection is an easy but not the most efficient option to store a list of value types in the database. You should, therefore, only use it for very small collections so that Hibernate doesn’t perform too many SQL statements. In all other cases, a one-to-many association is the better approach.

Categories

Resources