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
});
Related
Basically
Incrementing values seems like it should be so simple but it seems like it's not in my case.. I know I've still got so much to learn especially with VTL which is entirely new to me so please excuse my ignorance.
All I want is to be able to add to/subtract from an existing value possibly offline and for the server to capture all those changes when coming online, keeping multiple (intermittently online) devices consistent.
Use cases:
Keep count of inventory/
Keep balances for Accounts/
Votes/
Visits/
etc/
Scenario
device 1 makes n changes to Item A.quantityOnHand, ( i.e: A delivery is made from a driver using the app)
device 2 makes n changes to Item A.quantityOnHand,( i.e: Some Sales are made of the item over time)
device n makes n changes to Items/Customers/Cash/Votes/Visits/Some other Counting operation.
All changes need to be captured and at any time devices could go offline.
So Far..
I have looked at a custom resolver.. something like this simple change in my resolver:
#if ( $entryKeyAttributeName == "transacted")
##perhaps use regex and filter a set prefix
$util.qr($expAdd.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
#else
$util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
#end
I found that this only works once, for the most current version of the model. The result is only the latest update is synced, depending on current server versions, resulting in inconsistent data.
I thought the custom conflict handling could help, Perhaps If I have a locally stored 'last-synced-value' and then use the differences to create a modified model corrected with the changed local values taken into account.
Could this work?
What is the downside of creating _increment logic locally in a DB?
It seems like it would be a simple process?
if amplify.isConnected -> save normally, immediate update using model.
if amplify.isNotConnected:
->diff model saved and queued separately in a local DB (with a 'incremented value' field)
->query(model) finds and sums any relevant model during unSynced/offline State
upon Amplify.isNowSynced && connected
->save the now synced and updated models with the relevant increments
->delete the increment rows once response from server is received and checked for consistency
I did something similar in the below extension... i used another SQflite db with one table..
Can you see any downside to something like the followingextension?
extension AmplifyHelper on DataStoreCategory {
Future<bool> incrementValue<T extends Model>(T model, incrementValue, QueryField incField,
{QueryPredicate? where}) async {
/// If online and connected, just save in the normal way
if (_networkService.connectivityResult.value != ConnectivityResult.none) {
try {
save(model, where: where);
return true;
} catch (e) {
debugPrint('*** An error occurred trying to save while online ***');
return false;
}
} else {
/// Otherwise, create a map to save in a sqflite DB
var map = <String, dynamic>{};
map['dyn_tableName'] = model.getInstanceType().modelName();
map['dyn_rowId'] = model.getId();
map['dyn_increment'] = incrementValue;
map['dyn_field'] = incField.fieldName;
map['dyn_fromValue'] = model.toJson()[incField.fieldName];
return _dbService.updateOrInsertTable(map);
}
}
Future<List> queryWithOffline<T extends Model>(ModelType<T> modelType,
{QueryPredicate? where, QueryPagination? pagination, List<QuerySortBy>? sortBy}) async {
/// Get normal results ( basically unchanged from increments, contains the last-synced value for any increment)
List<T> amplifyResult = await query(modelType, where: where, pagination: pagination, sortBy: sortBy);
/// Get any increments from other DB
List<Map<String, dynamic>> offlineList = await _dbService.getOfflineTableRows(tableName: modelType.modelName());
if (offlineList.isNotEmpty && amplifyResult.isNotEmpty) {
/// If there is something in there SUM the relevant fields for each row and return.
List<T> listWithOffline = [];
for (var rowMap in offlineList) {
ModelField field = modelProvider.modelSchemas
.firstWhere((mdl) => mdl.name == rowMap['dyn_tableName'])
.fields![rowMap['dyn_field']]!;
Map<String, dynamic> modelMap =
amplifyResult.firstWhere((item) => item.getId() == rowMap['dyn_rowId']).toJson();
modelMap[field.name] = modelMap[field.name] + rowMap['dyn_increment'];
listWithOffline.add(modelType.fromJson(modelMap));
}
return listWithOffline;
} else {
/// There is nothing in the sync DB, just return data.
return amplifyResult;
}
}
Future<bool> returnToOnline<T extends Model>() async {
/// Called when Amplify is synced /ready after coming online
/// Check if Amplify is resynced
if (!_amplifySyncService.amplifyHasDirt) {
List<Map<String, dynamic>> offlineList = await _dbService.getOfflineTableRows();
if (offlineList.isNotEmpty) {
List<T> listWithOffline = [];
ModelType<T>? modelType;
List<T> amplifyResult = [];
for (var rowMap in offlineList) {
///Basically the same process of match and sum as above
if (modelType == null || modelType.modelName() != rowMap['dyn_tableName']) {
modelType = modelProvider.getModelTypeByModelName(rowMap['dyn_tableName']) as ModelType<T>?;
amplifyResult = await Amplify.DataStore.query(modelType!);
}
ModelField field = modelProvider.modelSchemas
.firstWhere((mdl) => mdl.name == rowMap['dyn_tableName'])
.fields![rowMap['dyn_field']]!;
Map<String, dynamic> modelMap = amplifyResult.firstWhere((item) => item.getId() == rowMap['dyn_rowId']).toJson();
modelMap[field.name] = modelMap[field.name] + rowMap['dyn_increment'];
listWithOffline.add(modelType.fromJson(modelMap));
}
for (var mdl in listWithOffline) {
/// Saving the updated model with the increments added
await Amplify.DataStore.save(mdl);
if (await _dbService.deleteRow(mdl.getId())) {
debugPrint('${mdl.getId()} has been processed from the jump queue');
// TODO: final looks...
// if(isNowSynced(mdl){
listWithOffline.remove(mdl);
// }else{
// rollback(mdl);
// }
}
} else {
debugPrint('No jump queue to process');
}
} else {
print('*** Amplify Had Dirt! ***');
}
return true;
}
}
Thanks for reading
I have the following realtime database schema:
schema
I'm working on a social app where a user can like another user. When the user clicks on the like button, a new entry will be added to myLikes->userId list
MyLike myLike = new MyLike(userId, System.currentTimeMillis();
FirebaseDatabase.getInstance().getReference().child("myLikes").child(userId).child(System.currentTimeMillis()).setValue(myLike);
When the new entry is completed, the cloud function gets triggered to write a new entry in whoLikedMe>userId list for the other User who has been liked.
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const data2 = change.after.val();
const likedDate = data2.date;
const myUserId = data2.I_Liked_UserId;
var likedMe = {
date: likedDate,
Who_Liked_Me_UserId: myUserId,
}
//get the current date in millis
var hrTime = process.hrtime();
const dateMillis = hrTime[0];
return admin.database().ref('/WholikedMe/'+myUserId+'/'+hrTime[0]).set(likedMe);
});
The function gets triggered ok but no entries are inserted into the realtime database. What I'm doing wrong?
EDIT:
When I changed:
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}')
with
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}/915170400000')
(which adds the hardcoded timestamp to the path) all works fine. The problem is that the timestamp is constantly changing. any idea how to accomplish this with the timestamp?
You're using wildcard syntax (curly braces) in the following line:
return change.after.ref('/WholikedMe/{myUserId}/{hrTime[0]}').set(likeMe)
That syntax does not apply to a set statement. You need to use :
return change.after.ref('/WholikedMe/' + myUserId + '/' + hrTime[0]).set(likeMe)
... I think, doing this on my phone so can't double check. :-)
I figure d out. In addition to the syntax correction suggested by #GrahamD I had to change
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}')
with
exports.whoLikedMe = functions.database.ref('/myLikes/{userId}/{timestamp')
and now all is working fine.
I'm trying to figure out how to execute a SQL query from my MainActivity. I'm currently using a large portion of this example. Upon clicking my Acknowledge button, it finds the selected rows, gets the jsonID of said row, then I'm wanting to update a value in my SQLite table of those rows.
So my question is, how do I execute my "jsonQuery" string, to my SQLite database upon clicking my Acknowledge button?
Here's the code I have in my MainActivity:
acknowledge.setOnClickListener {
var jsonData_text: String = ""
for (i in 0 until JSONParser.MrAdapter.public_modelArrayList!!.size) {
if (JSONParser.MrAdapter.public_modelArrayList!!.get(i).getSelecteds()) {
jsonData_text = jsonData_text + JSONParser.MrAdapter.public_modelArrayList!!.get(i).getJSONID() + ","
}
}
val jsonSQLQuery = jsonData_text.dropLast(1)
val jsonQuery = "update Notes set acknowledged = 1 WHERE jsonID in ("+jsonSQLQuery+")"
Log.d("logged_json", jsonQuery)
}
Thanks for any help!
I am trying to query data form Google Spreadsheet available to be read by anyone with the Read Only link.
I implemented this Quickstart solution but here is what I need:
Access data just with URL, no authentication needed
Query item in column A and get value in column B
No need for updating any data
I tried constructing queries like:
http://spreadsheets.google.com/tq?tq=SELECT%20*%20WHERE%20A=C298732300456446&key=2aEqgR1CDJF5Luib-uTL0yKLuDjcTm0pOIZeCf9Sr0wAL0yK
But all I get is:
/*O_o*/
google.visualization.Query.setResponse(
{
"version": "0.6",
"reqId": "0",
"status": "error",
"errors": [
{
"reason": "invalid_query",
"message": "INVALID_QUERY",
"detailed_message": "Invalid query: NO_COLUMN: C298732300456446"
}
]
}
This comes when the data is actually present in the sheet in column A with value C298732300456446.
What can I do for getting the data, without any authentication from my spreadsheet?
I am not sure if this can be done. If fine, I can suggest an alternative solution. You can try writing a Google App script like:
function doGet(e) { return getInfo(e); }
function doPost(e) { return getInfo(e); }
function getInfo(request) {
var someValueFromUrl = request.parameter.value;
var requiredValue = "";
var sheet = SpreadsheetApp.openById("spreadsheet_id");
var data = sheet.getDataRange().getValues();
for (var i = 0; i < data.length; i++) {
Logger.log("Reading row num: " + i);
if(data[i][0] == someValueFromUrl) {
requiredValue = data[i][1];
break;
}
}
Logger.log(requiredValue);
return ContentService.createTextOutput(JSON.stringify(requiredValue));
}
This way, you can publish this script as web app and call it using an URL which will be obtained when you publish this script.
Call the script like:
https://script.google.com/macros/s/obtained_url/exec?value=1234
If key is found, you will get the String response as:
"value"
I hope this helps.
C298732300456446 needs to be put within single quotes.
So you need to enclose C298732300456446 as %27C298732300456446%27
Your'e modified query would be
http://spreadsheets.google.com/tq?tq=SELECT%20*%20WHERE%20A=%27C298732300456446%27&key=2aEqgR1CDJF5Luib-uTL0yKLuDjcTm0pOIZeCf9Sr0wAL0yK
I'm unable to test this though - looks like you've removed/removed access from this spreadsheet.
I'm facing an issue in my application where I send the server request once and receive a single response but my request has created duplicate rows in the database. The only primary key in the database table is the row id which gets incremented every time a request is sent so duplicate rows of values get created with different row ids. I'm using Xamarin Android. Could you please let me know if this issue is a bug related to Xamarin and if they is any way in which this can be fixed?
Thank you.
Edit:
This is my code:
I call this method in my OnCreate()
public void SendMobileNo(Activity activity)
{
Services_GetValue.GetValue client = new Services_GetValue.GetValue();
Services_GetValue.GetValueRequest request = new Services_GetValue.GetValueRequest()
{
key = "abac",
PhoneNo = "1234567890"
};
client.BeginCheckPhone(request, (ar) => {
Services_GetValue.GetValueResponse response = client.EndCheckPhone(ar);
this.IsPhoneValid = response.IsValidPhoneNo;
activity.RunOnUiThread (() => {
if (SendMobileNoCompleted != null)
{
this.SendMobileNoCompleted();
this.SendMobileNoCompleted = null;
}
});
}, null);
client.CheckPhone(request);
}
I have created a Proxy in WebReferences package with the name "Services_GetValue.GetValue" and it is a SOAP webservice.