Use Blob field as query parameter in SQLite - java

I'm working on Android and i'm studing use of SQLite database. I already known how to do operation like create query insert etc.. for the database.
Just for example, suppose to have the following table definition:
CREATE TABLE bean84_b (id INTEGER PRIMARY KEY AUTOINCREMENT, column_bean BLOB);
Then SQL query to execute is:
SELECT id, column_bean FROM bean84_b WHERE column_bean=?
The java code to execute above query is:
byte[] param1=...
String[] args={String.valueOf(param1)};
Cursor cursor = database(). rawQuery("SELECT id, column_bean FROM bean84_b WHERE column_bean=?", args);
Is it possible to use a BLOB column like SELECT parameter?

This is a design bug in the Android database API.
query and rawQuery accept only string parameters. execSQL accepts any Object parameters but does not return results. SQLiteStatement accepts parameters of any type but allows only queries that return a single value.
Another type you can bind blob with another fields
http://www.programcreek.com/java-api-examples/index.php?api=android.database.sqlite.SQLiteStatement
In the Android database API, execSQL() is the only function where the
parameters are not String[] but Object[]:
byte[] blob = ...;
db.execSQL("DELETE FROM t WHERE my_blob = ?", new Object[]{ blob });

Yes, it is possible. You have to simply extend CursorFactory, bind values to its SQLiteQuery that will be available in NewCursor method.
Xamarin code snippet:
internal sealed class SQLiteCursorFactory : Java.Lang.Object, SQLiteDatabase.ICursorFactory
{
private Dictionary<int, object> _selectionArgs;
internal SQLiteCursorFactory(Dictionary<int, object> selectionArgs)
{
_selectionArgs = selectionArgs;
}
ICursor SQLiteDatabase.ICursorFactory.NewCursor(SQLiteDatabase db, ISQLiteCursorDriver masterQuery, string editTable, SQLiteQuery query)
{
foreach(var key in _selectionArgs.Keys)
{
var val = _selectionArgs[key];
if(val == null)
{
query.BindNull(key);
}
else if(val is int)
{
query.BindLong(key, (int)val);
}
else if (val is long)
{
query.BindLong(key, (long)val);
}
else if (val is double)
{
query.BindDouble(key, (double)val);
}
else if (val is string)
{
query.BindString(key, (string)val);
}
else if (val is byte[])
{
query.BindBlob(key, (byte[]) val);
}
}
return new SQLiteCursor(masterQuery, editTable, query);
}
}
Usage:
dbInstance.RawQueryWithFactory(new SQLiteCursorFactory(selectionArgs), query, null, null);

Related

Howto make SELECT queries with variable size for filtering parameters?

With RoomBD I could make in a DAO a query like this one:
#Query("SELECT * FROM table "
+ "WHERE field1 LIKE :param1 "
+ "OR field1 LIKE :param2 "
+ "OR field1 LIKE :param3")
public LiveData<List<Table>> filterTableData(String param1, String param2, String param3);
...but the point is that i need to do this kind of SELECT statement with a variable amount of filtering parameters, so I don't have to redundantly create multiple filterTableData methods in the DAO that would do exactly the same thing but with a different amount of filtering parameters. Is there a way to do so?
// XXX What I'd like it to be (SQL is pseudocode here)
#Query("SELECT * FROM table WHERE field1 LIKE :any_of_the_parameters")
public LiveData<List<Table>> filterTableData(ArrayList<String> params);
I haven't tested this yet, but according to this link, this might do the job for OR-like filtering:
#Query("SELECT * FROM table WHERE field1 IN(:params)")
LiveData<List<Table>> filterTableData(List<String> params);
However, I don't have any idea for AND-like filtering (yet)...
add this in your database class
public byte [] get(String query){
try {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(query,null);
if (cursor.moveToFirst()){
return cursor.getBlob(0);
}
return null;
} catch (Exception e){
e.printStackTrace();
return null;
}
}
and use this for retrive image
db_class = new DatabaseClass(MainActivity.this);
byte [] img = db_class.get("SELECT IMG FROM data WHERE id = 1");
if (img != null){
Bitmap bitmap = convertbytearyrtobitmap(img);
imageView.setImageBitmap(bitmap);
}
Create the query programatically with the list of parameters.

How to read CLOB type to Spring Boot? [duplicate]

I have a table in Database where datatype of a column(STATUS) is CLOB.I need to read that STATUS
create table STATUS_TABLE
(
STATE_ID number(20,0),
STATUS clob
)
I am trying to read CLOB column as below
String getStatus = "SELECT STATUS FROM STATUS_TABLE WHERE STATE_ID="+id;
Query statusResults = session.createSQLQuery(getStatus);
List statusRes = statusResults.list();
if ((statusRes != null) && (statusRes.size() > 0)) {
oracle.sql.CLOB clobValue = (oracle.sql.CLOB) statusRes.get(0);
status = clobValue.getSubString(1, (int) clobValue.length());
log.info("Status->:" + status.toString());
}
And getting the error as
java.lang.ClassCastException: $Proxy194 cannot be cast to oracle.sql.CLOB
How can i read clob data from DB and convert to String ?
Here is the corrected version, and an explanation appears below the code:
Query query = session.createSQLQuery("SELECT STATUS FROM STATUS_TABLE WHERE STATE_ID = :param1");
query.setInt("param1", id);
query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List statusRes = query.list();
if (statusRes != null) {
for (Object object : statusRes) {
Map row = (Map)object;
java.sql.Clob clobValue = (java.sql.Clob) row.get("STATUS");
status = clobValue.getSubString(1, (int) clobValue.length());
log.info("Status->:" + status.toString());
}
}
Problems I saw with your code:
You were building a raw query string using concatenation. This leaves you vulnerable to SQL injection and other bad things.
For whatever reason you were trying to cast the CLOB to oracle.sql.CLOB. AFAIK JDBC will return a java.sql.Clob
You are performing a native Hibernate query, which will return a list result set whose type is not known at compile time. Therefore, each element in the list represents one record.

Problems with JDBCTemplate retrieving value set by LAST_INSERT_ID() mySQL trick

I'm having some trouble using mySQL and Spring JDBCTemplate. I have an INSERT ... ON DUPLICATE KEY UPDATE which increments a counter, but uses the LAST_INSERT_ID() trick to return the new value in the same query through the last generated id. I'm using JDCTemplate.update() with the PreparedStatementCreator and GeneratedKeyHolder, but I'm getting multiple entries in the KeyHolder.getKeyList(). When I run it manually in the mySQL client, I'm getting 2 rows affected (when it hits the DUPLICATE KEY UPDATE), and then the SELECT LAST_INSERT_ID(); gives me the correct value.
UPDATE: It looks like the 3 entries in the keyList all have 1 entry each, and they are all incrementing values. So keyList.get(0).get(0) has the value that should be returned, and then keyList.get(0).get(1) is the previous value +1, and then keyList.get(0).get(2) is the previous value +1. So for example, if 4 is the value that should be returned, it gives me 4, 5, 6 in that order.
The relevant code is:
CREATE TABLE IF NOT EXISTS `ClickChargeCheck` (
uuid VARCHAR(50),
count INTEGER Default '0',
CreatedOn Timestamp DEFAULT '0000-00-00 00:00:00',
UpdatedOn Timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
String CLICK_CHARGE_CHECK_QUERY = "INSERT INTO ClickChargeCheck (uuid,count,CreatedOn) VALUES(?,LAST_INSERT_ID(1),NOW()) ON DUPLICATE KEY UPDATE count = LAST_INSERT_ID(count+1)";
...
...
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
centralStatsJdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(CLICK_CHARGE_CHECK_QUERY, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, uuid);
return ps;
}}, keyHolder);
int count = keyHolder.getKey().intValue();
For me, I ran into the same problem. For INSERT...ON DUPLICATE KEY UPDATE, GeneratedKeyHolder is throwing an exception because it expects there to only be one returned key, which is not the case with MySQL. I basically had to implement a new implementation of KeyHolder which can handle multiple returned key.
public class DuplicateKeyHolder implements KeyHolder {
private final List<Map<String, Object>> keyList;
/* Constructors */
public Number getKey() throws InvalidDataAccessApiUsageException, DataRetrievalFailureException {
if (this.keyList.isEmpty()) {
return null;
}
Iterator<Object> keyIter = this.keyList.get(0).values().iterator();
if (keyIter.hasNext()) {
Object key = keyIter.next();
if (!(key instanceof Number)) {
throw new DataRetrievalFailureException(
"The generated key is not of a supported numeric type. " +
"Unable to cast [" + (key != null ? key.getClass().getName() : null) +
"] to [" + Number.class.getName() + "]");
}
return (Number) key;
} else {
throw new DataRetrievalFailureException("Unable to retrieve the generated key. " +
"Check that the table has an identity column enabled.");
}
}
public Map<String, Object> getKeys() throws InvalidDataAccessApiUsageException {
if (this.keyList.isEmpty()) {
return null;
}
return this.keyList.get(0);
}
public List<Map<String, Object>> getKeyList() {
return this.keyList;
}
}
If you just use this class where you would otherwise use GeneratedKeyHolder, everything works. And I found that you don't even need to add ... ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id) ... in your insert statement.
Turns out that mySQL's PreparedStatement runs that query, and says it returns 3 rows. So the getGeneratedKeys() call (delegated to StatementImpl.getGeneratedKeysInternal()) which, since it sees 3 rows returned, grabs the first LAST_INSERT_ID() value, and then increments from there up to the number of rows. So, I'll have to grab the GeneratedKeyHolder.getKeyList() and grab the first entry, and get it's first value and that'll be the value I need.

Making a better android sqlite query

Currently I have a query that looks at the table and returns the newest row, reads that row and sets the string to the value in the chosen column index.
At the moment I have 9 columns so I have end up making 9 methods for returning each column just to return a string and set it to each individual textview.
Do I make a cursor to return the row and then set the column index when I am setting the textviews. Something like
cursor.getString(1)
This is my current query
public String getName() {
// TODO Auto-generated method stub
String[] columns = new String[] { KEY_ROWID, KEY_NAME, KEY_COLUMN2,
KEY_COLUMN3, KEY_COLUMN4, KEY_COLUMN5, KEY_COLUMN6, KEY_COLUMN7, KEY_COLUMN8 };
Cursor c = mDb.query(true, DATABASE_TABLE, columns, null, null, null,
null, "_id DESC", "1");
if (c != null) {
c.moveToFirst();
String name = c.getString(1);
return name;
}
return null;
}
You could easily change getName() to something field-agnostic, such as:
public String getField(String fieldName) {
...
String s = cursor.getString(cursor.getColumnIndexOrThrow(fieldName));
}
And use it like:
String s = mDb.getField(mDb.KEY_NAME);
A problem with this approach is that you can't use getString() for a numerical column, so you wind up with multiple methods for each datatype supported in your table. A way to tackle this is to look into the cursor's getType() method or just return a complete data structure from your database access methods. Say you have:
DB Column Field Type
FOO varchar
BAR integer
BAZ varchar
If you define a class, say, MyRowMapper you can do something like this:
public MyRowMapper getRow(String... queryParameters) {
//query table based on queryParameters
MyRowMapper mapper = null;
if (cursor.moveToFirst()) {
String foo = cursor.getString(cursor.getColumnIndexOrThrow("FOO"));
Integer bar = cursor.getInteger(cursor.getColumnIndexOrThrow("BAR"));
String baz = cursor.getString(cursor.getColumnIndexOrThrow("BAZ"));
mapper = new MyRowMapper(foo, bar, baz);
}
cursor.close(); // NEVER FORGET TO CLOSE YOUR CURSOR!
return mapper;
}
This is just abstract mailercode, so take the syntaxt with a grain of salt. But hopefully you get the idea.

SQLite query with byte[] WHERE clause

From an Android SQLite database point of view - I have a table which has a field with BLOB type and then I want to query this table contents based on this BLOB field.
I can insert my BLOB field using ContentValues and retrieve it using:
cursor.getBlob(0)// index
I just can't figure out how to query this table contents based on this BLOB field and have found nothing about this problem.
You can't query the (textual? binary? other?) contents of a blob.
If you look, you'll see the contents is in hex:
EXAMPLE: X'53514C697465'.
Suggestions:
Create a new text column, e.g. "blob_index". You can search on the "index" column, then fetch the blob.
Alternatively, just store the data as "text".
I found that you can query on a blob. One needs to use the hex() function on the query.
For example I'm using UUIDs in my database rows as a unique key that I can generate locally and still be sure of uniqueness on the server.
CREATE TABLE example (_ID INTEGER PRIMARY KEY AUTOINCREMENT,
uuid BLOB NON NULL UNIQUE,
...)
When inserting data the following works:
final ContentValues values = new ContentValues(4);
values.put(Contract.Line.COL_UUID,
UuidFactory.toBlob(uuid));
Given a query URI in the form:
content://package.example.com/example/uuid/11112222-3333-0444-0555-666677778888
the query becomes:
final SQLiteDatabase db = mHelper.getReadableDatabase();
return db.query(table, projection,
"hex(uuid) = ?",
new String[] { UuidFactory.toHex(uri.getLastPathSegment()) },
null, null, null, null);
In UuidFactory (which also contains the code to generate new UUIDs) the follow static functions are defined thus:
#NonNull
public static String toHex(#NonNull final UUID uuid) {
return String.format("%016X%016X",
uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits());
}
#NonNull
public static String toHex(#NonNull final String uuid) {
return toHex(UUID.fromString(uuid));
}
#NonNull
public static byte[] toBlob(#NonNull final UUID uuid) {
final ByteBuffer buf = ByteBuffer.allocate(16);
buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getLeastSignificantBits());
return buf.array();
}
And for completeness:
#NonNull
public static UUID fromBlob(#NonNull final byte[] array) {
final ByteBuffer buf = ByteBuffer.allocate(16);
buf.mark();
buf.put(array);
buf.reset();
final long msb = buf.getLong();
final long lsb = buf.getLong();
return new UUID(msb, lsb);
}

Categories

Resources