I'm actually working on a cordova application that is reading (not writing) a .mbtiles file. That is a database filed with tiles in order to show an offline map with leaflet.
Everything concerning the file (call it test.mbtiles) is working : downloading and openning. But there is still a big issue. I'm not able to read the tables. Here's the non functionnal sample of my code :
getTileUrl: function (tilePoint, callBack) {
var x = tilePoint.x;
var y = tilePoint.y;
var z = tilePoint.z;
var base64Prefix = 'data:image/png;base64,';
var db2 = new window.sqlitePlugin.openDatabase({name: 'test.mbtiles'});
db2.transaction(function(tx) {
tx.executeSql("SELECT tile_data FROM images INNER JOIN map ON images.tile_id = map.tile_id WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?", [z, x, y], function (tx, res) {
if(res.rows.length>0) {
console.log("success");
var src = base64Prefix + res.rows.item(0).tile_data;
callBack(src);
}
else {
console.log("response : no data");
}
}, function (tx, er) {
console.log('error with executeSql : ', er.message);
return;
});
});
}
So now everytime I try to use the offline map (using tiles from the test.mbtiles which is present on the device) i get this error :
error with executeSql : no such table: images (code 1): , while compiling: SELECT tile_data FROM images INNER JOIN map ON images.tile_id = map.tile_id WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?
So I assume that the file I'm getting, the test.mbtiles is not good because it doesn't have any images table. But that's wrong. I used SQLite Database Browser in order to look at my database scheme, and found that the images table is here and filled with the right data.
Has anyone else had this error and know how to fix it ?
Btw I'm using latest release versions of SQLite Plugin and Cordova, and mainly testing on android (will port to ios if android works). And as much as possible i would like to use only .js files, no addition in any java class or such (would destroy the whole workflow).
Thanks.
The MBTiles spec stores tiles in a table called tiles, not images.
Related
I have developed an android app in react-native and expo. I have also published the app on google play.
Now, I have made some modifications on my SQLite DB tables locally.
Suppose, before the schema of a table was like this:
CREATE TABLE expenditures (id integer primary key, max_amount REAL not null);
And now I would like to change it to this:
CREATE TABLE expenditures (id integer primary key, max_amount TEXT not null);
Is there any way to run a method after a new update/upgrade on a production app (google play store)? That way I can alter the tables only once after the upgrade, and other newly installed users won't be affected by this function. I found two methods on native android:
onCreate: Called for the first time when creation of tables are needed.
onUpgrade: This method is called when database version is upgraded.
But since I have developed my app with react-native and expo, I can't use the above methods. Although I have found onUpgrade in the expo code, I am not sure how to use this feature in expo.
Or is there any better way to handle database migrations on a published app in react-native and expo?
I don't think you can really use the versioning stuff you linked to, as that will drop your db and recreate it from scratch, so you would lose your data.
A simple solution to this is to manually keep track of migrations you've already executed in a table. Then you can create this table if it doesn't exist yet (which can be done in a very dumb way by first trying to query it, and if that fails, create it). If you have a list of all known migrations in order, you can just drop items that already have an entry in the table and run the remaining ones.
From an old Cordova application I wrote this code (yeah it's really old, it's still using Require JS to define the module):
/**
* Provide access to an SQL database, using the SQLite plugin for
* Cordova devices so we aren't limited in how much data we can store,
* and falling back to browser native support on desktop.
*
* Unfortunately webSQL is deprecated and slowly being phased out.
*/
define(['require', 'module', 'deviceReady!'], function(require, module, isCordova) {
'use strict';
var dbRootObject = isCordova ? window.sqlitePlugin : window,
config = module.config();
if (typeof dbRootObject.openDatabase == 'undefined') {
window.alert('Your browser has no SQL support! Please try a Webkit-based browser');
return null;
} else {
var db = dbRootObject.openDatabase(config.dbName, '', 'Direct Result database', null),
transaction = function(callback) {
// We go through this trouble to automatically provide
// error reporting and auto-rollback.
var makeFacade = function(t) {
return {
sql: function(sql, args, okCallback, errorCallback) {
var okFn, errFn;
if (okCallback) {
okFn = function(t, r) { return okCallback(makeFacade(t), r); };
} else {
okFn = null;
}
if (errorCallback) {
errFn = function(t, e) { console.log('SQL error: '+sql, e); return errorCallback(makeFacade(t), e); };
} else {
errFn = function(t, e) {
// It's important we throw an exn,
// else the txn won't be aborted!
window.alert(e.message + ' sql: '+sql);
throw(e.message + ' sql: '+sql);
};
}
return t.executeSql(sql, args, okFn, errFn);
}
};
};
return db.transaction(function(t) {
return callback(makeFacade(t));
}, function(e) { console.log('error'); console.log(e); });
},
// We're going to have to create or own migrations, because
// both the Cordova SQLite plugin and the Firefox WebSQL
// extension don't implement versioning in their WebSQL API.
migrate = function(version, upFn, done, txn) { // "Down" migrations are currently not supported
var doIt = function(t) {
t.sql('SELECT NOT EXISTS (SELECT version FROM sqldb_migrations WHERE version = ?) AS missing',
[version], function(t, r) {
if (r.rows.item(0).missing == '1') {
upFn(t, function() {
t.sql('INSERT INTO sqldb_migrations (version)'+
'VALUES (?)', [version], done);
});
} else {
done(t);
}
});
};
if (txn) doIt(txn);
else transaction(doIt);
},
maybeRunMigrations = function(callback) {
var migrations = [],
addMigration = function(name, migration) {
migrations.push([name, migration]);
},
runMigrations = function(t) {
if (migrations.length === 0) {
callback(t);
} else {
var m = migrations.shift(),
name = m[0],
migration = m[1];
migrate(name, migration, runMigrations, t);
}
};
// ADD MIGRATIONS HERE. The idea is you can just add migrations
// in a queue and they'll be run in sequence.
// Here are two example migrations
addMigration('1', function (t, done) {
t.sql('CREATE TABLE people ('+
' id integer PRIMARY KEY NOT NULL, '+
' initials text NOT NULL, '+
' first_name text NOT NULL, '+
' family_name text NOT NULL, '+
' email text NOT NULL, ', [], done);
});
addMigration('2', function(t, done) {
t.sql('ALTER TABLE people ADD COLUMN phone_number text', [], done);
});
transaction(function(t) {
t.sql('CREATE TABLE IF NOT EXISTS sqldb_migrations ('+
' version int UNIQUE, '+
' timestamp_applied text NOT NULL DEFAULT CURRENT_TIMESTAMP '+
')', [], function (t, r) { runMigrations(t, migrations); });
});
};
// Expose "migrate" just in case
return {transaction: transaction, migrate: migrate, maybeRunMigrations: maybeRunMigrations};
}
});
You'll also need to take a lot of care, as I found out the hard way you cannot actually alter or even drop columns with SQLite (or at least not with the Cordova plugin at the time I wrote this code)! So also be very careful with constraints or you'll end up painting yourself into a corner.
I have not tried it, but it might be possible if you rename the old table, create the new one again with the changed columns and then copy over the data.
you can put the sql alteration files in assets folder android/app/src/main/assets/ like
<version>.sql -> 1.sql or 2.sql
and these file can contain the migration query like
alter table NOTE add NAME TEXT;
and trigger these query according to version of app in onUpgrade() method
I have a sql lite database in my android project that gets fed its data from a web service giving JSON. The JSON is straight forward, no tags. It simply has the rows of table data in the same field order as the table in the database.
JSON
[["123","ny","45"],
["456","nj","76"],
["778","ca","33"]]
SQL Lite Customer Table CustomerID,State,NumofItems
How can I import this into the database without the overhead of creating classes for each table etc.. some tables have over 50 fields.
I am using Xamarin if it somehow makes things easier
It depends upon what you mean by without a lot of coding and how many rows you are populating ;-)
Using dynamics and Json.Net you can use DeserializeObject to obtain an array of dynamics and loop through those array elements and construct a SQL Insert statement bypassing column names and class/model definitions of each table... Using dynamics is slower but unless you are populating a hundred thousand rows every import/app launch/.... this works fine...
This is how I am populating a bunch of tables in a case very much like yours to import numeric sequences for drawing SparkLines within RecyclerView/CardViews, I removed the error checking to simplify the answer:
Batteries_V2.Init();
var sqlConn = new SQLiteConnection("foobar.db");
sqlConn.CreateCommand("create table Customer (CustomerID INT PRIMARY KEY NOT NULL, State TEXT NOT NULL, NumofItems INT NOT NULL)").ExecuteNonQuery();
var jsonString = #"
[[""123"",""ny"",""45""],
[""456"",""nj"",""76""],
[""778"",""ca"",""33""]]"
;
dynamic jsonResponse = JsonConvert.DeserializeObject(jsonString);
sqlConn.BeginTransaction();
foreach (var item in jsonResponse)
{
string aBunchOfValues = "";
var noOfColumns = item.Count;
for (int i = 0; i < noOfColumns; i++)
{
var isString = !((string)item[i]).TryParse(out float n);
aBunchOfValues += $"{(isString ? '"' : ' ')}{item[i]}{(isString ? '"' : ' ')} {(i < noOfColumns - 1 ? ',' : ' ')}";
}
var sqlInsertString = $"INSERT INTO Customer VALUES ({aBunchOfValues});";
sqlConn.CreateCommand(sqlInsertString).ExecuteNonQuery();
}
sqlConn.Commit();
Note: This is using FKrueger's sqlite-net to provide the SQLiteConnection, CreateCommand and the transaction, this is on top of ESink's SQLitePCL.raw, adjust accordingly...
I have an app that uses WebSQL transactions to create a table, insert data, and retrieve information from a database.
The app works fine on the Ripple Nexus (Galaxy) but not on the device (Galaxy Note 5) nor the emulator. The UI is there: text boxes, labels, buttons, etc. However, according to my notice, the operations on the database are not performed.
What can I do?
There are not enough details here to tell what is going on and no code samples. Note that ripple is using http and your app on the device is probably using a non http:// calling location. Another possible issue is that on your machine there is web connectivity to a local http endpoint and on the device the end point would not be available unless it is public on the internet / available to the device.
Here is sample of my code, open database and populate data tables:
document.addEventListener('deviceready', onDeviceReady.bind(this), false);
var db = openDatabase('DUTIESDB', '1.0', 'Test DB', 2 * 1024 * 1024);
db.transaction(function (tx) {
tx.executeSql("DROP TABLE DUTIES", []);
tx.executeSql("DROP TABLE SDUTIES", []);
tx.executeSql('CREATE TABLE IF NOT EXISTS DUTIES (id, name, date, time, course, room)');
tx.executeSql('CREATE TABLE IF NOT EXISTS SDUTIES (id, name, date, time, course, room, invg)');
});
PupulateSupTable();
function PopulateSupTable() {
var xmlhttp = new XMLHttpRequest();
var url = "/scripts/SupDuties.txt";
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var myArr = JSON.parse(xmlhttp.responseText);
db.transaction(
function (tx) {
var k, sql = 'INSERT INTO SDUTIES (id, name, date, time, course, room, invg) VALUES (?,?,?,?,?,?,?)';
for (k = 0; k < myArr.length; k++) {
tx.executeSql(sql, [myArr[k].ID, myArr[k].Name, myArr[k].Date, myArr[k].Time, myArr[k].Course, myArr[k].Room, myArr[k].Invigilator]);
}
}
);
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
I am making an application using the ionic framework and I am using sqlite to store a list of about 150 rows. Each row has two attributes, ID and Name.
Now I am retrieving this data using a database factory which runs a query.
It works, however when I test it on an my Galaxy Tab 3 the list takes about 5-10 seconds to load and once it it loaded the list it super laggy scrolling through the list items.
Here's my controller
.controller('ActionSearchCtrl', function($scope, ActionSearchDataService, DBA, $cordovaSQLite){
var tablet = true;
var query = "select action FROM actions;";
$scope.items = [];
$scope.runSQL = function(){
DBA.query(query).then(function(result){
$scope.items = DBA.getAll(result);
});
};
if(tablet){$scope.runSQL()};
Here's my Database Factory
.factory('DBA', function($cordovaSQLite, $q, $ionicPlatform) {
var self = this;
// Handle query's and potential errors
self.query = function (query, parameters) {
parameters = parameters || [];
var q = $q.defer();
$ionicPlatform.ready(function () {
$cordovaSQLite.execute(herbsDatabase, query, parameters)
.then(function (result) {
q.resolve(result);
}, function (error) {
console.warn('I found an error');
console.warn(error);
alert(error.message);
q.reject(error);
});
});
return q.promise;
}
// Proces a result set
self.getAll = function(result) {
var output = [];
for (var i = 0; i < result.rows.length; i++) {
output.push(result.rows.item(i));
}
return output;
}
// Proces a single result
self.getById = function(result) {
var output = null;
output = angular.copy(result.rows.item(0));
return output;
}
return self;
})
So the query returns about 150 entries which I need all on one page (I've looked into infinite scrolling and pagination but my client wants all the items on one page so this is not an option. From what I've read, 150 entries shouldn't be too slow in terms of watchers as I am using ng-repeat for the list items to display. If anyone has a way I can display this many items using the cordovaSQLite plugin to make the list function quicker let me know! at the moment the list is pretty much unusable, I've tried it on other devices too and it has the same result.
I've also tried creating about 200 dummy objects in the controller (without making a call to the db to get data) and the performance is fine. That's why I am thinking it's an SQLite performance issue. This is my first post on here so apologies if I am not clear enough.
Okay, so I used the collection-repeat instead of ng-repeat in my template where the list was being generated. Dramatically increased the performance of the list. I hope this helps someone as it was driving me crazy!
Your problem may comes from several points.
Are you running on Android?
First, if you are running on android, be aware that list management and SCROLLING is pretty lame at present on ionic. On my case, only Native scroll was providing good results with keeping ng-repeat.
app.config(function($ionicConfigProvider) {
if(ionic.Platform.isAndroid())
$ionicConfigProvider.scrolling.jsScrolling(false);
});
Fasting up SQLite
I have clearly improved my performance of SQLite by defining correct index with this command :
'CREATE INDEX IF NOT EXISTS ' + _indexUniqueId + ' ON ' + _tableName + ' ' + IndexDefinition));
For your case, if your have 2 cols, (A and B) and if you are using only A to query on your table, then you need only one index on ColA, and above IndexDefinition will be equal to : (A)
Moreover, check that officiel sqlite doc to understand how index are managed :
https://www.sqlite.org/lang_createindex.html
Update to ionic RC5
If you have not done it yet, do it.
There huge improvements on scrolling ng-repeat on 1.0.0-rc5
Else
If those information does not work, please provide more info on your issue :
- is the list unscrollable ? or delays a lot before displaying ? or both ?
I'm using Azure Mobile Services with an android application which I am trying to get working with a SQL database in Azure. Im using a server-side JavaScript function within Mobile Services to handle the insertion and update of data. Insertion of new data operates correctly, however I cannot update any rows with the function.
The error I received is: 409 - error: Could not insert the item because an item with that id already exists.
It seems as though it is trying to insert instead of update, but I can't figure out the solution. Help is much appreciated!
Here's my server-side script from Azure:
function insert(item, user, request) {
var table = tables.getTable('Reviews');
table.where({
text: item.id
}).read({
success: upsertItem
});
function upsertItem(existingItems) {
if (existingItems.length == 0) {
item.numReviews = 1;
item.rating = item.reviews;
request.execute();
} else {
item.id = existingItems[0].id;
item.numReviews = existingItems[0].numReviews + 1;
var average = existingItems[0].reviews / item.numReviews;
item.reviews = existingItems[0].reviews + item.reviews;
item.rating = average;
table.update(item, {
success: function(updatedItem) {
request.respond(200, updatedItem)
}
});
}
}
}
For your initial query, you want to query by the id field:
table.where({
id: item.id
}).read({
success: upsertItem
});