Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling parser with backtick #1512

Draft
wants to merge 10 commits into
base: v5
Choose a base branch
from
22 changes: 21 additions & 1 deletion src/10start.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

"use strict";

const isBacktickQuery = (arg) => Array.isArray(arg.raw);

const formatQueryParams = (params) => (queryStr, index) => {
const param = params[index + 1];
return queryStr + (typeof param === 'undefined' ? '' : param);
};

const normalizeBacktickQuery = (args) => {
const stringFormatted = args[0]
.map(formatQueryParams(args))
.join('')
// Remove breakline in case of characters in same line | optional
.replace(/[\r\n]/g, '')
mathiasrw marked this conversation as resolved.
Show resolved Hide resolved
.replace(/\s+/g, ' ') // Remove extras
.trim(); // Remove extras
return stringFormatted;
};

/**
@fileoverview AlaSQL JavaScript SQL library
@see http://github.com/alasql/alasql
Expand Down Expand Up @@ -57,7 +75,9 @@
alasql().From(data).Where(function(x){return x.a == 10}).exec();
*/

var alasql = function(sql, params, cb, scope) {
var alasql = function(...args) {
var [sqlQuery, params, cb, scope] = args;
mathiasrw marked this conversation as resolved.
Show resolved Hide resolved
var sql = isBacktickQuery(sqlQuery) ? normalizeBacktickQuery(args) : sqlQuery;

params = params||[];

Expand Down
4 changes: 2 additions & 2 deletions src/17alasql.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ alasql.parser.parseError = function (str, hash) {
// My own parser here
}
*/
alasql.parse = function (sql) {
return alasqlparser.parse(alasql.utils.uncomment(sql));
alasql.parse = function (command) {
return alasqlparser.parse(alasql.utils.uncomment(command));
};

/**
Expand Down
23 changes: 12 additions & 11 deletions test/test000.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ if (typeof exports === 'object') {
var alasql = require('..');
}

describe('Test 000 - multiple statements', function () {
const test = '000'; // insert test file number
// Please consider using the github issue number as test ID
describe(`Test 000 - multiple statements`, function () {
const testID = '000';

before(function () {
alasql('create database test' + test);
alasql('use test' + test);
before(function () {
alasql('create database test' + testID);
alasql('use test' + testID);
});

after(function () {
alasql('drop database test' + test);
alasql('drop database test' + testID);
});

it('A) From single lines', function () {
var res = [];
const res = [];
res.push(alasql('create table one (a int)'));
res.push(alasql('insert into one values (1),(2),(3),(4),(5)'));
res.push(alasql('select * from one'));
Expand All @@ -25,19 +26,19 @@ describe('Test 000 - multiple statements', function () {

it('B) Multiple statements in one string', function () {
//
var sql = 'create table two (a int);';
let sql = 'create table two (a int);';
sql += 'insert into two values (1),(2),(3),(4),(5);';
sql += 'select * from two;';
var res = alasql(sql);
let res = alasql(sql);
assert.deepEqual(res, [1, 5, [{a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}]]);
});

it('C) Multiple statements in one string with callback', function (done) {
// Please note that first parameter (here `done`) must be called if defined - and is needed when testing async code
var sql = 'create table three (a int);';
let sql = 'create table three (a int);';
sql += 'insert into three values (1),(2),(3),(4),(5);';
sql += 'select * from three;';
alasql(sql, function (res) {
alasql.promise(sql).then(function (res) {
assert.deepEqual(res, [1, 5, [{a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}]]);
done();
});
Expand Down
161 changes: 161 additions & 0 deletions test/test1723a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
if (typeof exports === 'object') {
var assert = require('assert');
var alasql = require('../dist/alasql');
}

describe('Issue #1723 - tagFunction for template strings', function () {
it('Will mark free fields as parameters', function (done) {
assert.deepEqual(tagBraid`SELECT 123 as abc`, ['SELECT 123 as abc']);
assert.deepEqual(tagBraid`SELECT ${123} as abc`, ['SELECT ? as abc', [123]]);
assert.deepEqual(tagBraid`${'SELECT'} ${123} as abc`, ['? ? as abc', ['SELECT', 123]]);
assert.deepEqual(tagBraid`${'SELECT'} ${123} as ${'abc'}`, [
'? ? as ?',
['SELECT', 123, 'abc'],
]);
done();
});

it('Will work second time when data is fetched from the cache', function (done) {
assert.deepEqual(tagBraid`SELECT 123 as abc`, ['SELECT 123 as abc']);
assert.deepEqual(tagBraid`SELECT ${123} as abc`, ['SELECT ? as abc', [123]]);
assert.deepEqual(tagBraid`${'SELECT'} ${123} as abc`, ['? ? as abc', ['SELECT', 123]]);
assert.deepEqual(tagBraid`${'SELECT'} ${123} as ${'abc'}`, [
'? ? as ?',
['SELECT', 123, 'abc'],
]);
done();
});

it('Will inline connected fields', function (done) {
assert.deepEqual(tagBraid`S${'ELECT'} 1${23} as ab${'c'}`, ['SELECT 123 as abc', []]);
assert.deepEqual(tagBraid`SELECT 123 as ${'ab'}${'c'}`, ['SELECT 123 as abc', []]);
done();
});

it('Will treat "()," as free space and become parameter', function (done) {
assert.deepEqual(tagBraid`SELECT AVG(${1},${2},${3}) as abc`, [
'SELECT AVG(?,?,?) as abc',
[1, 2, 3],
]);
done();
});

it('Can force free fields as inline', function (done) {
assert.deepEqual(tagBraid`~${'SELECT'} ~${123} as abc`, ['SELECT 123 as abc', []]);
assert.deepEqual(tagBraid`~${'SELECT'} ~${123} as ${'abc'}`, ['SELECT 123 as ?', ['abc']]);
assert.deepEqual(tagBraid`${'SELECT'} ${123} as ~${'abc'}`, ['? ? as abc', ['SELECT', 123]]);
assert.deepEqual(tagBraid`${'SELECT'} ${123} as ~${'abc'}~`, ['? ? as ~abc~', ['SELECT', 123]]);
assert.deepEqual(tagBraid`SELECT AVG(~${1},~${2},${3}) as abc`, [
'SELECT AVG(1,2,?) as abc',
[3],
]);
done();
});

it('Default to markring as parameter (option B from PR #1512)', function (done) {
let items = `toys`;
let type = 'Montessori';
let item = 'batman';
let orderBy = `ORDER BY x desc, y asc`;

let res = tagBraid`
SELECT author
FROM ${items}
WHERE
AND type = ${type}_v2
AND name = ${item}
~${orderBy}
`;

let expected = `
SELECT author
FROM ?
WHERE
AND type = Montessori_v2
AND name = ?
ORDER BY x desc, y asc
`;
assert.deepEqual(res, [expected, [`toys`, 'batman']]);
done();
});

/*it('Will return a promise', async function (done) {
let res = alasql`SELECT 123`;
assert(typeof res.then === 'function');
assert.equal(await alasql`SELECT 123`.then((x) => 555), 555);
done();
});

it('Will return the data from the query', async function (done) {
assert.equal(await alasql`VAlUE OF SELECT 123`, 123);
assert.deepEqual(await alasql`SELECT 123 as abc`, [{abc: 123}]);
done();
});

it('Will inline string connected to other areas', async function (done) {
assert.deepEqual(await alasql`SELECT 123 as abc`, [{abc: 123}]);
done();
});*/
});

const re = {
preFree: /[\(,\s]~?$/,
postFree: /^[\),\s]/,
};

const cache = new Map();

function tagBraid(template, ...params) {
if (
!Array.isArray(template) ||
!Array.isArray(template.raw) ||
template.length - 1 != params.length
)
throw 'Please use as tagfunction to provide the right arguments';

if (1 == template.length) return [template[0]];

let sql = '';

let paramsIDs = [];
if (cache[template.raw]) {
({sql, paramsIDs} = cache.get(template.raw));
} else {
for (let i = 0; i <= params.length; i++) {
sql += template[i];

if (i === params.length) break;

let inline = true;

// if the field is "free" and not connected to other texts
if (
(re.preFree.test(template[i]) ||
(0 === i && ('' === template[i] || '~' === template[i]))) &&
(re.postFree.test(template[i + 1]) || (params.length - 1 === i && '' === template[i + 1]))
) {
inline = false;
// force inline if prepended with ~
if ('~'.charCodeAt(0) === template[i].charCodeAt(template[i].length - 1)) {
sql = sql.slice(0, -1);
inline = true;
}
}

if (inline) {
if (typeof params[i] !== 'number' && typeof params[i] !== 'string')
console.error(
'You are inlining a value that is not a string or a number so it might not work. Will proceed with the .toString() value but consider making space around the value so it can be provided as a parameter.',
{parameter: params[i], template: template.raw}
);
sql += params[i].toString();
} else {
sql += '?';
paramsIDs.push(i);
}
}
cache.set(template.raw, {sql, paramsIDs});
}

return [sql, [...paramsIDs.map((x) => params[x])]];
}
60 changes: 60 additions & 0 deletions test/test1723b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
if (typeof exports === 'object') {
var alasql = require('../dist/alasql');
}

const table = {
name: 'midnightcalls',
columns: [
{name: 'track_name', type: 'string'},
{name: 'author', type: 'string'},
{name: 'views', type: 'int'},
],
};

describe('Issue #1723 - Testing backtick call function', function () {
it('1. Create table', function () {
alasql`DROP TABLE IF EXISTS test`;
alasql`CREATE TABLE test (a int, b int)`;
});

it('2. Insert values ', function () {
alasql`INSERT INTO test VALUES (1,1)`;
alasql`INSERT INTO test VALUES (1,7)`;
alasql`INSERT INTO test VALUES (2,2)`;
alasql`INSERT INTO test VALUES (3,3)`;
});

it('3. Create a new table', function () {
alasql`DROP TABLE IF EXISTS ${table.name}`;

alasql(`
CREATE TABLE ${table.name} (${table.columns
.map((item) => ` ${item.name} ${item.type.toUpperCase()}`)
.join(', ')
.toString()})
`);
});

it('4. Insert values', function () {
const values = [
['qhAfaWdLbIE', 'Baby bi', 'Yunk Vino', 72],
['YA-db3f8Ak4', 'Sonar', 'Yunk Vino', 809],
];
const valuesToInsert = values
.map(
(item, i) =>
`('${item[0]}', '${item[1]}', '${item[2]}', ${item[3]})${
i + 1 === values.length ? '' : ', '
}`
)
.join('');

//console.log(valuesToInsert);

alasql(`
INSERT INTO ${table.name}
VALUES
${valuesToInsert}
`);
});
});