loop to generate a unique url-frinedly route - mysql

I have a table in mariadb in this structure,
CREATE TABLE `items` (
`id` char(36), `route` varchar(255), `value` text,
PRIMARY KEY (`id`),
UNIQUE KEY `route` (`route`)
)
I use the route column to get user-friendy urls, such as http://www.examle.com/this-is-the-route-of-an-item
When a user creates a new item, apart from omitting spaces and illegal characters, I would like to "catch" cases where the route chosen for the new item is in use, and generate a valid route.
For example, if route-of-an-item is already in use, i would fallback to route-of-an-item-a, or route-of-an-item-b, etc.
The naive solution could be querying db in a loop, for example (kind of pseudo code):
var additionalChars = "";
while (db.query("select count * from `items` where `route`='" + route + "-" + additionalChars + "'"))
additionalChars = nextAdditionalChars(additionalChars);
finalRoute = route + '-' + additionalChars;
Since this involves querying the db many times I thought of another solution.
var additionalChars = "";
var usedRoutes = db.query("select `route` from `items` where `route` like '" + route + "%'");
while(usedRoutes.contains(route + '-' + additionalChars))
additionalChars = nextAdditionalChars(additionalChars);
finalRoute = route + '-' + additionalChars;
Is there any better way to approach this kind of a problem?
Am I correct that the second solution would perform better?
If I use the second solution, should I add a fulltext index to route field?

You could sort your query by route descending and only retrieve and check one item. In your pseudo code this would look like:
var additionalChars = "";
var usedRoutes = db.query("select `route` from `items` where `route` like '" + route + "%' order by `route` desc limit 1");
if(usedRoutes.route is already in use)
additionalChars = nextAdditionalChars(additionalChars);
finalRoute = route + '-' + additionalChars;

Ok, so after consulting with a colleague I ended up using solution 2, here is the code (node.js), in case anyone faces this problem:
db access
var route = req.body.route || 'noname';
route = route.replace(/[\s_]/g, '-').toLowerCase().replace(/[^0-9a-z\u0591-\u05F4\u0621-\u064A\-_\s]/g, "").replace(/_+/g, ' ').trim().replace(/[\s_]+/g, '-');
var isArabic = (/[\u0621-\u064A]/g).test(route),
isHebrew = (/[\u0591-\u05F4]/g).test(route),
lang = isArabic ? 'ar' : (isHebrew ? 'he' : 'en');
Items.findAll({ where: { route: { $like: route + '%' } }, attributes: ['route'] })
.then((items) => {
var routes = _.keyBy(items, 'route'),
prefix = '';
while (routes[route + (prefix ? '-' + prefix : '')])
prefix = charactersCount(prefix, lang);
Items.create({ route: route + (prefix ? '-' + prefix : ''), value: req.body.value })
.then(function(item){
res.send({ item: _.pick(item, ['id', 'route', 'author_id', 'created_at']) })
})
.catch(function(){ res.sendStatus(500)});
})
.catch(function(){ res.sendStatus(500) });
generate additional characters
var chars = {
ar: { val: "اﻻبتثجحخدذرزسشصضطظعغفقكلمنهةوىي", len: 0 },
he: { val: "אבגדהוזחטיכלמנסעפצקרשת", len: 0 },
en: { val: "abcdefghijklmnopqrstuvwxyz", len: 0 }
};
_.forEach(chars, (c) => { c.len = c.val.length });
function charactersCount (current, lang) => {
if (!current) return chars[lang].val[0];
lang = lang || 'en';
var curr = current.split(''),
len = curr.length,
pointer = len,
lastIndex;
while ((lastIndex = chars[lang].val.indexOf(curr[--pointer]) + 1) >= chars[lang].len) curr[pointer] = chars[lang].val[0];
if (pointer < 0) { curr.unshift(''); pointer++; }
curr[pointer] = chars[lang].val[lastIndex];
return curr.join('');
}
so I end up with one select query and one inset query, and prevent the clashes in node's side.
and a fulltext index is not needed since the % apears only at the end of the like operater

Related

How do I insert a mysql row from Object (in nodeJS)?

Right now I use this cumbersome approach when I want to add a row whose data is in a JS Object
Adding a row to a table:
const mysql = require('mysql')
var db = mysql.createConnection(DBInfo)
var databaseObj = {val1: '1', name: 'John', age: 40} // row to insert
var query = 'INSERT INTO my_table ('
var databaseKeys = Object.keys(databaseObj)
for (let i = 0; i < databaseKeys.length; i++) {
query += databaseKeys[i] + (i !== databaseKeys.length - 1 ? ', ' : ')')
}
query += ' ' + 'VALUES('
for (let i = 0; i < databaseKeys.length; i++) {
query += '\'' + databaseObj[databaseKeys[i]] + '\'' + (i !== databaseKeys.length - 1 ? ', ' : ')')
}
db.query(query, function (err, results, fields) {...
Is there any simpler or neater way to add a row into a table, where such row data is in a JS Object? The examples I see around use an array of arrays, but in my case the info is in a Object
I should use the INSERT into table SET because they are equivalent
var db = mysql.createConnection(DBInfo)
var databaseObj = {val1: '1', name: 'John', age: 40}
var query = 'INSERT INTO my_table SET ' + db.escape(databaseObj)
db.query(query, function (err, results, fields) {...

Google Apps Script - How do i get my AdminDirectory.Users.List query to be accepted?

I'm using GAS to create a contact lookup, where the function parameters build a query that is used in AdminDirectory.Users.List . This problem only happens when the orgTitle and orgDepartment parameters are used. When I copy and paste the Logger output in place of the q variable, the query works. If I used the q variable, it fails.
function listAllUsersBase(name = null, familyName = null, givenName = null, orgTitle = 'Help Desk', orgDepartment = null) {
var aQuery = 'query:\''
if (name !== null){
Logger.log('Name: ' + name);
var aQuery = aQuery + 'name:' + name + ' ';
} if (familyName !== null){
Logger.log('familyName: ' + familyName);
var aQuery = aQuery + 'familyName:' + familyName + ' ';
} if (givenName !== null){
Logger.log('givenName: ' + givenName);
var aQuery = aQuery + 'givenName:' + givenName + ' ';
} if (orgTitle !== null){
Logger.log('orgTitle: ' + orgTitle);
var aQuery = aQuery + 'orgTitle:\\\'' + orgTitle + '\\\' ';
} if (orgDepartment !== null){
Logger.log('orgDepartment: ' + orgDepartment)
var aQuery = aQuery + 'orgDepartment:\\\'' + orgDepartment + '\\\' ';
}
var aQuery = aQuery + '\''
var q = aQuery.toString();
Logger.log(q);
var pageToken;
var page;
do {
page = AdminDirectory.Users.list({
domain: 'yourdomain.com',
query: q,
orderBy: 'givenName',
maxResults: 100,
pageToken: pageToken
});
var users = page.users;
if (users) {
for (var i = 0; i < users.length; i++) {
var user = users[i];
Logger.log('%s (%s)', user.name.fullName, user.primaryEmail);
}
} else {
Logger.log('No users found.');
}
pageToken = page.nextPageToken;
} while (pageToken);
}
There are a number of issues with your script.
First, you do not need to re-declare the aQuery variable. The first declaration of var aQuery = ... is sufficient for your use-case.
Second, query is not a valid field for a search clause so it is not necessary (see documentation with list of valid fields available to search clauses). To clarify, when you first declare your aQuery variable you initialized it as var aQuery = 'query:\'';. Here, query is being used as a field in a search clause. However, it is not a valid option in this context.
Third, the position of the opening single-quotes on the field values for orgTitle and orgDepartment are incorrect, the opening escaped single-quote should precede the escaped backslash.
So, instead of:
var aQuery = aQuery + 'orgTitle:\\\'' + orgTitle + '\\\' ';
you should have:
var aQuery = aQuery + 'orgTitle:\'\\' + orgTitle + '\\\' ';

Mysql NodeJs syncronous queries

Thank you for the answer,
I did that : i used "sync-mysql" :
but now its very very slow...
Maybe i could do the same code using Mysql NPM
Do you know how my code must look like if I want to use asyncronous function and doing the same thing as below ? It will help me a lot :)
I have almost finished my project and I only have this function left
const customer_booked = []
customer_booked[0] = []
customer_booked[1] = []
let sql = "SELECT * " +
"FROM customer as C " +
"WHERE customer_reference REGEXP '^[c]i*' "
if (filters[0].value.length){
sql += "AND C.customer_name LIKE '%" + filters[0].value + "%' "
}
if (filters[3].value.length){
sql += "LIMIT " + filters[3].value
}
var result = connection.query(sql);
const customers = [];
const booked = connection.query('SELECT cr.customer_id, a.codeAgent ' +
'FROM customer_reservation as cr ' +
'INNER JOIN agent as a ' +
'ON a.id = cr.agent_id')
booked.forEach(customer_booking => {
customer_booked[0].push(customer_booking.customer_id)
customer_booked[1].push(customer_booking.codeAgent)
});
result.forEach( customer => {
var months;
let d1 = new Date(customer.last_order);
let d2 = new Date();
months = (d2.getFullYear() - d1.getFullYear()) * 12;
months -= d1.getMonth() + 1;
months += d2.getMonth();
months = months <= 0 ? 0 : months;
if (customer_booked[0].includes(customer.customer_id)){
let code_agent_index = customer_booked[0].indexOf(customer.customer_id)
customer.available = 'booked'
customer._rowVariant = 'warning'
customer.agent_code = customer_booked[1][code_agent_index]
}
else if (months >= 12){
customer.available = 'available'
customer._rowVariant = 'success'
} else {
customer.available = 'notAvailable'
customer._rowVariant = 'danger'
}
let sql2 = "SELECT * " +
"FROM customer_addresses AS CA " +
"WHERE CA.customer_id = " + customer.id
customer.addresses = connection.query(sql2)
customers.push(customer);
//customers[customers.length].push()
})
callback(false, result)
You can use node.js async/await using IIFE, like this:
(async() => {
const users = await getUsers();
for(const user of users){
user.addresses = await getAddresses(user.id);
// your other code just translated to JS.
}
return users;
})()
So, the main idea is to await your async code.
For example we use IIFE (Immediately Invoked Function Expression) to access needed async/await and for tests.
In real code you should name functions with keyword async
Here is nice tutorials which could explain how to use async/await 1, 2

Node.js chaining promises in a loop with MySQL

I am trying to make a non-relational DB into a relational DB. So I am starting from data with no unique IDs.
I need to get the result from one SQL call loop through those rows, and for each one, do a SQL SELECT using part of the first result, then another SQL select using the next result, and then a write using IDs from the first and last queries.
I am using Node.js and ES6 promises to keep everything in order, but I seem to be missing something. I was actually trying to do an extra SQL call, and also use that result in the third query, but I am simplifying it to just get one call to feed into another.
Maybe some code will help show what I am trying to do.
Here is my query class that returns promises:
var mysql = require('mysql');
class Database {
constructor() {
this.connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "root",
database: "pressfile"
});
}
query(sql, args) {
return new Promise((resolve, reject) => {
this.connection.query(sql, args, (err, result, fields) => {
if (err) return reject(err);
resolve (result);
});
});
}
close() {
return new Promise((resolve, reject) => {
this.connection.end(err => {
if (err) return reject (err);
resolve();
});
});
}
}
This was stolen pretty much as is from a tutorial site, and this part seems to work pretty well. Then here comes the loop, and the multiple queries:
var contactId;
var address1;
var orgName;
var database = new Database();
database.query("SELECT * FROM contact")
.then( result => {
for (var i = 0; i < result.length; i++) {
contactId = result[i].contactId;
orgName = result[i].org;
var sql2 = "SELECT * FROM organization WHERE (name = \"" + orgName + "\")";
console.log(sql2);
database.query(sql2)
.then(result2 => {
console.log(result2);
var orgId = result2[0].organizationId;
var sql3 = "INSERT INTO contact_organization (contactId, organizationId) VALUES (" + contactId + ", " + orgId + ")";
console.log(sql3);
return ""; //database.query(sql3);
}).then( result3 => {
console.log(result3);
});
}
}).catch((err) => {
console.log(err);
databse.close();
});
I know it is kind of unraveling at the end, but I'm not wanting to do the INSERT query until I know I can get it right. Right now in the console, I get a valid organization object, followed by:
`INSERT INTO contact_organization (contactId, organizationId) VALUES (17848, 29)'
17848 is the final contactId that is returned in the for loop. How can I get the contactId that is assigned before the second query. I know I am not doing this asynchronous stuff right.
Try something like this. Just a quick solution. (not tested).
const selectOrg = (result) => {
contactId = result[i].contactId;
orgName = result[i].org;
var sql = "SELECT * FROM organization WHERE (name = \"" + orgName + "\")";
return database.query(sql);
};
const insertOrg = (result) => {
var orgId = result[0].organizationId;
var sql = "INSERT INTO contact_organization (contactId, organizationId) VALUES (" + contactId + ", " + orgId + ")";
return database.query(sql);
};
database.query("SELECT * FROM contact")
.then(result => {
const promises = [];
for (var i = 0; i < result.length; i++) {
promises << selectOrg(result)
.then(insertOrg);
}
return Promise.all(promises);
})
.then(allResults => {
console.log(allResults);
})
.catch((err) => {
databse.close();
});
I found a way to do this, but it is kind of cheesy. I included the contactId as a constant in the SQL query to get the organization, so I could then pass the value to the .then, keeping everything in order.
My sql2 statement becomes:
var sql2 = "SELECT *, " + contactId + " AS contactId FROM organization WHERE (name = \"" + orgName + "\")";
Then when that query returns, I can just pull the correct contactId out as result[0].contactId, from the same result I get the organizationId from.
Here is the final code:
database.query("SELECT * FROM contact")
.then( result => {
for (var i = 0; i < result.length; i++) {
var contactId = result[i].contactId;
var orgName = result[i].org;
var sql2 = "SELECT *, " + contactId + " AS contactId FROM organization WHERE (name = \"" + orgName + "\")";
database.query(sql2)
.then(result2 => {
var orgId = result2[0].organizationId;
var contactId = result2[0].contactId;
var sql3 = "INSERT INTO contact_organization (contactId, organizationId) VALUES (" + contactId + ", " + orgId + ")";
console.log(sql3);
return database.query(sql3);
}).then( result3 => {
console.log(result3);
});
}
}).catch((err) => {
console.log(err);
databse.close();
});
The console.log(result3) returns a bunch of these:
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0 }
And I got one contact_organization inserted for every contact row returned from the first query.

How to build dynamic query by binding parameters in node.js-sql?

I'm using nodejs-mysql module to do query in node.js recently, and in my working case I could only use the parameter-binding syntax like:
SELECT * FROM table WHERE name = ?
Now I want to build dynamic sql with these ? OR ?? parameters. Assume that I have 2 conditions(name and age) which either of them could be null (if user doesn't provide it),
So I want to build MySQL in 3 cases:
only name=Bob: SELECT * FROM table WHERE name = 'Bob'
only age=40: SELECT * FROM table WHERE age > 40
both: SELECT * FROM table WHERE name = 'Bob' AND age > 40
I know it's easy if you build the query on your own, but how can I achieve it when using placeholders which can only bind field or values ?
In document of nodejs-mysql, placeholder ? only stands for values and ?? stands for fields:
https://github.com/felixge/node-mysql/#escaping-query-values
https://github.com/felixge/node-mysql/#escaping-query-identifiers
My first thinking of solution is to insert query piece by using these placeholders, but it comes to failure because both ? and ?? will escape my query piece, and my query will be executed incorrectly.
My code so far is as below, which I'm defenitly sure it's not correct because query piece has been escaped:
// achieve paramters from url request
var condition = {};
if(params.name)condition["name"] = ["LIKE", "%" + params.name + "%"];
if(params.age)condition["age"] = parseInt(params.age, 10);
//build query
var sqlPiece = buildQuery(condition);
//try to replace ? with query
var sql = 'SELECT * FROM table WHERE ?';
connection.query(sql, sqlPiece, function(err, results) {
// do things
});
// my own query build function to proceed conditions
function buildQuery(condition) {
var conditionArray = [];
for(var field in condition){
var con = condition[field];
if(con !== undefined){
field = arguments[1] ? arguments[1] + "." + field : field;
var subCondition;
if(con instanceof Array) {
subCondition = field + " " + con[0] + " " + wrapString(con[1]);
}else{
subCondition = field + " = " + wrapString(con);
}
conditionArray.push(subCondition);
}
}
return conditionArray.length > 0 ? conditionArray.join(" AND ") : "1";
}
//wrap string value
function wrapString(value){
return typeof value === "string" ? "'" + value + "'" : value;
}
So is there any way I can fix this problem?
Update
Thanks to Jordan's Offer, it's working, but :
I know building query by string concat is very good, but in my case I can't use that, because I'm using some middleware or handle mysql and controller, so what I can do is to define interface, which is a sql string with placeholders. So, the interface string is predefined before, and I can't modify it during my controller function.
You're off to a really good start, but you may have been overthinking it a bit. The trick is to build a query with placeholders (?) as a string and simultaneously build an array of values.
So, if you have params = { name: 'foo', age: 40 }, you want to build the following objects:
where = 'name LIKE ? AND age = ?';
values = [ '%foo%', 40 ];
If you only have { name: 'foo' }, you'll build these instead:
where = 'name LIKE ?';
values = [ '%foo%' ];
Either way, you can use those objects directly in the query method, i.e.:
var sql = 'SELECT * FROM table WHERE ' + where;
connection.query(sql, values, function...);
How do we build those objects, then? In fact, the code is really similar to your buildQuery function, but less complex.
function buildConditions(params) {
var conditions = [];
var values = [];
var conditionsStr;
if (typeof params.name !== 'undefined') {
conditions.push("name LIKE ?");
values.push("%" + params.name + "%");
}
if (typeof params.age !== 'undefined') {
conditions.push("age = ?");
values.push(parseInt(params.age));
}
return {
where: conditions.length ?
conditions.join(' AND ') : '1',
values: values
};
}
var conditions = buildConditions(params);
var sql = 'SELECT * FROM table WHERE ' + conditions.where;
connection.query(sql, conditions.values, function(err, results) {
// do things
});
For Inserting into MYSQL like DB:
function generateInsertQuery(data, tableName) {
let part1 = `INSERT INTO ${tableName} (`;
let part2 = ")",
part3 = "VALUES (",
part4 = ")";
let tableKeys = "",
tableValues = "";
for (let key in data) {
tableKeys += `${key},`;
tableValues += `'${data[key]}',`
}
tableKeys = tableKeys.slice(0, -1);
tableValues = tableValues.slice(0, -1);
let query = `${part1}${tableKeys}${part2} ${part3}${tableValues}${part4}`;
return query;
}
generateInsertQuery({name: "Sam", tel: 09090909, email: "address#domain.com"}, "Person")
Output:
INSERT INTO Person (name,tel,email) VALUES ('Sam','9090909','address#domain.com');
Code Snippet for Update query:
function generateUpdateQuery(data, tableName, clauseKey, clauseValue) {
let part1 = `UPDATE ${tableName} SET`;
let part2 = `WHERE ${clauseKey} = ${clauseValue};`; //Add any number of filter clause statements here
let updateString = "";
for (let key in data) {
updateString += `${key} = '${data[key]}',`;
}
updateString = updateString.slice(0, -1);
let query = `${part1} ${updateString} ${part2}`;
return query;
}
generateUpdateQuery({
name: "Tanjiro",
tel: 77777777,
email: "tanjiro#demonslayer.com"
}, "Person", "ID", 111);
Output:
UPDATE Person SET name = 'Tanjiro',tel = '77777777',email = 'tanjiro#demonslayer.com' WHERE ID = 111;
I modify your code #Jordan-Running
describe("Test generateFilterQuery", () => {
it("Query filter with params", () => {
let params = []
params.push(Query.generateParams("title", "%_%", "Coding"))
params.push(Query.generateParams("published", "=", true))
console.log(Query.generateFilterQuery(params))
});
});
const qInclude = require('./QueryInclude');
exports.generateParams = (name, eq, value) => {
return {
name: name,
eq: eq, // %_%, %_, _%, =, >, <, !=,
value: value
}
}
exports.generateFilterQuery = (params) => {
let conditions, values = []
let conditionsStr;
if (params.length == 0) {
return false
}
[conditions, values] = qInclude.queryCondition(params)
let build = {
where: conditions.length ?
conditions.join(' AND ') : '1',
values: values
};
let query = 'SELECT * FROM table WHERE ' + build.where;
return [query, build.values]
}
exports.queryCondition = (params) => {
var conditions = [];
var values = [];
params.forEach(item => {
switch (item.eq) {
case '=': {
conditions.push(item.name + " = ?");
values.push(item.value);
break;
}
case '!=': {
conditions.push(item.name + " != ?");
values.push(item.value);
break;
}
case '<': {
conditions.push(item.name + " < ?");
values.push(item.value);
break;
}
case '>': {
conditions.push(item.name + " > ?");
values.push(item.value);
break;
}
case '%_%': {
conditions.push(item.name + " LIKE ?");
values.push("%" + item.value + "%");
break;
}
case '%_': {
conditions.push(item.name + " LIKE ?");
values.push("%" + item.value);
break;
}
case '_%': {
conditions.push(item.name + " LIKE ?");
values.push(item.value + "%");
break;
}
}
});
return [conditions, values]
}