Skip to content

Commit 8f71de2

Browse files
committed
fix(cubesql): Fix NULLS FIRST/LAST SQL push down for several dialects
1 parent 8fb14f0 commit 8f71de2

23 files changed

+6540
-6259
lines changed

packages/cubejs-pinot-driver/src/PinotQuery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ export class PinotQuery extends BaseQuery {
149149
'{% if limit %}\nLIMIT {{ limit }}{% endif %}';
150150
templates.expressions.extract = 'EXTRACT({{ date_part }} FROM {{ expr }})';
151151
templates.expressions.timestamp_literal = `fromDateTime('{{ value }}', ${DATE_TIME_FORMAT})`;
152+
// NOTE: this template contains a comma; two order expressions are being generated
153+
templates.expressions.sort = '{{ expr }} IS NULL {% if nulls_first %}DESC{% else %}ASC{% endif %}, {{ expr }} {% if asc %}ASC{% else %}DESC{% endif %}';
152154
templates.quotes.identifiers = '"';
153155
delete templates.types.time;
154156
delete templates.types.interval;

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3230,7 +3230,7 @@ export class BaseQuery {
32303230
case: 'CASE{% if expr %} {{ expr }}{% endif %}{% for when, then in when_then %} WHEN {{ when }} THEN {{ then }}{% endfor %}{% if else_expr %} ELSE {{ else_expr }}{% endif %} END',
32313231
is_null: '{{ expr }} IS {% if negate %}NOT {% endif %}NULL',
32323232
binary: '({{ left }} {{ op }} {{ right }})',
3233-
sort: '{{ expr }} {% if asc %}ASC{% else %}DESC{% endif %}{% if nulls_first %} NULLS FIRST{% endif %}',
3233+
sort: '{{ expr }} {% if asc %}ASC{% else %}DESC{% endif %} NULLS {% if nulls_first %}FIRST{% else %}LAST{% endif %}',
32343234
cast: 'CAST({{ expr }} AS {{ data_type }})',
32353235
window_function: '{{ fun_call }} OVER ({% if partition_by_concat %}PARTITION BY {{ partition_by_concat }}{% if order_by_concat or window_frame %} {% endif %}{% endif %}{% if order_by_concat %}ORDER BY {{ order_by_concat }}{% if window_frame %} {% endif %}{% endif %}{% if window_frame %}{{ window_frame }}{% endif %})',
32363236
window_frame_bounds: '{{ frame_type }} BETWEEN {{ frame_start }} AND {{ frame_end }}',

packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ export class MssqlQuery extends BaseQuery {
224224
templates.functions.LEAST = 'LEAST({{ args_concat }})';
225225
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
226226
delete templates.expressions.ilike;
227+
// NOTE: this template contains a comma; two order expressions are being generated
228+
templates.expressions.sort = '{{ expr }} IS NULL {% if nulls_first %}DESC{% else %}ASC{% endif %}, {{ expr }} {% if asc %}ASC{% else %}DESC{% endif %}';
227229
templates.types.string = 'VARCHAR';
228230
templates.types.boolean = 'BIT';
229231
templates.types.integer = 'INT';

packages/cubejs-schema-compiler/src/adapter/MysqlQuery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ export class MysqlQuery extends BaseQuery {
158158
const templates = super.sqlTemplates();
159159
templates.quotes.identifiers = '`';
160160
templates.quotes.escape = '\\`';
161+
// NOTE: this template contains a comma; two order expressions are being generated
162+
templates.expressions.sort = '{{ expr }} IS NULL {% if nulls_first %}DESC{% else %}ASC{% endif %}, {{ expr }} {% if asc %}ASC{% else %}DESC{% endif %}';
161163
delete templates.expressions.ilike;
162164
templates.types.string = 'VARCHAR';
163165
templates.types.boolean = 'TINYINT';

packages/cubejs-testing-drivers/fixtures/mssql.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
"SQL API: Nested Rollup with aliases",
151151
"SQL API: Nested Rollup over asterisk",
152152
"SQL API: Extended nested Rollup over asterisk",
153-
"SQL API: ungrouped pre-agg"
153+
"SQL API: ungrouped pre-agg",
154+
"SQL API: NULLS FIRST/LAST SQL push down"
154155
]
155156
}

packages/cubejs-testing-drivers/src/tests/testQueries.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,5 +2013,17 @@ from
20132013

20142014
expect(res.rows).toMatchSnapshot('metabase_count_cast_to_float32_from_push_down');
20152015
});
2016+
2017+
executePg('SQL API: NULLS FIRST/LAST SQL push down', async (connection) => {
2018+
const res = await connection.query(`
2019+
SELECT CASE WHEN "category" > 'G' THEN "category" ELSE NULL END AS "category"
2020+
FROM "Products"
2021+
WHERE LOWER("category") != 'invalid'
2022+
GROUP BY 1
2023+
ORDER BY 1 ASC NULLS FIRST
2024+
LIMIT 100
2025+
`)
2026+
expect(res.rows).toMatchSnapshot('nulls_first_last_sql_push_down')
2027+
});
20162028
});
20172029
}

packages/cubejs-testing-drivers/test/__snapshots__/athena-export-bucket-s3-full.test.ts.snap

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`Queries with the @cubejs-backend/athena-driver SQL API: NULLS FIRST/LAST SQL push down: nulls_first_last_sql_push_down 1`] = `
4+
Array [
5+
Object {
6+
"category": null,
7+
},
8+
Object {
9+
"category": "Office Supplies",
10+
},
11+
Object {
12+
"category": "Technology",
13+
},
14+
]
15+
`;
16+
317
exports[`Queries with the @cubejs-backend/athena-driver SQL API: metabase count cast to float32 from push down: metabase_count_cast_to_float32_from_push_down 1`] = `
418
Array [
519
Object {

0 commit comments

Comments
 (0)