Insert to sqlite only runs the last statement [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I'm quite frustrated by a seemingly simple sqlite problem. I have a table with 3 records on the client. After collecting these records up and making a trip to the server to insert them, I have a cleanup function that hunts down new records on the server and runs deletes on the client in preparation to add them back in with server-specific ids. I don't necessarily like deleting, when updating would be preferable, however, the web-based app I've written may be accessed from multiple iPad devices and users, and we need a way to keep the client data in sync with the server. I have tried various methods that became problematic once offline possibilities were factored in.
So, to the code. I collect ids and perform a delete (that multiple_records_deleted is a boolean used to indicate to a setTimeout when the delete statement has completed).
ids = ids.substring(0,ids.length-1);
db.transaction(function (tx) {
tx.executeSql("DELETE FROM " + table + " WHERE id IN (" + ids + ")", [], function (tx, results) {
console.log("DELETE FROM " + table + " WHERE id IN (" + ids + ")");
multiple_records_deleted = true;
});
});
Then, the goal is to insert new records that may or may not be identical (there are keys and timestamp fields and other complex relationships crucial to the app that only the server has.)
sqlInserts[0] = "sql to insert first record";
sqlInserts[1] = "sql to insert second record";
sqlInserts[2] = "sql to insert third record";
function insertMultipleRecords() {
for (var x = 0; x < sqlInserts.length - 1; x++) {
db.transaction(function (tx) {
tx.executeSql(sqlInserts[x],[]);
}, badCB, goodCB);
}
}
The long and short of my problem, is that every time I run this code, only the last item in the array seems to execute. I have tried just about every variation of db.transaction I can think of. used timers. used other callbacks. but to no avail. The weird thing is that I run a similar code block in another part of the app and it works fine.
Any ideas why it is only ever the last item in the array that successfully runs, while the rest seem to go nowhere?
Thanks..
Robin
EDIT:
Here is a follow up snip of code.
for (var rec in response.table.records.record) {
db.transaction(function (tx) {
console.log("DELETE FROM " + table + " WHERE id = " + response.table.records.record[rec].f[record_idnumber_fld_ctr]);
tx.executeSql("DELETE FROM " + table + " WHERE id = " + response.table.records.record[rec].f[record_idnumber_fld_ctr], [], function (tx, results) {
// then insert
fld_ctr = 0;
setTableColumns("table_columns",table); // ensure we are using the right table
sql = "INSERT INTO " + table + " (";
for (x = 0; x < table_columns.length; x++) {
sql += table_columns[x] + ",";
}
sql = sql.substring(0,sql.length-1) + ") ";
sql += "SELECT ";
for (var fld in response.table.fields.field) {
var obj = response.table.fields.field[fld];
for (var prop in obj) {
if (prop === "#attributes") { // cast the data
sql += formatData(obj[prop].field_type,response.table.records.record[rec].f[fld_ctr]) + ",";
}
}
fld_ctr++;
}
// special cases for certain tables
if (table == "orders") {
if (response.table.records.record[rec].f[43] != "") sql += formatData("string","YES") + ","; // siggy
else sql += formatData("string","") + ",";
}
if (table == "pictures") {
sql += formatData("string","") + ","; // datauri
}
sql += "'SYNCED',";
sql += "'NO',";
sql += formatData("integer",response.table.records.record[rec].f[record_idnumber_fld_ctr]) + ",";
sql += "'VALID',";
sql += "'NO ERRORS'";
console.log(sql);
db.transaction(insertRecords(sql), errorHandler);
});
});
}
I run through a resultset of records from the server. And for each one, I attempt to delete from the table where a key is the same. Both console.logs display the same verbiage all 3 times... matching the last one in the array. I'm sure this is a closure problem, just not sure how to attack it.

It is the classic issue where the variable is just a reference and not a snapshot in time. So by the time the callback is executed, the value of x has been changed. So you need to either pull out the function into a separate function or use a closure.
function runTrans (x) {
db.transaction(function (tx) {
tx.executeSql(sqlInserts[x],[]);
}, badCB, goodCB);
}
function insertMultipleRecords() {
for (var x = 0; x < sqlInserts.length - 1; x++) {
runTrans(x);
}
}
or
function insertMultipleRecords() {
for (var x = 0; x < sqlInserts.length - 1; x++) {
(function(x) {
db.transaction(function (tx) {
tx.executeSql(sqlInserts[x],[]);
}, badCB, goodCB);
}(x));
}
}

Related

MySQL query with LIMIT and updating data by chunks

I have a table in my mysql db with columns id and drivers_license_number. Now data in drivers_license_number column is stored in plaintext, and I want to encrypt it.
This table contains about 400000 records. I have read about problems with LIMIT and OFFSET issue and try to use in my query late row lookups.
So first of all I need to get data from db, then encrypt the right field and update db with this encrypted field. I don't understand, how to organize my code to send parameters with limit and offset to db. I need to work in loop in this situation?
function getUpdatedEncryptedField(db_table_name, field_name) {
return getDataFromDb(db_table_name, field_name).then(function(result){
return encryptData(result, field_name).then(function (result){
var promises = result.map(function(item) {
var data = [item[field_name], item.id];
return updateFieldNameData(db_table_name, field_name, data);
});
return q.all(promises);
});
});
}
After the first pass I will get for example first 1000 records and how to move forward to get another 1000 rows?
function getDataFromDb(db_table_name, field_name, limit, offset) {
var sql = 'SELECT ds.id, ' + field_name + ' FROM ( SELECT id FROM ' + db_table_name + 'WHERE ' + field_name +
' IS NOT NULL ORDER BY id LIMIT ' + limit + ', ' + offset + ') d JOIN ' + db_table_name + ' ds ON ds.id = d.id ORDER BY ds.id;'
return db.modio.executeSql(sql);
}
You can use a carring/high order function to update the chunks by e.g 1000 and get the next 1000 records until the 400000, like so:
function getUpdatedEncryptedField(db_table_name, field_name) {
var idFrom = 0;
var idTo = 1;
return function getDataFromDB(db_table_name, field_name){
idFrom = idTo;
idTo += 1000;
console.log(id range is ${idFrom}-${idTo}`);
// call your db to get the recods
}
}
// assign high order func to updateChunks var, which is again a func
var updateChunks = getUpdatedEncryptedField('studentsDB','creditCard')
// so each time you execute updateChunks() the values idFrom & idTo are updated
// updateChunks()
// id range is 1-1001
// updateChunks()
// id range is 1001-2001
// and so on
// create a loop until you get all the table records, e.g.
// pay attention to define 'i' with let and not with var because of async loop
for(let i=0; i <= 400000/1000; i++){
// call the updateChunk func here
// at eaach itteration it will ask for the next chunk of records
updateChunks()
}
`
Of course you should update a little bit your mysql query, to get the range of Ids from - to, this is out of the question scope.
Hope helps, good luck.

Accessing variables from a parent function within a jQuery click() event (Word add-in)

The code for my add-in takes a search term, then displays a list of matching links on a table. Each link is supposed to insert itself into a word document when it's clicked, but I can't figure out how to pass variables in to a jQuery .click() function.
Currently, no matter what option I click, the URL of the link that gets inserted into the word document is always the URL of the last item on the list. So for example, if I had 3 results in the table: Facebook, Instagram and Yahoo, then whatever option I click, the URL that gets inserted is always http://www.yahoo.com
function displayLinks() {
// Empty pre-existing results
$('#linksTable').empty();
var filteredLinks = [];
// Grab value from search box
var searchTerm = document.getElementById('linksSearchField').value;
// Separate all links containing search term and put them in a filtered list
for (var i = 0; i < numberOfLinks; i++) {
if (sortedLinks[i].linkName.toLowerCase().includes(searchTerm.toLowerCase())){
filteredLinks.push(sortedLinks[i]);
}
}
// Get length of filtered links array
var numberOfSearchResults = filteredLinks.length;
// Populate table with loop
if (searchTerm != '') {
for (var x = 0; x < numberOfSearchResults; x++) {
var table = document.getElementById('linksTable');
var row = table.insertRow(x);
var nameCell = row.insertCell(0);
var linkName = filteredLinks[x].linkName;
var linkNameFormattedForID = linkName.replace(/([ &/!*^%$##+_-])+/g);
var linkURL = filteredLinks[x].linkURL;
// Add link to table
nameCell.innerHTML = "<a href='javascript:void(0);' id='" + linkNameFormattedForID + "'>" + linkName + "</a>";
// Code to add link to word document
$('#' + linkNameFormattedForID).click(linkName, function (linkName) {
Word.run(function (context) {
const doc = context.document;
const originalRange = doc.getSelection();
originalRange.insertHtml("<a href='" + linkURL + "'>" + linkName.currentTarget.innerText + "</a>", "Start");
originalRange.insertText("Refer to ", "Start");
originalRange.load("text");
return context.sync()
})
.catch(function (error) {
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
});
}
}
}
I think I could maybe fix the problem by defining the linkURL variable within the click function itself, but the issue is that I can't access filteredLinks[x] inside of it. I can access the filteredLinks array on its own, but it can't read x, even though the click function is contained within the loop?
As a last-resort super hacky fix, I think I could just change the ID of each item to include it's URL, then extract it from linkName.currentTarget.innerText, but I'd rather not do that unless I really have to.
Thanks in advance!
The problem is that var in JavaScript is function scoped, and because the click event gets invoked after the for loop ends, the value of x will always be the final value of x. There's more information in pleanty of blog posts like this one: https://medium.com/front-end-developers/es6-variable-scopes-in-loops-with-closure-9cde7a198744
The solution:
Use let or const instead of var
A slightly worse solution:
wrap the contents of the for loop inside an IIFE
for (var x = 0; x < numberOfSearchResults; x++) {
(function(x){
...
})(x);
}

Performing calculations on data via json

I am trying to perform a simple addition on data I have gathered from an external source via JSON. The data I am getting is being returned as a string but is a number so I have tried using both parseInt() and Number() to no avail. I have shown a simple section of the code below:
var total_energy = 0;
var energy_val;
$.each(result.report.food.nutrients, function (i, v) {
if (v.name == "Energy"){
energy_val = v.value;
var energy = Number(energy_val);
total_energy = total_energy + energy;
console.log("energy " + energy);
console.log("totalenergy " + total_energy);
energy_val = "";
energy = 0;
}
}
The console returns the correct value for energy each time but the totalenergy value just seems to stay the same as the energy value. The sum doesn't seem to have any affect. Could anyone tell me where I am going wrong with this problem?
change console.log("energy " + total_energy); to console.log("energy " + energy);
try parseInt your values... can be this
I doubt if its due to the closure created in the loop where you are iterating over the items. Try the following code.
var total_energy = 0;
var energy_val;
$.each(result.report.food.nutrients, function (i, v) {
var item = v;
//Calling calculateEnergy() function so that each 'item' is the current instance 'v' value.
(function calculateEnergy(itemClicked))(item);
}
function calculateEnergy(itemClicked){
if(itemClicked.name == "Energy"){
energy_val = itemClicked.value;
var energy = Number(energy_val);
total_energy = total_energy + energy;
console.log("energy " + energy);
console.log("totalenergy " + total_energy);
energy_val = "";
energy = 0;
}
}
I have put comments within the code.
Update console.log() is buggy with ajax requests. So instead of logging, try creating an html div and populate the values there.

Creating dynamic Prepared Statements to insert into database

I'm trying to write one row of data into a Google Cloud SQL database, using a prepared statement as described here:
https://developers.google.com/apps-script/guides/jdbc
I have the data stored in an object called valuesByTitle, which looks like this:
{NAME: 'fun event', LOCATION: '123 Main St.'}
The data is inserted into the SQL table if I write everything out like this:
var conn = getConnection(); // connects to db
var stmt = conn.prepareStatement('INSERT INTO events '
+ '(NAME, LOCATION) values (?, ?)');
stmt.setString(1, valuesByTitle['NAME']);
stmt.setString(2, valuesByTitle['LOCATION']);
stmt.execute();
}
However, I would like to automate the process, so I don't have to change the code each time the variables change.
I have two new variables, one for the column names and one for a set of place holders (e.g. "?") for the insert statement.
var columns = Object.keys(valuesByTitle);
var placeholders = columns.map(function(column) {
return "?";
};
Using these, this prepared statement should automatically insert whatever is in the valuesByTitle object (assuming all the values are string values):
var conn = getConnection();
var stmt = conn.prepareStatement("INSERT INTO events (" + columns.join(",") + ") VALUES (" + placeholders.join(",") + ")");
for (i = 0; i < columns.length; i++) {
stmt.setString(i+1, valuesByTitle[columns[i]]);
}
stmt.execute();
For some reason, it's not inserting the data. Surprised not to find any examples either. Is the logic off or is it just not possible to do?
Thanks.
All your code is OK, you simply forgot to close the map() bracket i.e.
var columns = Object.keys(valuesByTitle);
var placeholders = columns.map(function(column) {
return "?";
};
should be
var columns = Object.keys(valuesByTitle);
var placeholders = columns.map(function(column) {
return "?";
});
(note the ending bracket after return "?";})

What is wrong with this Javascript code to insert data into a Web SQL database?

I am trying to create a simple page that takes a binary file and inserts the values into a Web SQL database.
This is the function I am using to insert the data:
function bin2dbfunc()
{
var result, n, aByte, byteStr;
var i=0;
var sql = new Array();
result = fr.result; //Input file
for (n = 0; n < result.length; ++n)
{
aByte = result.charCodeAt(n);
byteStr = aByte.toString(16);
if (byteStr.length < 2)
{
byteStr = "0" + byteStr;
} //Format to add leading 0 for hex values
//Looping through taking each byte read from file and adding to array
//sql[i] = aByte; //Value
sql[i] = byteStr; //String
//When completed one row of database run single SQL insert statement with array contents
if(i==15)
{
i=0; //Clear counter for next row
db.transaction(function (tx)
{
tx.executeSql('INSERT INTO binary_data VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [sql[0], sql[1], sql[2], sql[3], sql[4], sql[5], sql[6], sql[7], sql[8], sql[9], sql[10], sql[11], sql[12], sql[13], sql[14], sql[15]]);
}, function (tx, err) {
document.getElementById("result3").innerHTML += 'ERROR '; //Display error message if SQL not run successfully
});
}
else
{
i++; //Otherwise increment counter
}
}
}
I have stripped back the code to remove all my debug messages but essentially the code appears to run. I am using a binary file with 6 rows worth of data, the code however inserts the last row of data 6 times into the database.
Can anyone spot where I am going wrong?
Variables are captured by reference and not by value in JavaScript closures (this is true for objects such as arrays but for primitive values as well); this means that the functions passed to db.transaction will use the current value of sql when they run. It seems that they run asynchronously in your case, thus using the value of sql once bin2dbfunc has returned (which explains why you end up with the last row of data).
You have to copy the value of sql in order to make sure that the right one is used:
function transactionCallback(sql_) {
var sql = sql_.slice(0); // copy
return function(tx) {
tx.executeSql(…, sql);
};
}
db.transaction(transactionCallback(sql), …);
or
function transactionCallback(sql) {
return function(tx) {
tx.executeSql(…, sql);
};
}
db.transaction(transactionCallback(sql), …);
sql = [];
as you don't need sql's values afterwards in bin2dbfunc; this second solution would also enable you to get rid of i and use a more elegant solution based on push and length.

Categories

Resources