Skip to content

Commit

Permalink
feat(cubesql): support GREATEST/LEAST SQL functions (#8325)
Browse files Browse the repository at this point in the history
* feat(cubesql): allow float scalars in LEAST/GREATEST functions
* feat(cubesql): add support for the greatest function with 2 parameters
* feat(cubesql): support GREATEST sql function with arbitrary number of arguments
* feat(cubesql): support LEAST sql function with arbitrary number of arguments
* feat(cubesql): propogate GREATEST/LEAST support to Postgres, MS SQL, DuckDB and Databricks drivers
  • Loading branch information
KSDaemon authored Jun 13, 2024
1 parent a4ca555 commit c13a28e
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 85 deletions.
2 changes: 2 additions & 0 deletions packages/cubejs-databricks-jdbc-driver/src/DatabricksQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export class DatabricksQuery extends BaseQuery {
templates.functions.LTRIM = 'LTRIM({{ args|reverse|join(", ") }})';
templates.functions.RTRIM = 'RTRIM({{ args|reverse|join(", ") }})';
templates.functions.DATEDIFF = 'DATEDIFF({{ date_part }}, DATE_TRUNC(\'{{ date_part }}\', {{ args[1] }}), DATE_TRUNC(\'{{ date_part }}\', {{ args[2] }}))';
templates.functions.LEAST = 'LEAST({{ args_concat }})';
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
templates.expressions.timestamp_literal = 'from_utc_timestamp(\'{{ value }}\', \'UTC\')';
templates.quotes.identifiers = '`';
templates.quotes.escape = '``';
Expand Down
2 changes: 2 additions & 0 deletions packages/cubejs-duckdb-driver/src/DuckDBQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export class DuckDBQuery extends BaseQuery {
public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.DATETRUNC = 'DATE_TRUNC({{ args_concat }})';
templates.functions.LEAST = 'LEAST({{ args_concat }})';
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
return templates;
}
}
12 changes: 10 additions & 2 deletions packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -2954,7 +2954,16 @@ export class BaseQuery {
FLOOR: 'FLOOR({{ args_concat }})',
CEIL: 'CEIL({{ args_concat }})',
TRUNC: 'TRUNC({{ args_concat }})',
LEAST: 'LEAST({{ args_concat }})',

// There is a difference in behaviour of these function processing in different DBs and DWHs.
// The SQL standard requires greatest and least to return null in case one argument is null.
// However, many DBMS ignore NULL values (mostly because greatest and least were often supported
// decades before they were added to the SQL standard in 2023).
// Cube follows the Postgres implementation (as we mimic the Postgres protocol) and ignores NULL values.
// So these functions are enabled on a driver-specific basis for databases that ignores NULLs.
// LEAST: 'LEAST({{ args_concat }})',
// GREATEST: 'GREATEST({{ args_concat }})',

LOWER: 'LOWER({{ args_concat }})',
UPPER: 'UPPER({{ args_concat }})',
LEFT: 'LEFT({{ args_concat }})',
Expand All @@ -2976,7 +2985,6 @@ export class BaseQuery {
REPEAT: 'REPEAT({{ args_concat }})',
NULLIF: 'NULLIF({{ args_concat }})',
ROUND: 'ROUND({{ args_concat }})',
GREATEST: 'GREATEST({{ args_concat }})',

STDDEV: 'STDDEV_SAMP({{ args_concat }})',
SUBSTR: 'SUBSTRING({{ args_concat }})',
Expand Down
13 changes: 10 additions & 3 deletions packages/cubejs-schema-compiler/src/adapter/MssqlQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,15 @@ export class MssqlQuery extends BaseQuery {
const timeDimensionsColumns = this.timeDimensions.map(
(t) => `${t.dateSeriesAliasName()}.${this.escapeColumnName('date_from')}`
);

// Group by regular dimensions
const dimensionColumns = R.flatten(
this.dimensions.map(s => s.selectColumns() && s.dimensionSql() && s.aliasName())
).filter(s => !!s);

// Combine time dimensions and regular dimensions for GROUP BY clause
const allGroupByColumns = timeDimensionsColumns.concat(dimensionColumns);

const forSelect = this.overTimeSeriesForSelect(cumulativeMeasures);
return (
`SELECT ${forSelect} FROM ${dateSeriesSql}` +
Expand Down Expand Up @@ -180,4 +180,11 @@ export class MssqlQuery extends BaseQuery {
const amountInterval = interval.split(' ', 2);
return `DATEADD(${amountInterval[1]}, ${amountInterval[0]}, ${date})`;
}

public sqlTemplates() {
const templates = super.sqlTemplates();
templates.functions.LEAST = 'LEAST({{ args_concat }})';
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
return templates;
}
}
2 changes: 2 additions & 0 deletions packages/cubejs-schema-compiler/src/adapter/PostgresQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export class PostgresQuery extends BaseQuery {
templates.functions.CONCAT = 'CONCAT({% for arg in args %}CAST({{arg}} AS TEXT){% if not loop.last %},{% endif %}{% endfor %})';
templates.functions.DATEPART = 'DATE_PART({{ args_concat }})';
templates.functions.CURRENTDATE = 'CURRENT_DATE';
templates.functions.LEAST = 'LEAST({{ args_concat }})';
templates.functions.GREATEST = 'GREATEST({{ args_concat }})';
templates.functions.NOW = 'NOW({{ args_concat }})';
// DATEADD is being rewritten to DATE_ADD
// templates.functions.DATEADD = '({{ args[2] }} + \'{{ interval }} {{ date_part }}\'::interval)';
Expand Down
3 changes: 3 additions & 0 deletions rust/cubesql/cubesql/src/compile/engine/df/coerce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ pub fn numerical_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option<Da
(_, DataType::Null) => Some(lhs_type.clone()),
(DataType::Null, _) => Some(rhs_type.clone()),
//
(_, DataType::Float64) => Some(DataType::Float64),
(DataType::Float64, _) => Some(DataType::Float64),
//
(_, DataType::UInt64) => Some(DataType::UInt64),
(DataType::UInt64, _) => Some(DataType::UInt64),
//
Expand Down
Loading

0 comments on commit c13a28e

Please sign in to comment.