"Node.js / Edge.js - Insert JS variables into SQL" part 2 - javascript

Here's the original question.
Okay, I'm using Node.js and Edge.js to insert values to an SQL database.
First attempt was a straightforward insertion of hardcoded values:
var insertRow = edge.func('sql', function () {/*
INSERT INTO dbo.table (column0, column1, column2)
VALUES (value0, value1, value2)
*/});
insertRow();
This hardcoded insertion works as expected, of course.
And as seen in the answer of the question before me, passing the function an object allows the SQL statement to recognize a name/value pair of the object via an #, allowing dynamic value assignment:
var rowObj = {
v0: 'value0',
v1: 'value1',
v2: 'value2'
}
var insertRow = edge.func('sql', function () {/*
INSERT INTO dbo.table (column0, column1, column2)
VALUES (#v0, #v1, #v2)
*/});
insertRow(rowObj);
Works as expected.
What I would like to do is have the table and columns be variable as well, through properties provided by the same rowObj.
I tried:
var rowObj = {
t: 'dbo.table',
c0: 'column0',
c1: 'column1',
c2: 'column2',
v0: 'value0',
v1: 'value1',
v2: 'value2'
}
var insertRow = edge.func('sql', function () {/*
INSERT INTO #t (#c0, #c1, #c2)
VALUES (#v0, #v1, #v2)
*/});
insertRow(rowObj);
But this doesn't work. I tried making the table dynamic on its own, and the columns on their own, and that didn't work either.
note: I don't know SQL, I don't know the limitations of Node.js/Edge.js, and I'm very new to programming in general (so if my nomenclature is inconsistent/wrong, let me know but please don't tear me apart) I was just wondering if what I'm trying to do can be done.

I suggest you call edge.func as many times as you need different selection criteria. If you don't know the column names a priori, you can always construct the T-SQL string you pass to edge.func dynamically. With this approach you would normally loose some optimizations around precompiled SQL statements, but given how MS SQL optimizes query execution on the server the savings are minimal.

Related

Sequelize how to return result as a 2D array instead of array of objects?

I am using Sequelize query() method as follows:
const sequelize = new Sequelize(...);
...
// IMPORTANT: No changed allowed on this query
const queryFromUser = "SELECT table1.colname, table2.colname FROM table1 JOIN table2 ON/*...*/";
const result = await sequelize.query(queryFromUser);
Because I am selecting two columns with identical names (colname), in the result, I am getting something like:
[{ "colname": "val1" }, { "colname": "val2" }...], and this array contains values only from the column table2.colname, as it is overwriting the table1.colname values.
I know that there is an option to use aliases in the SQL query with AS, but I don't have control over this query.
I think it would solve the issue, if there was a way to return the result as a 2D array, instead of the array of objects? Are there any ways to configure the Sequelize query that way?
Im afraid this will not be possible without changes in the library directly connecting to the database and parsing its response.
The reason is:
database returns BOTH values
then in javascript, there is mapping of received rows values to objects
This mapping would looks something like that
// RETURNED VALUE FROM DB: row1 -> fieldName:value&fieldName:value2
// and then javascript code for parsing values from database would look similar to that:
const row = {};
row.fieldName = value;
row.fieldName = value2;
return row;
As you see - unless you change the inner mechanism in the libraries, its impossible to change this (javascript object) behaviour.
UNLESS You are using mysql... If you are using mysql, you might use this https://github.com/mysqljs/mysql#joins-with-overlapping-column-names but there is one catch... Sequelize is not supporting this option, and because of that, you would be forced to maintain usage of both libraries at ones (and both connected)
Behind this line, is older answer (before ,,no change in query'' was added)
Because you use direct sql query (not build by sequelize, but written by hand) you need to alias the columns properly.
So as you saw, one the the colname would be overwritten by the other.
SELECT table1.colname, table2.colname FROM table1 JOIN table2 ON/*...*/
But if you alias then, then that collision will not occur
SELECT table1.colname as colName1, table2.colname as colName2 FROM table1 JOIN table2 ON/*...*/
and you will end up with rows like: {colName1: ..., colName2: ...}
If you use sequelize build in query builder with models - sequelize would alias everything and then return everything with names you wanted.
PS: Here is a link for some basics about aliasing in sql, as you may aliast more than just a column names https://www.w3schools.com/sql/sql_alias.asp
In my case I was using:
const newVal = await sequelize.query(query, {
replacements: [null],
type: QueryTypes.SELECT,
})
I removed type: QueryTypes.SELECT, and it worked fine for me.

appendRow returns [Ljava.lang.Object;# instead of values

I want to copy form submissions over to a different sheet so that the copied data can be edited without affecting the original submissions.
I have the following code:
function copy2(){
var responses = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("from");
var tracker = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("to");
var lastrow = responses.getLastRow();
var col = responses.getLastColumn();
var row = responses.getRange(lastrow, 1, 1, col).getValues();
tracker.appendRow([null,row[0]]);
Using null in appendRow helps you move the info over to the next column. However, it doesn't quite work with the row[0] array. If I remove the null it works fine, but I want the info copied on a column different that the first one.
Why Ljava.lang.Object?
Because you are using the older Rhino runtime that was written in Java. Hence when something unexpected happens you get a glimpse of the infrastructure GAS is built upon. Now, the java.lang.object is a base class in Java from which other objects, including arrays, are derived.
Since the appendRow method signature's only parameter accepts a one-dimensional array of values, your row[0], which contains an array (see what getvalues method returns), made it to the sheet as a string tag indicating that this was an object at runtime.
What to do in Rhino?
All solutions depend on taking [ null ] as your base array and using concat to append the rest of the first row, something like this: [ null ].concat(row[0]). You can also use push with a simple for loop for better performance.
What to do in V80?
As the other answer mentioned, your best bet is the spread syntax. You can also do a push(...row[0]) to avoid concatenation of arrays (since you immediately use and discard the copy resulting from [ null, ...row[0] ]).
0 See official docs on how to migrate to V8 to take advantage of new language features and improved speed.
Explanation:
The approach of using null is clearly a workaround and not a futureproof
solution. Namely, if you want to start pasting from column 4 you
would have to do [null,null,null,...row[0]] which is not the proper
way to do it in my opinion.
I would advice you to get rid of appendRow and null since you
want to paste the data from the second column onwards. Therefore,
use setValues() instead.
Replace:
tracker.appendRow([null,row[0]]);
with:
tracker.getRange(tracker.getLastRow()+1,2,1,row[0].length).setValues(row);
Complete Solution:
function copy2(){
var responses = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("from");
var tracker = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("to");
var lastrow = responses.getLastRow();
var col = responses.getLastColumn();
var row = responses.getRange(lastrow, 1, 1, col).getValues();
tracker.getRange(tracker.getLastRow()+1,2,1,row[0].length).setValues(row);
}
The row variable contains an array so you should use the spread operator with appendRow
Replace:
tracker.appendRow([null,row[0]]);
with:
tracker.appendRow([null,...row[0]]);
Make sure your project is enabled for Chrome V8 runtime.

Javascript object with arrays to search param style query string

Looking for clean way to convert a javascript object containing arrays as values to a search param compatible query string. Serializing an element from each array before moving to the next index.
Using libraries such as querystring or qs, converts the object just fine, but handles each array independently. Passing the resulting string to the server (which I cannot change) causes an error in handling of the items as each previous value is overwritten by the next. Using any kind of array notation in the query string is not supported. The only option I have not tried is a custom sort function, but seems like it would be worse than writing a custom function to parse the object. Any revision to the object that would generate the expected result is welcome as well.
var qs = require("qs")
var jsobj = {
origString:['abc','123'],
newString:['abcd','1234'],
action:'compare'
}
qs.stringify(jsobj,{encode:false})
qs.stringify(jsobj,{encode:false,indices:false})
qs.stringify(jsobj,{encode:false,indices:false,arrayFormat:'repeat'})
Result returned is
"origString=abc&origString=123&newString=abcd&newString=1234&action=compare"
Result desired would be
"origString=abc&newString=abcd&origString=123&newString=1234&action=compare"
I tried reorder your json:
> var jsobj = [{origString: 'abc', newString: 'abcd' }, {origString: '123',
newString: '1234' }, {action:'compare'}]
> qs.stringify(jsobj,{encode:false})
'0[origString]=abc&0[newString]=abcd&1[origString]=123&1[newString]=1234&2[action]=compare'
But I don't know if this is a good alternative for your problem.
Chalk this up to misunderstanding of the application. After spending some more time with the API I realized my mistake, and as posted above by others, order does no matter. Not sure why my first several attempts failed but the question is 'answered'

How can I print a list of recursive, ordered, dependent tables for a specified MySQL table

Given: table dependencies in order (Generated by MySQL Foward Engineer script)
tableA, NULL
tableB, NULL
tableB, tableA
tableC, NULL
tableC, tableA
tableD, NULL
tableD, tableC
I have this MySQL database that has 40+ relational tables. So I wrote a little Node.js program that would list out tables and their dependencies.
I'd like to know what are the dependent tables (recursively) and the order they need to be populated for a specified table.
So for example, if I wanted to add a record to tableD:
tableD (relies on) tableC (which relies on) tableA
So in order to populate tableD, I'll need to insert records in this order:
tableA,
tableC,
tableD
I feel like this is not a new problem so there is probably something out there already that I'm missing.
So, another illustration: If I have a list of 40+ tables and I want to pick a table at the bottom of the list and it only requires 2 tables to be populated before I can populate the table, I don't want to populate all 40 tables if I don't have to.
I'm not quite sure about the way you are representing dependencies in your example, but in general this a graph problem known as topological sorting. You can find the topological sort order using depth-first search, which is simple to implement recursively. You just need to be careful of circular dependencies, for which there is no topological order. This reflects the fact that a can't require b while b also requires a.
To do this you can simply model your dependencies as an object where each key represents a table and it's value is an array of its dependencies. Then use a standard DFS algorithm to work from your start node until there's nothing left to discover. The order you discover nodes will be the opposite of your dependency graph, so we can just unshift() these into an array. The final array will be the order you should load your tables.
let deps = {
a: [], // a has no dependencies
b: [],
c: ['a'],
d: ['a', 'b'], // d depends on a & b
e: ['d', 'c'],
f: ['g'],
g: ['e']
}
function getDeps(node, visited = new Set(), discovered = new Set(), res = [] ) {
visited.add(node)
for (child of deps[node]) {
if (discovered.has(child)) continue
if (visited.has(child)) throw("circular dependency")
discovered.add(child)
getDeps(child, visited, discovered, res)
}
res.unshift(node)
return res
}
console.log(getDeps('g'))

Deserializing JavaScript object instance

I am working on an app that heavily uses JavaScript. I am attempting to include some object-oriented practices. In this attempt, I have created a basic class like such:
function Item() { this.init(); }
Item.prototype = {
init: function () {
this.data = {
id: 0,
name: "",
description: ""
}
},
save: function() {
alert("Saving...");
$.ajax({
url: getUrl(),
type: "POST",
data: JSON.stringify(this.data),
contentType: "application/json"
});
}
}
I am creating Item instances in my app and then saving them to local storage like such:
Item item = new Item();
window.localStorage.setItem("itemKey", JSON.stringify(item));
On another page, or at another time, I am retriving that item from local storage like such:
var item = window.localStorage.getItem("itemKey");
item = JSON.parse(item);
item.save();
Unfortunately, the "save" function does not seem to get reached. In the console window, there is an error that says:
*save_Click
(anonymous function)
onclick*
I have a hunch that the "(anonymous function)" is the console window's way of saying "calling item.save(), but item is an anonymous type, so I am trying to access an anonymous function". My problem is, I'm not sure how to convert "var item" into an Item class instance again. Can someone please show me?
Short answer:
Functions cannot be serialized into JSON.
Explanation:
JSON is a cross-platform serialization scheme based on a subset of JS literal syntax. This being the case, it can only store certain things. Per http://www.json.org/ :
Objects: An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).
Arrays: An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).
values: A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.
Functions cannot be serialized into JSON because another non-JS platform would not be able to unserialize and use it. Consider the example in reverse. Say I had a PHP object at my server which contained properties and methods. If I serialized that object with PHP's json_encode() and methods were included in the output, how would my JavaScript ever be able to parse and understand PHP code in the methods, let alone use those methods?
What you are seeing in your resulting JSON is the toString() value of the function on the platform you're using. The JSON serilizer calls toString() on anything being serialized which isn't proper for JSON.
I believe your solution is to stop storing instances in JSON/local storage. Rather, save pertinent data for an instance which you set back to a new instance when you need.
I know this question is answered already, however I stumbled upon this by accident and wanted to share a solution to this problem, if anyone is interested.
instead of doing this:
var item = window.localStorage.getItem("itemKey");
item = JSON.parse(item);
item.save();
do something like this:
// get serialized JSON
var itemData = window.localStorage.getItem("itemKey");
//instantiate new Item object
var item = new Item();
// extend item with data
$.extend(item, JSON.parse(itemData));
// this should now work
item.save();
this will work so long as the function you are wanting to call (ie, save()) is prototypal and not an instance method (often times the case, and is indeed the case in the OP's original question.
the $.extend method is a utility method of jquery, but it is trivial to roll your own.
You cant do that, how can javascript possibly knows that item have a save function ? json doesnt allow functions as datas. just read the json spec , you cant save functions.
what you need to do is to create a serialize and deserialize method in the hash you want to stock. that will specifiy what to export and how you can "wake up" an object after parsing the corresponding json string.
You can only store plain Objects in DOMstorages (cookies, urlparams..., everything that needs [de]serialisation through JSON.stringify/JSON.parse). So what you did when sending the ajax data
ajaxsend(this.data);
also applies to string serialisation. You can only store the data, not the instance attributes (like prototype, constructor etc.). So use
savestring(JSON.stringify(item.data));
which is possible because item.data is such a plain Object. And when restoring it, you will only get that plain data Object back. In your case it's easy to reconstruct a Item instance from plain data, because your Items hold their values (only) in a public available property:
var item = new Item;
item.data = JSON.parse(getjsonstring());
Disclaimer
Not a full time time J.S. Developer, answer may have some minor bugs:
Long Boring Explanation
As mentioned by #JAAulde, your object cannot be serialized into JSON, because has functions, the technique that you are using doesn't allow it.
Many people forget or ignore that the objects that are used in an application, may not be exactly the same as saved / restored from storage.
Short & quick Answer
Since you already encapsulate the data members of your object into a single field,
you may want to try something like this:
// create J.S. object from prototype
Item item = new Item();
// assign values as you app. logic requires
item.data.name = "John Doe";
item.data.description = "Cool developer, office ladies, love him";
// encoded item into a JSON style string, not stored yet
var encodedItem = JSON.stringify(item.data)
// store string as a JSON string
window.localStorage.setItem("itemKey", encodedItem);
// do several stuff
// recover item from storage as JSON encoded string
var encodedItem = window.localStorage.getItem("itemKey");
// transform into J.S. object
item.data = JSON.parse(encodedItem);
// do other stuff
Cheers.

Categories

Resources