I can not make a simple select on a SQLite database, via GRDB.swift
Here is my code:
override func viewDidLoad()
{
super.viewDidLoad()
do
{
let dbQueue = try DatabaseQueue(path: "games.sqlite")
try dbQueue.inDatabase
{
db in try db.execute("SELECT * FROM games")
}
}
catch
{
print("Erreur !")
}
}
I do not find in the documentation a simple way to perform a simple select, the doc speaks of model, and I desire just for the moment to make direct queries.
The documentation speaks only for error on my part of model.
Thank you all for your help.
My environment :
Xcode8.2.1
swift3
cocoapods 1.2.0.rc.1
The GRDB documentation says:
Once granted with a database connection, the execute method executes the SQL statements that do not return any database row, such as CREATE TABLE, INSERT, DELETE, ALTER, etc.
So if you want to fetch rows, you picked the wrong method.
To fetch rows, you use a fetching method (which starts with fetch), as documented in the Row Queries documentation chapter, that I encourage you to read.
For example:
let rows = try dbQueue.inDatabase { db in
try Row.fetchAll(db, "SELECT * FROM games")
}
// Now we're back to the main thread: use rows:
for row in rows {
let name: String = row.value(named: "name")
let year: Int = row.value(named: "year")
print(name)
print(year)
}
That's what it gives "without going through models".
Yet models are still handy:
let games = try dbQueue.inDatabase { db -> [Game] in
let rows = try Row.fetchAll(db, "SELECT * FROM games")
return rows.map { row in
Game(
name: row.value(named: "name"),
year: row.value(named: "year"))
}
}
// Now we're back to the main thread: use games
for game in games {
print(game.name)
print(game.year)
}
If you have your Game type (struct or class) adopt the RowConvertible protocol, you can even write:
let games = try dbQueue.inDatabase { db in
try Game.fetchAll(db, "SELECT * FROM games")
}
And if Game also adopts TableMapping protocol, you get:
let games = try dbQueue.inDatabase { try Game.fetchAll($0) }
See Record for the documentation of those protocols.
Related
I have been getting this problem now a few times when I'm coding and I think I just don't understand the way SwiftUI execute the order of the code.
I have a method in my context model that gets data from Firebase that I call in .onAppear. But the method doesn't execute the last line in the method after running the whole for loop.
And when I set breakpoints on different places it seems that the code first is just run through without making the for loop and then it returns to the method again and then does one run of the for loop and then it jumps to some other strange place and then back to the method again...
I guess I just don't get it?
Has it something to do with main/background thread? Can you help me?
Here is my code.
Part of my UI-view that calls the method getTeachersAndCoursesInSchool
VStack {
//Title
Text("Settings")
.font(.title)
Spacer()
NavigationView {
VStack {
NavigationLink {
ManageCourses()
.onAppear {
model.getTeachersAndCoursesInSchool()
}
} label: {
ZStack {
// ...
}
}
}
}
}
Here is the for-loop of my method:
//Get a reference to the teacher list of the school
let teachersInSchool = schoolColl.document("TeacherList")
//Get teacherlist document data
teachersInSchool.getDocument { docSnapshot, docError in
if docError == nil && docSnapshot != nil {
//Create temporary modelArr to append teachermodel to
var tempTeacherAndCoursesInSchoolArr = [TeacherModel]()
//Loop through all FB teachers collections in local array and get their teacherData
for name in teachersInSchoolArr {
//Get reference to each teachers data document and get the document data
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument {
teacherDataSnapshot, teacherDataError in
//check for error in getting snapshot
if teacherDataError == nil {
//Load teacher data from FB
//check for snapshot is not nil
if let teacherDataSnapshot = teacherDataSnapshot {
do {
//Set local variable to teacher data
let teacherData: TeacherModel = try teacherDataSnapshot.data(as: TeacherModel.self)
//Append teacher to total contentmodel array of teacherdata
tempTeacherAndCoursesInSchoolArr.append(teacherData)
} catch {
//Handle error
}
}
} else {
//TODO: Error in loading data, handle error
}
}
}
//Assign all teacher and their courses to contentmodel data
self.teacherAndCoursesInSchool = tempTeacherAndCoursesInSchoolArr
} else {
//TODO: handle error in fetching teacher Data
}
}
The method assigns data correctly to the tempTeacherAndCoursesInSchoolArr but the method doesn't assign the tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool in the last line. Why doesn't it do that?
Most of Firebase's API calls are asynchronous: when you ask Firestore to fetch a document for you, it needs to communicate with the backend, and - even on a fast connection - that will take some time.
To deal with this, you can use two approaches: callbacks and async/await. Both work fine, but you might find that async/await is easier to read. If you're interested in the details, check out my blog post Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese.
In your code snippet, you use a completion handler for handling the documents that getDocuments returns once the asynchronous call returns:
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument { teacherDataSnapshot, teacherDataError in
// ...
}
However, the code for assigning tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool is outside of the completion handler, so it will be called before the completion handler is even called.
You can fix this in a couple of ways:
Use Swift's async/await for fetching the data, and then use a Task group (see Paul's excellent article about how they work) to fetch all the teachers' data in parallel, and aggregate them once all the data has been received.
You might also want to consider using a collection group query - it seems like your data is structure in a way that should make this possible.
Generally, iterating over the elements of a collection and performing Firestore queries for each of the elements is considered a bad practice as is drags down the performance of your app, since it will perform N+1 network requests when instead it could just send one single network request (using a collection group query).
Hi i have a noSql db in firebase.
I want to get the object where userId is 288
i'v tried many combinations but i cant figure out how its done.
This is my code so far :
var refTest= database.ref('conversation')
var query = refTest
.orderByChild('messages');
query.on('value', function(data) {
var a = data.val();
console.log(a.messages.userId);
console.log(data.val());
});
This is a image of my "schema"
I'm obviously a noob when it comes to NoSQL. I do understand SQL
All help is appreciated
You can order/filter on a nested value like this:
var refTest= database.ref('conversation')
var query = refTest.orderByChild('messages/userId').equalTo("288");
query.on('value', function(snapshot) {
snapshot.forEach(function(child) {
console.log(child.key);
console.log(child.val());
});
});
The forEach is needed, since there may be multiple child nodes with messages/userId equal to 288.
The key named "messages" doesn't make sense in your schema. Because if you want to have another message under that conversation, then you wouldn't be able to add it with the same key name and you also couldn't add it under "messages" because it would overwrite the other one. My suggestion is to use the push() method for adding a new message. This way you uniquely identify each message.
Regarding your question, an easy to understand way of parsing your schema is this: you loop through each message of each conversation for finding the messages with userID.
refTest.on('value', function(data) {
var conversations = data.val();
for (conversation in conversations){
for (message in conversation) {
if (message.userId == 288) {
// do whatever you need
// and eventually return something to break the loops
}
}
}
}
Of course, you can adapt it based on your needs
I'm using Azure Mobile App with Xamarin.Forms to create an offline capable mobile app.
My solution is based on https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter3/client/
Here is the code that I use for offline sync :
public class AzureDataSource
{
private async Task InitializeAsync()
{
// Short circuit - local database is already initialized
if (client.SyncContext.IsInitialized)
{
return;
}
// Define the database schema
store.DefineTable<ArrayElement>();
store.DefineTable<InputAnswer>();
//Same thing with 16 others table
...
// Actually create the store and update the schema
await client.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
}
public async Task SyncOfflineCacheAsync()
{
await InitializeAsync();
//Check if authenticated
if (client.CurrentUser != null)
{
// Push the Operations Queue to the mobile backend
await client.SyncContext.PushAsync();
// Pull each sync table
var arrayTable = await GetTableAsync<ArrayElement>();
await arrayTable.PullAsync();
var inputAnswerInstanceTable = await GetTableAsync<InputAnswer>();
await inputAnswerInstanceTable.PullAsync();
//Same thing with 16 others table
...
}
}
public async Task<IGenericTable<T>> GetTableAsync<T>() where T : TableData
{
await InitializeAsync();
return new AzureCloudTable<T>(client);
}
}
public class AzureCloudTable<T>
{
public AzureCloudTable(MobileServiceClient client)
{
this.client = client;
this.table = client.GetSyncTable<T>();
}
public async Task PullAsync()
{
//Query name used for incremental pull
string queryName = $"incsync_{typeof(T).Name}";
await table.PullAsync(queryName, table.CreateQuery());
}
}
The problem is that the syncing takes a lot of time even when there isn't anything to pull (8-9 seconds on Android devices and more than 25 seconds to pull the whole database).
I looked at Fiddler to find how much time takes the Mobile Apps BackEnd to respond and it is about 50 milliseconds per request so the problem doesn't seem to come from here.
Does anyone have the same trouble ? Is there something that I'm doing wrong or tips to improve my sync performance ?
Our particular issue was linked to our database migration. Every row in the database had the same updatedAt value. We ran an SQL script to modify these so that they were all unique.
This fix was actually for some other issue we had, where not all rows were being returned for some unknown reason, but we also saw a substantial speed improvement.
Also, another weird fix that improved loading times was the following.
After we had pulled all of the data the first time (which, understandably takes some time) - we did an UpdateAsync() on one of the rows that were returned, and we did not push it afterwards.
We've come to understand that the way offline sync works, is that it will pull anything that has a date newer than the most recent updated at. There was a small speed improvement associated with this.
Finally, the last thing we did to improve speed was to not fetch the data again, if it already had cached a copy in the view. This may not work for your use case though.
public List<Foo> fooList = new List<Foo>
public void DisplayAllFoo()
{
if(fooList.Count == 0)
fooList = await SyncClass.GetAllFoo();
foreach(var foo in fooList)
{
Console.WriteLine(foo.bar);
}
}
Edit 20th March 2019:
With these improvements in place, we are still seeing very slow sync operations, used in the same way as mentioned in the OP, also including the improvements listed in my answer here.
I encourage all to share their solutions or ideas on how this speed can be improved.
One of the reasons for the slow Pull() is when more than (10) rows get the same UpdatedAt value. This happens when you update the rows at once, for example running an SQL command.
One way to overcome this is to modify the default trigger on the tables. To ensure every row gets a unique UpdateAt, we did something like this:
ALTER TRIGGER [dbo].[TR_dbo_Items_InsertUpdateDelete] ON [dbo].[TableName]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE #InsertedAndDeleted TABLE
(
Id NVARCHAR(128)
);
DECLARE #Count INT,
#Id NVARCHAR(128);
INSERT INTO #InsertedAndDeleted
SELECT Id
FROM inserted;
INSERT INTO #InsertedAndDeleted
SELECT Id
FROM deleted
WHERE Id NOT IN
(
SELECT Id
FROM #InsertedAndDeleted
);
--select * from #InsertedAndDeleted;
SELECT #Count = Count(*)
FROM #InsertedAndDeleted;
-- ************************ UpdatedAt ************************
-- while loop
WHILE #Count > 0
BEGIN
-- selecting
SELECT TOP (1) #Id = Id
FROM #InsertedAndDeleted;
-- updating
UPDATE [dbo].[TableName]
SET UpdatedAt = Convert(DATETIMEOFFSET, DateAdd(MILLISECOND, #Count, SysUtcDateTime()))
WHERE Id = #Id;
-- deleting
DELETE FROM #InsertedAndDeleted
WHERE id = #Id;
-- counter
SET #Count = #Count - 1;
END;
END;
I am developing a mobile application with Ionic and SQLITE database. I want a scenario where same record should not be inserted twice for Inventory operation. I am checking the database as follows:
var query = "SELECT COUNT(*) FROM productScan WHERE uniqueId = (?) AND sync = \'N\'";
$cordovaSQLite.execute(db, query, [uniqueId]).then(function (res) {
alert(JSON.stringify(res));
}, function (err) {
alert(JSON.stringify(err));
});
what happen here is when i install the app for the first time and run it then after scan operation when this code run for duplication check, it gives me following output even though there is no record in database
{"rows":{"length":1},""rowsAffected":0}
I am new to Structured Query Language(SQL). I am not able to parse the result here. The result is coming wrong. Is the query needs to be reformatted or any different way to achieve the goal?
Thanks for your time.
BEST way to do this is by giving ALIAS to the COUNT(*) .
something like this
db.executeSql("select count(*) AS TOTAL from mydebit where aid = ?", [
parseInt(this.mydata)
]);
and then
console.log(data.rows.item(0).TOTAL)
so we can easily give the name to the return result and use the to get he row value
cheersss.....!!!!!!!!!
I do it in this way
JSON.stringify(data.rows.item(0)['COUNT(*)'])
Somehow i managed to achieve it in a different way
var query = "SELECT * FROM productScan WHERE uniqueId = (?) AND sync = \'N\'";
$cordovaSQLite.execute(db, query, [uniqueId]).then(function (res) {
if (res.rows.length == 0) {
//processed the operation
}else{
alert('You have already scanned this asset!.');
}
var query = "SELECT COUNT(*) FROM productScan WHERE uniqueId = (?) AND sync = \'N\'";
$cordovaSQLite.execute(db, query, [uniqueId]).then(function (res) {
just parse the result,(json.parse(res)) and find by rowsAffected like
if(rowsAffected != 0)
{
//just do the operation
}
else {
//terminate
}
Is there a design pattern in meteor application to handle multiple clients inserting the same logical record 'simultaneously'?
Specifically I have a scoring type application, and multiple clients could create the initial, basically blank, Score record for an Entrant when the entrant is ready to start. The appearance of the record is then used to make it available on the page for editing by the officials, incrementing penalty counts and such.
Stages = new Meteor.Collection("contests");
Entrants = new Meteor.Collection("entrants");
Scores = new Meteor.Collection("scores");
// official picks the next entrant
Scores.insert( stage_id:xxxx, entrant_id:yyyy)
I am happy with the implications of the conflict resolutions of edits to the Score record once it is in the Collection. I am not sure how to deal with multiple clients trying to insert the Score for the stage_id/entrant_id pair.
In a synchronous app I would tend to use some form of interlocking, or a relational DB key constraint.
Well, according to this answer Meteor $upsert flag is still in enhancement list and seems to be added in stable branch after 1.0 release.
So the first way is how it was said to add an unique index:
All the implementation ways are listed here. I would recommend you to use native mongo indexes, not a code implementation.
The optimistic concurrency way is much more complicated according to no transactions in MongoDB.
Here comes my implementation of it(be careful, might be buggy))):
var result_callback = function(_id) {
// callback to call on successfull insert made
}
var $set = {stage_id: xxxx, entrant_id: xxxx};
var created_at = Date.now().toFixed();
var $insert = _.extend({}, $set, {created_at: created_at});
Scores.insert($insert, function(error, _id) {
if (error) {
//handle it
return;
}
var entries = Scores.find($set, {sort: {created_at: -1}}).fetch()
if (entries.length > 1) {
var duplicates = entries.splice(0, entries.length - 1);
var duplicate_ids = _.map(duplicates, function(entry) {
return entry._id;
});
Scores.remove({_id: {$in: duplicate_ids}})
Scores.update(entries[0]._id, $set, function(error) {
if (error) {
// handle it
} else {
result_callback(entries[0]._id)
}
})
} else {
result_callback(_id);
}
});
Hope this will give you some good ideas)
Sorry, previous version of my answer was completely incorrect.