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/agershun/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
60 changes: 60 additions & 0 deletions test/_test847.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
if (typeof exports === 'object') {
var alasql = require('..');
}

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

describe('Test 847 - Testing backtick call function', function () {
mathiasrw marked this conversation as resolved.
Show resolved Hide resolved
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}
`);
});
});
167 changes: 167 additions & 0 deletions test/test1512.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
if (typeof exports === 'object') {
var assert = require('assert');
var alasql = require('../dist/alasql');
}

/*
Test for PR #1512
*/

var test = '1512'; // insert test file number

describe('Test ' + test + ' - 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])]];
}
2 changes: 1 addition & 1 deletion test/test238.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"100": 100}]
[{"100":100}]
2 changes: 1 addition & 1 deletion test/test270.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ if (typeof exports === 'object') {
}

describe('Test 270 RECORDSET tests', function () {
const pluck = (arr, key) => arr.map(e => e[key])
const pluck = (arr, key) => arr.map((e) => e[key]);

var emptydata = [];
var data1 = [
Expand Down
2 changes: 1 addition & 1 deletion test/test272.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ if (typeof exports === 'object') {
}

describe('Test 272 REMOVE columns', function () {
const pluck = (arr, key) => arr.map(e => e[key]);
const pluck = (arr, key) => arr.map((e) => e[key]);

before(function () {
alasql('CREATE DATABASE test272; USE test272');
Expand Down
2 changes: 1 addition & 1 deletion test/test273.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ if (typeof exports === 'object') {
}

describe('Test 273 Source columns detextion', function () {
const pluck = (arr, key) => arr.map(e => e[key])
const pluck = (arr, key) => arr.map((e) => e[key]);

before(function () {
alasql('CREATE DATABASE test273; USE test273');
Expand Down