rbql 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/package.json +1 -1
- package/rbql.js +40 -18
package/README.md
CHANGED
|
@@ -267,6 +267,7 @@ $ rbql-js --input input.csv --output result.csv
|
|
|
267
267
|
* GROUP BY
|
|
268
268
|
* TOP _N_
|
|
269
269
|
* LIMIT _N_
|
|
270
|
+
* AS
|
|
270
271
|
|
|
271
272
|
All keywords have the same meaning as in SQL queries. You can check them [online](https://www.w3schools.com/sql/default.asp)
|
|
272
273
|
|
|
@@ -306,7 +307,7 @@ _COUNT_, _ARRAY_AGG_, _MIN_, _MAX_, _SUM_, _AVG_, _VARIANCE_, _MEDIAN_
|
|
|
306
307
|
Limitation: aggregate functions inside JavaScript expressions are not supported. Although you can use expressions inside aggregate functions.
|
|
307
308
|
E.g. `MAX(float(a1) / 1000)` - valid; `MAX(a1) / 1000` - invalid.
|
|
308
309
|
There is a workaround for the limitation above for _ARRAY_AGG_ function which supports an optional parameter - a callback function that can do something with the aggregated array. Example:
|
|
309
|
-
`
|
|
310
|
+
`SELECT a2, ARRAY_AGG(a1, v => v.sort().slice(0, 5)) GROUP BY a2`
|
|
310
311
|
|
|
311
312
|
|
|
312
313
|
### JOIN statements
|
|
@@ -346,14 +347,15 @@ You can define custom functions and/or import libraries in a special file: `~/.r
|
|
|
346
347
|
|
|
347
348
|
## Examples of RBQL queries
|
|
348
349
|
|
|
349
|
-
* `
|
|
350
|
-
* `
|
|
351
|
-
* `
|
|
352
|
-
* `
|
|
353
|
-
* `
|
|
354
|
-
* `
|
|
355
|
-
* `
|
|
356
|
-
* `
|
|
350
|
+
* `SELECT TOP 100 a1, a2 * 10, a4.length WHERE a1 == "Buy" ORDER BY parseInt(a2) DESC`
|
|
351
|
+
* `SELECT a.id, a.weight / 1000 AS weight_kg`
|
|
352
|
+
* `SELECT * ORDER BY Math.random()` - random sort
|
|
353
|
+
* `SELECT TOP 20 a.vehicle_price.length / 10, a2 WHERE parseInt(a.vehicle_price) < 500 && ["car", "plane", "boat"].indexOf(a['Vehicle type']) > -1 limit 20` - referencing columns by names from header
|
|
354
|
+
* `UPDATE SET a3 = 'NPC' WHERE a3.indexOf('Non-playable character') != -1`
|
|
355
|
+
* `SELECT NR, *` - enumerate records, NR is 1-based
|
|
356
|
+
* `SELECT a1, b1, b2 INNER JOIN ./countries.txt ON a2 == b1 ORDER BY a1, a3` - example of join query
|
|
357
|
+
* `SELECT MAX(a1), MIN(a1) WHERE a.Name != 'John' GROUP BY a2, a3` - example of aggregate query
|
|
358
|
+
* `SELECT ...a1.split(':')` - Using JS "destructuring assignment" syntax to split one column into many. Do not try this with other SQL engines!
|
|
357
359
|
|
|
358
360
|
|
|
359
361
|
### References
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rbql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Rainbow Query Language",
|
|
5
5
|
"keywords": ["CSV", "TSV", "spreadsheet", "SQL", "SQL-like", "transpiler", "CLI", "command-line", "library", "browser", "Node", "select", "update", "join"],
|
|
6
6
|
"scripts": {
|
package/rbql.js
CHANGED
|
@@ -70,7 +70,7 @@ var query_context = null; // Needs to be global for MIN(), MAX(), etc functions.
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
const wrong_aggregation_usage_error = 'Usage of RBQL aggregation functions inside JavaScript expressions is not allowed, see the docs';
|
|
73
|
-
const RBQL_VERSION = '0.
|
|
73
|
+
const RBQL_VERSION = '0.26.0';
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
function check_if_brackets_match(opening_bracket, closing_bracket) {
|
|
@@ -128,28 +128,32 @@ function column_info_from_text_span(text_span, string_literals) {
|
|
|
128
128
|
let attribute_match = /^([ab])\.([_a-zA-Z][_a-zA-Z0-9]*)$/.exec(text_span);
|
|
129
129
|
let subscript_int_match = /^([ab])\[([0-9]+)\]$/.exec(text_span);
|
|
130
130
|
let subscript_str_match = /^([ab])\[___RBQL_STRING_LITERAL([0-9]+)___\]$/.exec(text_span);
|
|
131
|
+
let as_alias_match = /^(.*) (as|AS) +([a-zA-Z][a-zA-Z0-9_]*) *$/.exec(text_span);
|
|
132
|
+
if (as_alias_match !== null) {
|
|
133
|
+
return {table_name: null, column_index: null, column_name: as_alias_match[3], is_star: false, is_alias: true};
|
|
134
|
+
}
|
|
131
135
|
if (simple_var_match !== null) {
|
|
132
136
|
if (text_span == rbql_star_marker)
|
|
133
|
-
return {table_name: null, column_index: null, column_name: null, is_star: true};
|
|
137
|
+
return {table_name: null, column_index: null, column_name: null, is_star: true, is_alias: false};
|
|
134
138
|
if (text_span.startsWith('___RBQL_STRING_LITERAL'))
|
|
135
139
|
return null;
|
|
136
140
|
let match = /^([ab])([0-9]+)$/.exec(text_span);
|
|
137
141
|
if (match !== null) {
|
|
138
|
-
return {table_name: match[1], column_index: parseInt(match[2]) - 1, column_name: null, is_star: false};
|
|
142
|
+
return {table_name: match[1], column_index: parseInt(match[2]) - 1, column_name: null, is_star: false, is_alias: false};
|
|
139
143
|
}
|
|
140
144
|
// Some examples for this branch: NR, NF
|
|
141
|
-
return {table_name: null, column_index: null, column_name: text_span, is_star: false};
|
|
145
|
+
return {table_name: null, column_index: null, column_name: text_span, is_star: false, is_alias: false};
|
|
142
146
|
} else if (attribute_match !== null) {
|
|
143
147
|
let table_name = attribute_match[1];
|
|
144
148
|
let column_name = attribute_match[2];
|
|
145
149
|
if (column_name == rbql_star_marker) {
|
|
146
|
-
return {table_name: table_name, column_index: null, column_name: null, is_star: true};
|
|
150
|
+
return {table_name: table_name, column_index: null, column_name: null, is_star: true, is_alias: false};
|
|
147
151
|
}
|
|
148
|
-
return {table_name: null, column_index: null, column_name: column_name, is_star: false};
|
|
152
|
+
return {table_name: null, column_index: null, column_name: column_name, is_star: false, is_alias: false};
|
|
149
153
|
} else if (subscript_int_match != null) {
|
|
150
154
|
let table_name = subscript_int_match[1];
|
|
151
155
|
let column_index = parseInt(subscript_int_match[2]) - 1;
|
|
152
|
-
return {table_name: table_name, column_index: column_index, column_name: null, is_star: false};
|
|
156
|
+
return {table_name: table_name, column_index: column_index, column_name: null, is_star: false, is_alias: false};
|
|
153
157
|
} else if (subscript_str_match != null) {
|
|
154
158
|
let table_name = subscript_str_match[1];
|
|
155
159
|
let replaced_string_literal_id = subscript_str_match[2];
|
|
@@ -157,7 +161,7 @@ function column_info_from_text_span(text_span, string_literals) {
|
|
|
157
161
|
let quoted_column_name = string_literals[replaced_string_literal_id];
|
|
158
162
|
let unquoted_column_name = unquote_string(quoted_column_name);
|
|
159
163
|
if (unquoted_column_name !== null && unquoted_column_name !== undefined) {
|
|
160
|
-
return {table_name: null, column_index: null, column_name: unquoted_column_name, is_star: false};
|
|
164
|
+
return {table_name: null, column_index: null, column_name: unquoted_column_name, is_star: false, is_alias: false};
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
}
|
|
@@ -1328,9 +1332,11 @@ function translate_update_expression(update_expression, input_variables_map, str
|
|
|
1328
1332
|
|
|
1329
1333
|
|
|
1330
1334
|
function translate_select_expression(select_expression) {
|
|
1331
|
-
let
|
|
1332
|
-
let
|
|
1333
|
-
let
|
|
1335
|
+
let as_alias_replacement_regexp = / +(AS|as) +([a-zA-Z][a-zA-Z0-9_]*) *(?=$|,)/g;
|
|
1336
|
+
let expression_without_counting_stars = replace_star_count(select_expression);
|
|
1337
|
+
let expression_without_as_column_alias = expression_without_counting_stars.replace(as_alias_replacement_regexp, '');
|
|
1338
|
+
let translated = str_strip(replace_star_vars(expression_without_as_column_alias));
|
|
1339
|
+
let translated_for_header = str_strip(replace_star_vars_for_header_parsing(expression_without_counting_stars));
|
|
1334
1340
|
if (!translated.length)
|
|
1335
1341
|
throw new RbqlParsingError('"SELECT" expression is empty');
|
|
1336
1342
|
return [`[].concat([${translated}])`, translated_for_header];
|
|
@@ -1571,12 +1577,21 @@ function remove_redundant_table_name(query_text) {
|
|
|
1571
1577
|
|
|
1572
1578
|
|
|
1573
1579
|
function select_output_header(input_header, join_header, query_column_infos) {
|
|
1574
|
-
if (input_header === null
|
|
1580
|
+
if (input_header === null) {
|
|
1581
|
+
assert(join_header === null);
|
|
1582
|
+
}
|
|
1583
|
+
if (input_header === null) {
|
|
1584
|
+
for (let qci of query_column_infos) {
|
|
1585
|
+
if (qci !== null && qci.is_alias) {
|
|
1586
|
+
throw new RbqlParsingError(`Specifying column alias "AS ${qci.column_name}" is not allowed if input table has no header`);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1575
1589
|
return null;
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1590
|
+
}
|
|
1591
|
+
if (join_header === null) {
|
|
1592
|
+
// This means there is no JOIN table.
|
|
1579
1593
|
join_header = [];
|
|
1594
|
+
}
|
|
1580
1595
|
let output_header = [];
|
|
1581
1596
|
for (let qci of query_column_infos) {
|
|
1582
1597
|
// TODO refactor this and python version: extract this code into a function instead to always return something
|
|
@@ -1799,6 +1814,8 @@ async function shallow_parse_input_query(query_text, input_iterator, join_tables
|
|
|
1799
1814
|
query_context.aggregation_key_expression = '[' + combine_string_literals(rb_actions[GROUP_BY]['text'], string_literals) + ']';
|
|
1800
1815
|
}
|
|
1801
1816
|
|
|
1817
|
+
|
|
1818
|
+
let input_header = await input_iterator.get_header();
|
|
1802
1819
|
let join_variables_map = null;
|
|
1803
1820
|
let join_header = null;
|
|
1804
1821
|
if (rb_actions.hasOwnProperty(JOIN)) {
|
|
@@ -1813,6 +1830,12 @@ async function shallow_parse_input_query(query_text, input_iterator, join_tables
|
|
|
1813
1830
|
}
|
|
1814
1831
|
join_variables_map = await join_record_iterator.get_variables_map(query_text);
|
|
1815
1832
|
join_header = await join_record_iterator.get_header();
|
|
1833
|
+
if (input_header === null && join_header !== null) {
|
|
1834
|
+
throw new RbqlIOHandlingError('Inconsistent modes: Input table doesn\'t have a header while the Join table has a header');
|
|
1835
|
+
}
|
|
1836
|
+
if (input_header !== null && join_header === null) {
|
|
1837
|
+
throw new RbqlIOHandlingError('Inconsistent modes: Input table has a header while the Join table doesn\'t have a header');
|
|
1838
|
+
}
|
|
1816
1839
|
let [lhs_variables, rhs_indices] = resolve_join_variables(input_variables_map, join_variables_map, variable_pairs, string_literals);
|
|
1817
1840
|
let sql_join_type = {'JOIN': InnerJoiner, 'INNER JOIN': InnerJoiner, 'LEFT JOIN': LeftJoiner, 'LEFT OUTER JOIN': LeftJoiner, 'STRICT LEFT JOIN': StrictLeftJoiner}[rb_actions[JOIN]['join_subtype']];
|
|
1818
1841
|
query_context.lhs_join_var_expression = lhs_variables.length == 1 ? lhs_variables[0] : 'JSON.stringify([' + lhs_variables.join(',') + '])';
|
|
@@ -1830,7 +1853,6 @@ async function shallow_parse_input_query(query_text, input_iterator, join_tables
|
|
|
1830
1853
|
query_context.where_expression = combine_string_literals(where_expression, string_literals);
|
|
1831
1854
|
}
|
|
1832
1855
|
|
|
1833
|
-
let input_header = await input_iterator.get_header();
|
|
1834
1856
|
if (rb_actions.hasOwnProperty(UPDATE)) {
|
|
1835
1857
|
var update_expression = translate_update_expression(rb_actions[UPDATE]['text'], input_variables_map, string_literals, ' '.repeat(8));
|
|
1836
1858
|
query_context.update_expressions = combine_string_literals(update_expression, string_literals);
|
|
@@ -1847,9 +1869,9 @@ async function shallow_parse_input_query(query_text, input_iterator, join_tables
|
|
|
1847
1869
|
query_context.select_expression = select_expression;
|
|
1848
1870
|
query_context.writer.set_header(output_header);
|
|
1849
1871
|
} else {
|
|
1850
|
-
let [select_expression,
|
|
1872
|
+
let [select_expression, select_expression_for_header] = translate_select_expression(rb_actions[SELECT]['text']);
|
|
1851
1873
|
query_context.select_expression = combine_string_literals(select_expression, string_literals);
|
|
1852
|
-
let column_infos = adhoc_parse_select_expression_to_column_infos(
|
|
1874
|
+
let column_infos = adhoc_parse_select_expression_to_column_infos(select_expression_for_header, string_literals);
|
|
1853
1875
|
let output_header = select_output_header(input_header, join_header, column_infos);
|
|
1854
1876
|
query_context.writer.set_header(output_header);
|
|
1855
1877
|
}
|