Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class BigqueryQuery extends BaseQuery {
}

public convertTz(field) {
return `DATETIME(${this.timeStampCast(field)}, '${this.timezone}')`;
return `DATETIME(${field}, '${this.timezone}')`;
}

public timeStampCast(value) {
Expand Down Expand Up @@ -263,7 +263,7 @@ export class BigqueryQuery extends BaseQuery {
templates.expressions.binary = '{% if op == \'%\' %}MOD({{ left }}, {{ right }}){% else %}({{ left }} {{ op }} {{ right }}){% endif %}';
templates.expressions.interval = 'INTERVAL {{ interval }}';
templates.expressions.extract = 'EXTRACT({% if date_part == \'DOW\' %}DAYOFWEEK{% elif date_part == \'DOY\' %}DAYOFYEAR{% else %}{{ date_part }}{% endif %} FROM {{ expr }})';
templates.expressions.timestamp_literal = 'DATETIME(TIMESTAMP(\'{{ value }}\'))';
templates.expressions.timestamp_literal = 'TIMESTAMP(\'{{ value }}\')';
delete templates.expressions.ilike;
delete templates.expressions.like_escape;
templates.filters.like_pattern = 'CONCAT({% if start_wild %}\'%\'{% else %}\'\'{% endif %}, LOWER({{ value }}), {% if end_wild %}\'%\'{% else %}\'\'{% endif %})';
Expand Down
67 changes: 67 additions & 0 deletions packages/cubejs-schema-compiler/test/unit/base-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,73 @@ describe('SQL Generation', () => {
})
);

it('BigQuery timestamp literal regression test - should not double-wrap DATETIME(TIMESTAMP())', async () => {
await compilers.compiler.compile();

const query = new BigqueryQuery(compilers, {
measures: [
'cards.count'
],
timeDimensions: [{
dimension: 'cards.createdAt',
granularity: 'day',
dateRange: ['2025-06-03', '2025-06-10']
}],
filters: [],
timezone: 'UTC'
});

const queryAndParams = query.buildSqlAndParams();
const sql = queryAndParams[0];

// Should contain TIMESTAMP() but NOT DATETIME(TIMESTAMP())
expect(sql).toContain('TIMESTAMP(');
expect(sql).not.toContain('DATETIME(TIMESTAMP(');
// Should contain correct BigQuery timestamp comparison syntax with parameters
expect(sql).toContain('TIMESTAMP(?)');
// Should contain the properly formatted DATETIME function without double wrapping
expect(sql).toContain('DATETIME(`cards`.created_at, \'UTC\')');
});

it('BigQuery SQL pushdown timestamp literal test - should not double-wrap DATETIME(TIMESTAMP())', async () => {
await compilers.compiler.compile();

const query = new BigqueryQuery(compilers, {
measures: [
'cards.count'
],
timeDimensions: [{
dimension: 'cards.createdAt',
granularity: 'day',
dateRange: ['2025-06-03', '2025-06-10']
}],
filters: [{
member: 'cards.createdAt',
operator: 'inDateRange',
values: ['2025-06-03', '2025-06-10']
}],
timezone: 'UTC',
// Simulate SQL pushdown scenario with external query context
ungrouped: true
});

const queryAndParams = query.buildSqlAndParams();
const sql = queryAndParams[0];

console.log('Generated SQL pushdown:', sql);

// Should contain TIMESTAMP() but NOT DATETIME(TIMESTAMP()) in pushdown context
expect(sql).toContain('TIMESTAMP(');
expect(sql).not.toContain('DATETIME(TIMESTAMP(');
// Should contain correct BigQuery timestamp comparison syntax with parameters
expect(sql).toContain('TIMESTAMP(?)');
// Should contain the properly formatted DATETIME function without double wrapping
expect(sql).toContain('DATETIME(`cards`.created_at, \'UTC\')');
// Should handle date range filters correctly in pushdown context
expect(sql).toMatch(/DATETIME\(`cards`\.created_at, 'UTC'\) >= TIMESTAMP\(\?\)/);
expect(sql).toMatch(/DATETIME\(`cards`\.created_at, 'UTC'\) <= TIMESTAMP\(\?\)/);
});

it('Test time series with 6 digits timestamp precision - bigquery', async () => {
await compilers.compiler.compile();

Expand Down
52 changes: 52 additions & 0 deletions rust/cubesql/cubesql/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16970,4 +16970,56 @@ LIMIT {{ limit }}{% endif %}"#.to_string(),
displayable(physical_plan.as_ref()).indent()
);
}

#[tokio::test]
async fn test_bigquery_timestamp_literal_regression() {
if !Rewriter::sql_push_down_enabled() {
return;
}

Check warning on line 16978 in rust/cubesql/cubesql/src/compile/mod.rs

View workflow job for this annotation

GitHub Actions / Check fmt/clippy

Diff in /__w/cube/cube/rust/cubesql/cubesql/src/compile/mod.rs
init_testing_logger();

let bq_templates = vec![
("expressions/timestamp_literal".to_string(), "TIMESTAMP('{{ value }}')".to_string()),
];

let query_plan = convert_select_to_query_plan_customized(
r#"
SELECT COUNT(*) AS count_orders, LOWER(customer_gender) AS gender
FROM KibanaSampleDataEcommerce
WHERE order_date >= TIMESTAMP '2025-06-03 00:00:00'
AND order_date <= TIMESTAMP '2025-06-10 23:59:59'
AND LOWER(customer_gender) = 'test'
GROUP BY LOWER(customer_gender)
ORDER BY 1 DESC
"#
.to_string(),
DatabaseProtocol::PostgreSQL,
bq_templates,
)
.await;

Check warning on line 17000 in rust/cubesql/cubesql/src/compile/mod.rs

View workflow job for this annotation

GitHub Actions / Check fmt/clippy

Diff in /__w/cube/cube/rust/cubesql/cubesql/src/compile/mod.rs
let logical_plan = query_plan.as_logical_plan();
let sql = logical_plan.find_cube_scan_wrapped_sql().wrapped_sql.sql;

println!("Generated BigQuery SQL: {}", sql);

// Should contain TIMESTAMP() but NOT DATETIME(TIMESTAMP())
assert!(sql.contains("TIMESTAMP("));
assert!(!sql.contains("DATETIME(TIMESTAMP("));

Check warning on line 17009 in rust/cubesql/cubesql/src/compile/mod.rs

View workflow job for this annotation

GitHub Actions / Check fmt/clippy

Diff in /__w/cube/cube/rust/cubesql/cubesql/src/compile/mod.rs
// Should contain correct BigQuery timestamp literal syntax
assert!(sql.contains("TIMESTAMP('2025-06-03T00:00:00.000Z')"));
assert!(sql.contains("TIMESTAMP('2025-06-10T23:59:59.000Z')"));

Check warning on line 17013 in rust/cubesql/cubesql/src/compile/mod.rs

View workflow job for this annotation

GitHub Actions / Check fmt/clippy

Diff in /__w/cube/cube/rust/cubesql/cubesql/src/compile/mod.rs
// The key test: ensure we don't have the double-wrapping issue
// In the broken version, BigQuery would generate DATETIME(TIMESTAMP()) instead of just TIMESTAMP()
// Our fix ensures timestamp literals are not double-wrapped
assert!(!sql.contains("DATETIME(TIMESTAMP("));

let physical_plan = query_plan.as_physical_plan().await.unwrap();
println!(
"Physical plan: {}",
displayable(physical_plan.as_ref()).indent()
);
}
}
Loading