Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

executeSql silently fails on parameterised list value #410

Open
iamleeg opened this issue Feb 10, 2020 · 3 comments · May be fixed by #411
Open

executeSql silently fails on parameterised list value #410

iamleeg opened this issue Feb 10, 2020 · 3 comments · May be fixed by #411

Comments

@iamleeg
Copy link

iamleeg commented Feb 10, 2020

Expected Behavior

Here's my schema:

sqlite> .schema
CREATE TABLE risk (id integer primary key, description varchar, weight integer, severity integer, likelihood integer, type_1 varchar, type_2 varchar, approach_time varchar, approach_type varchar, stage_1 varchar, stage_2 varchar);
CREATE TABLE role (id integer primary key, name varchar, unique(name));
CREATE TABLE skill (id integer primary key, name varchar, unique(name));
CREATE TABLE method (id integer primary key, name varchar, unique(name));
CREATE TABLE risk_role_skill (id integer primary key, risk_id integer, role_id integer, skill_id integer);
CREATE TABLE risk_method (id integer primary key, risk_id integer, method_id integer);

Here's a statement that works in the sqlite3 CLI:

sqlite> select max(weight) from risk inner join risk_role_skill on risk.id = risk_role_skill.risk_id and risk_role_skill.role_id in (1,2,3,4) where stage_2 like
 'Stage0 - Appraisal';
4

Now, I want to run this query in my Android react native app. If I do this:

db.executeSql('select stage_2, max(weight) from risk inner join risk_role_skill on risk.id = risk_role_skill.risk_id and risk_role_skill.role_id in (1,2,3) where stage_2 like ?', [stage])

then it works. However, if I do this (which is what I'm trying to achieve):

db.executeSql('select stage_2, max(weight) from risk inner join risk_role_skill on risk.id = risk_role_skill.risk_id and risk_role_skill.role_id in (?) where stage_2 like ?', [selected_roles, stage])

then the library does nothing. It doesn't call either success or error callback, it just doesn't run the statement. The library doesn't produce any debug logging for this situation, either. I have alternatively tried removing the parentheses around the first parameter (i.e. ...role_id in ? where stage_2..., with no change in behavior.

My expectation is that it's possible to pass a list as a parameter to an SQL statement, and that if I get the syntax wrong, the library will produce an error.

Current Behavior

See the description above: TL;DR the current behavior is no behavior.

Possible Solution

I think the issue is at sqlite.core.js:506 in SQLitePluginTransaction.prototype.addStatement, because you'll coerce my argument list to a string so we end up with SQL that looks like this:

...and role_id in [1,2,3]...

which is not valid. For an array parameter, emit an opening (, then stringify each element, then a closing ).

Steps to Reproduce (for bugs)

See the database schema and example queries above.

Context

I know that my query is correct because I have executed it in the sqlite3 CLI. The impact of this issue is that I can't implement a feature of my risk-management app which requires me to visualize the highest risk level at each stage in a particular project type.

Your Environment

  • React Native SQLite Storage Version used: 4.1.0
  • React Native version used: 0.61.5
  • Operating System and version (simulator or device): Android Simulator API level 29
  • IDE used: VS Code

Debug logs

No logging output is given. I have called SQLite.DEBUG(true), and still don't get any output.

@likern
Copy link

likern commented Feb 28, 2020

Ok, looks like I've found the real bug.
Mulltiple parameters are working correctly, the problem is in react-native-sqlite-storage converts numbers into strings making whole query expression not correct.

Let's say I have table tasks with 3 rows inside.

SELECT COUNT(tasks.id), TYPEOF(COUNT(tasks.id)) FROM tasks;
# 3 | integer

Now I want to compare count value with some literal

SELECT COUNT(tasks.id) == 3 FROM tasks;
# 1

SELECT COUNT(tasks.id) == 3.0 FROM tasks;
# 1

SELECT COUNT(tasks.id) == "3" FROM tasks;
# 0

No type conversion is applied between string and integer in this context.

Both COUNT(tasks.id) and "3" operands have no type affinity according to 3.2. Affinity Of Expressions and then according to 4.2. Type Conversions Prior To Comparison

Otherwise, no affinity is applied and both operands are compared as is

no any type conversion is happening and we compare type INTEGER with TEXT

Then according to 4.1. Sort Order

An INTEGER or REAL value is less than any TEXT or BLOB value

which means comparison 3 == "3" will always be false (because 3 always less than "3").

SELECT COUNT(tasks.id) < "3" FROM tasks;
# 1
SELECT COUNT(tasks.id) >= "3" FROM tasks;
# 0

If this expression is applied somewhere in WHERE or HAVING COUNT(tasks.id) == "1" then it means you will always get empty list of results.

This is a whole realm of possible bugs and very strange noone came across with this.

Bug is in executeSqlStatementQuery function

params[j] = SQLitePluginConverter.getString(queryParams, j, "");

where all parameters are converted into strings.

I suggest that Android's rawQuery function wrap strings with backtics (as strings should be wrapped to distinguish them between integers) or even worse it only allows to bind strings for prepared statements (https://www.sqlite.org/c3ref/bind_blob.html)).

@iamleeg @andpor

@likern
Copy link

likern commented Feb 28, 2020

I've tested and now it works!

// params[j] = SQLitePluginConverter.getString(queryParams, j, "");
params[j] = SQLitePluginConverter.get(queryParams, j, "");

but I need to mention I'm using my own fork https://www.npmjs.com/package/@breeffy/mobbly
where I substituted android SQLite version to https://github.com/requery/sqlite-android
where rawQuery signature has Object[] selectionArgs

public Cursor rawQuery(String sql, Object[] selectionArgs) {
  return rawQueryWithFactory(null, sql, selectionArgs, null, null);
}

while on Android documentation

public Cursor rawQuery (String sql, String[] selectionArgs)

@likern
Copy link

likern commented Feb 28, 2020

The other possible way to fix this (using Android API, where only String[] is available) is to cast string into appropriate type directly.

SELECT 3  == CAST("3" as integer);
# 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants