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.
Files changed (3) hide show
  1. package/README.md +11 -9
  2. package/package.json +1 -1
  3. 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
- `select a2, ARRAY_AGG(a1, v => v.sort().slice(0, 5)) group by a2`
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
- * `select top 100 a1, a2 * 10, a4.length where a1 == "Buy" order by parseInt(a2) desc`
350
- * `select * order by Math.random()` - random sort
351
- * `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
352
- * `update set a3 = 'NPC' where a3.indexOf('Non-playable character') != -1`
353
- * `select NR, *` - enumerate records, NR is 1-based
354
- * `select a1, b1, b2 inner join ./countries.txt on a2 == b1 order by a1, a3` - example of join query
355
- * `select MAX(a1), MIN(a1) where a.Name != 'John' group by a2, a3` - example of aggregate query
356
- * `select ...a1.split(':')` - Using JS "destructuring assignment" syntax to split one column into many. Do not try this with other SQL engines!
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.25.1",
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.25.1';
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 expression_without_stars = replace_star_count(select_expression);
1332
- let translated = str_strip(replace_star_vars(expression_without_stars));
1333
- let translated_for_header = str_strip(replace_star_vars_for_header_parsing(expression_without_stars));
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 && join_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
- if (input_header === null)
1577
- input_header = [];
1578
- if (join_header === null)
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, select_expression_for_ast] = translate_select_expression(rb_actions[SELECT]['text']);
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(select_expression_for_ast, string_literals);
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
  }