SQL logic error only when querying on Android - android
I am working on a C++ Android NDK project that relies on a SQLite3 database as a data repository. The database has three tables; column counts are 6, 8, and 6; row counts are 240, ~12.5 million, and ~4 million.
The SQLite driver is being compiled, unmodified, directly into my native library from the SQLite3 amalgamation source code, version 3.19.3.
My problem is that when running the native library, the database query results in "SQL logic error or missing database" . I know the database is where I expect, and is accessible. If I execute the same query against the same database in a desktop environment (rather than on the mobile device), I see the expected results. Furthermore, if I execute the same query on the mobile device using a database housing a subset of the data in the repository (~300 total records), I see the expected results.
Any ideas?
For reference, here is the query I'm using:
WITH
weather_distance AS
(SELECT latitude, longitude, temperature, humidity, time, (ABS((latitude - $lat)) + ABS((longitude - $lon))) AS distance FROM Weather WHERE time BETWEEN $start AND $end),
min_weather_distance AS
(SELECT latitude, longitude, temperature, humidity, time FROM weather_distance WHERE distance = (SELECT MIN(distance) FROM weather_distance)),
solar_distance AS
(SELECT latitude, longitude, ghi, (time - 1800) AS time, (ABS((latitude - $lat)) + ABS((longitude - $lon))) AS distance FROM SolarData WHERE time BETWEEN $start AND $end),
min_solar_distance AS
(SELECT latitude, longitude, ghi, time FROM solar_distance WHERE distance = (SELECT MIN(distance) FROM solar_distance))
SELECT s.time, s.ghi, w.temperature, w.humidity FROM min_weather_distance w INNER JOIN min_solar_distance s ON w.time = s.time ORDER BY s.time ASC
and the code (C++) I'm using to make the query:
const char* getEnvQuery =
"WITH "
"weather_distance AS "
"(SELECT latitude, longitude, temperature, humidity, time, (ABS((latitude - $lat)) + ABS((longitude - $lon))) AS distance FROM Weather WHERE time BETWEEN $start AND $end), "
"min_weather_distance AS "
"(SELECT latitude, longitude, temperature, humidity, time FROM weather_distance WHERE distance = (SELECT MIN(distance) FROM weather_distance)), "
"solar_distance AS "
"(SELECT latitude, longitude, ghi, (time - 1800) AS time, (ABS((latitude - $lat)) + ABS((longitude - $lon))) AS distance FROM SolarData WHERE time BETWEEN $start AND $end), "
"min_solar_distance AS (SELECT latitude, longitude, ghi, time FROM solar_distance WHERE distance = (SELECT MIN(distance) FROM solar_distance)) "
"SELECT s.time, s.ghi, w.temperature, w.humidity FROM min_weather_distance w INNER JOIN min_solar_distance s ON w.time = s.time ORDER BY s.time ASC;\0";
sqlite3_stmt* getEnvStmt;
prepareSqlStatement(getEnvQuery, &getEnvStmt, envdbhandle, "Error preparing SQL statement to retrieve environmental data. SQLite return code: ");
sqlite3_bind_double(getEnvStmt, 1, iter->latitude); //iter is defined above quoted code
sqlite3_bind_double(getEnvStmt, 2, iter->longitude);
sqlite3_bind_double(getEnvStmt, 3, iter->startTime);
sqlite3_bind_double(getEnvStmt, 4, iter->endTime);
__android_log_print(ANDROID_LOG_DEBUG, "SPP/getEnvironment", "Bound parameters: lat=%f, lon=%f, start=%ld, end=%ld", iter->latitude, iter->longitude, iter->startTime, iter->endTime);
int rc = sqlite3_step(getEnvStmt);
__android_log_print(ANDROID_LOG_DEBUG, "SPP/getEnvironment", "step(getEnvStmt) = %d", rc);
int errCode = sqlite3_extended_errcode(envdbhandle);
__android_log_print(ANDROID_LOG_DEBUG, "SPP/getEnvironment", "Most recent SQLITE error code: %s. Message: %s", sqlite3_errstr(errCode), sqlite3_errmsg(envdbhandle));
while(rc == SQLITE_ROW)
{
EnvironmentDatum envData;
int dbTime = sqlite3_column_int(getEnvStmt, 0);
envData.UnixTime = timeconvert::secondsOfYearToUNIXTime(dbTime, year);
__android_log_print(ANDROID_LOG_DEBUG, "SPP/getEnvironment", "EnvironmentDatum dbTime=%d, UnixTime=%f", dbTime, envData.UnixTime);
envData.GHI = sqlite3_column_double(getEnvStmt, 1);
envData.Temperature = sqlite3_column_double(getEnvStmt, 2);
envData.Humidity = sqlite3_column_double(getEnvStmt, 3);
envCollection.push_back(envData);
rc = sqlite3_step(getEnvStmt);
}
sqlite3_finalize(getEnvStmt);
Important debugging info:
sqlite3_stmt* verQueryStmt;
prepareSqlStatement("select sqlite_version();\0", &verQueryStmt, envdbhandle, "Error getting driver version. Error code:");
sqlite3_step(verQueryStmt);
std::string sqliteVersion = parseSqliteStringColumn(verQueryStmt, 0);
sqlite3_finalize(verQueryStmt);
__android_log_print(ANDROID_LOG_DEBUG, "SPP/buildScenario", "sqlite version=%s", sqliteVersion.c_str()); // outputs "sqlite version=3.19.3"
__android_log_print(ANDROID_LOG_DEBUG, "SPP/buildScenario", "env db readability=%s", (sqlite3_db_readonly(envdbhandle, "main") == 1 ? "READONLY" : (sqlite3_db_readonly(envdbhandle, "main") == 0 ? "READ/WRITE" : "NOT CONNECTED"))); // outputs "READ/WRITE"
Per request, here is prepareStatement:
static int prepareSqlStatement(const char* query, sqlite3_stmt** statement, sqlite3* db, const char* failMsg)
{
int returnCode = sqlite3_prepare(db, query, -1, statement, NULL);
if(returnCode != SQLITE_OK || statement == NULL)
{
int errCode = sqlite3_extended_errcode(dbhandle);
std::cout << "Most recent SQLITE error code: " << sqlite3_errstr(errCode) << ". Message: " << sqlite3_errmsg(dbhandle) << std::endl;
reportError(failMsg, -1 * returnCode);
}
return returnCode;
}
And in anticipation, here is reportError:
static void reportError(const char* message, int errorCode)
{
std::stringstream ss;
ss << message << errorCode;
throw std::runtime_error(ss.str());
}
When intermediate results of the query become too large, the database must swap out some data into a temporary file.
6410 is SQLITE_IOERR_GETTEMPPATH, which means that none of the temporary file storage locations are accessible.
Android doesn't have any of the default Unix paths. The built-in database framework compiles its copy of the SQLite library with SQLITE_TEMP_STORE = 3. If you want to get actual temporary files instead, you should put them into whatever directory is returned by Context.getCacheDir(); this would require setting the SQLITE_TMPDIR environment variable.
Related
Room/SQLite returning two different results in one query
So I've got the following queries: //returns min max value my odomoter field must have #Query("SELECT MAX(odometer) FROM MaintenanceRecord WHERE vehicleId = :maintenanceVehicleId AND " + "maintenanceTs < :dateAsLong") Maybe<Float> getMinAllowedOdometer(long dateAsLong, int maintenanceVehicleId); //returns the max value my odometer field is allowed to have #Query("SELECT MIN(odometer) FROM MaintenanceRecord WHERE vehicleId = :maintenanceVehicleId AND " + "CAST(strftime('%s',DATE(maintenanceTs/1000,'unixepoch')) AS integer) > " + "CAST(strftime('%s',DATE(:dateAsLong/1000,'unixepoch')) AS integer)") Maybe<Float> getMaxAllowedOdometer(long dateAsLong,int maintenanceVehicleId); One function returns the min value the odometer column must have, and the other query returns the max value the odometer column is allowed to have. The issue is that BOTH functions are executed after each other since I need to subscribe to both functions. That's bad practice, In my honest opinion. Can I put these two queries in one query and return Maybe<Float, Float> as a result? My other solution would be running these functions synchronously; instead of Maybe<Float>, I would directly return Float.
You can use conditional aggregation to get both columns in a single query: SELECT MAX(CASE WHEN maintenanceTs < :dateAsLong THEN odometer END) AS max_odometer, MIN(CASE WHEN CAST(strftime('%s', DATE(maintenanceTs / 1000, 'unixepoch')) AS INTEGER) > CAST(strftime('%s', DATE(:dateAsLong / 1000,'unixepoch')) AS INTEGER) THEN odometer END) AS min_odometer FROM MaintenanceRecord WHERE vehicleId = :maintenanceVehicleId;
I need to separate the text from a string based on column names
I am working on OCR based Android app, getting this text as string from the attached image dynamically (getting the text in Horizontal Direction from the image) Text from Image: "Part Name Part Cost Engine Oil and Oil Filter Replacement Rs 10K Alf Filter Rs 4500 Cabin AC Micro Filter Rs 4000 Pollen Filter Rs 1200 - 1500 AC Disinfectant Rs 3000 Fuel Filter Rs 6000 - 8000 Spark Plug Set Replacement (Applicable in TFSI / Petrol Car Range) Rs 10K Body Wash, Basic Clean 8. Engine Degrease Rs 3000 Body Wax Polish Detailed Rs 7000 - 8000 Car interior Dry Clean with Genn Clean Rs 8000 - 10000 Wheel Alignment \u0026 Balancing Rs 6000 - 7000 Brake Pads Replacernent (Pair) Rs 30K - 32K Brake Disc Replacernent (Pair) Rs 30K - 35K ..........". I need to separate the Part Name and Part Cost(just 2 columns i.e Part Name, Part Cost) (ignore all extra text from the column heading). Separate the values from String and should store it in SQLIte Database Android. I am stuck how to get the values and separate them.
The text returned from the OCR isn't ideal. The first thing you should do is check if whatever OCR solution can be configured to provide a better output. Ideally, you want the lines to be separated by newline characters and the space between the columns to be interpreted as something more useful, such as a tab character. If you have no way of changing the text you get, you'll have to find some way of parsing it. You may want to look into using a parser, such as ANTLR to make this easier. The following observations may help you to come up with a parsing strategy: Column 2 items all start with "Rs" or "Upto Rs". Column 2 items end with: A number (where a number is allowed to be a string of digits [0-9.], optionally followed by a "K" "Lakh" Column 1 items don't begin with a number or "Lakh" So a basic algorithm could be: List<String> column1 = new ArrayList<String>(); List<String> column2 = new ArrayList<String>(); String[] tokens = ocrString.split(" "); List<String> column = column1; String item = ""; for (int i = 0; i < tokens.length; i++) { String token = tokens[i]; String nextToken = i == tokens.length - 1 ? "" : tokens[i+1]; if (column == column1) { if (token == "Rs" || (token == "Upto" && nextToken == "Rs")) { column = column2; column.add(item); item = ""; i--; continue; } item += " " + token; } else { item += " " + token; if (/*token is number or "Lakh" and nextToken is not*/) { column.add(item); item = ""; column = column1; } } }
Android How to avoid junk characters in a string while getting location from gps latitude/longitude
I am receiving a location address from GPS(Latitude/Longitude) programmatically; however, sometimes I receive a location address with special/unwanted characters. When I send the addresses containing special/unwanted characters to the server, they are declined by the server. Example 1: , delhi, ä¸å?½ Example 2: सà¥à¤ How can I prevent this issue?
You can use regex to avoid this problem and match all the characters are of US-ASCII code point type. String testText [] = new String[] { "delhi", "ä¸å?½", "à¤", "à¥à¤• ", "tessst½", "some valid test text1213" }; for (String str : testText) { if (str.matches("\\A\\p{ASCII}*\\z")) { //do something Log.d("TAG", str + " - String is valid"); } } If you are using Java 8 you can do it this way: textInfoFromLocation.chars().allMatch(c -> c < 128) In case if you just want to check each character, do the following: for (Character c : str.toCharArray()) { if (c > 127) //character is invalid Log.d("TAG","Character " + c + " is invalid"); } More information could be found in this answer
Sqlite Android order by & group_concat
I'm using the sqlite3 : 3.8.2 with sqliteman (Ububuntu 14.04) to try the query that I need in my program Android. Android version 4.0+. I have 3 table to use to obtain data I found this problem: when I use this query in sqliteman SELECT st.nome AS nome, st.cognome AS cognome, (SELECT ae.usercode AS usercode FROM assenze ae WHERE st.ultimo_evento = ae.fk_evento) AS ultimo_evento, (SELECT GROUP_CONCAT( ev.nome_evento, ", " ) AS nome_evento FROM eventi ev WHERE ev.studente_id = st.id ) AS nome_evento FROM studenti st WHERE st.classe_ident ='4297' ORDER BY st.cognome the output is: EP, USC, R1, R, A ... perfect for me but when I use it in android with db.rawQuery() the result is A, EP, R, R1, USC ... no good for me also seems to be the same result of this query (execute in sqliteman and android): SELECT st.nome AS nome, st.cognome AS cognome, ae.usercode AS ultimo_evento, GROUP_CONCAT( ev.nome_evento, ", " ) AS nome_evento FROM studenti st LEFT JOIN eventi ev ON st.id = ev.studente_id LEFT JOIN assenze ae ON st.ultimo_evento = ae.fk_evento WHERE st.classe_ident ='4297' GROUP BY st.id ORDER BY st.cognome Probably it's my fault but I'm still trying to find a solution... Thanks seems that it works also without ORDER BY ev.data_ora_evento. But in android still not working...
The group_concat() documentation says: The order of the concatenated elements is arbitrary. However, you could try to force an order by executing group_concat() on a subquery that is sorted: SELECT GROUP_CONCAT(ev.nome_evento, ", ") AS nome_evento FROM (SELECT nome_evento FROM eventi WHERE studente_id = st.id ORDER BY whatever) AS ev
Checking data enter by user with parameter set, sqlite android
i'm doing a checking from data enter by user with data that already set. As example, data save in sqlite is 60-80. If user input is 88. i want to check the user input which is 88 with data 60-80, so that i can come out with appropriate advice Help me. i did like this public String getRangeFromUserInput(int b1, int b2, int b3, int a1, int a2, int a3) { if (((70<b1<<100)||(70<b2<<100)||(70<b3<<100))&&((70<a1<<135)||(70<a2<<135)||(70<a3<<135))) Toast.makeText(GlucoseAdd.this, "Your glucose level is at normal range", Toast.LENGTH_LONG).show(); else if (((50<b1<<60)||(50<b2<<60)||(50<b3<<60))&&((135<a1<<180)||(135<a2<<180)||(135<a3<<180))) Toast.makeText(GlucoseAdd.this, "You need to start diet. The diet should " + "also be low in fat and high in dietary fibre. Regular exercise " + "is important in maintaining balanced blood glucose levels.", Toast.LENGTH_LONG).show(); else if (((b1<50)||(b2<50)||(b3<50))&&((180<a1<<200)||(180<a2<<200)||(180<a3<<200))) Toast.makeText(GlucoseAdd.this, "You are in danger state.", Toast.LENGTH_LONG).show(); return null; } but when i enter data all prompt out the first one.. Why?
Since the data stored in your DB table is in the format of string i.e. 60-80 So you'll need to convert the user input value (for ex. 88) into i.e. 80-100 using the following code: public String getRangeFromUserInput(String userInput) { return getRangeFromUserInput(Integer.parseInt(userInput)); } public String getRangeFromUserInput(int userInput) { if (userInput >= 60 && userInput < 80) return "60-80"; else if (userInput >= 80 && userInput < 100) return "80-100"; // ...more range checks return null; } Now, use the output of the method getRangeFromUserInput() which is a string (for ex. 80-100) in the where clause of your query as follows: String where = "range = '" + getRangeFromUserInput("88") + "'"; Assuming the column name is range in your table. If the query returns a row then you can read the advice corresponding to the range using cursor.getString(cursor.getColumnIndex("advice")) Assuming the column name is advice in your table.