Skip to content

Commit 9aa2401

Browse files
committed
test(testing-drivers): move Trino LIKE escaping coverage into the shared suite
The Trino LIKE-escaping regression was covered by a standalone birdbox smoke test in cubejs-testing. That put the only end-to-end proof of the fix in a Trino-specific file, so no other engine exercised it and the driver suite - where every backend runs the same cases - stayed blind to the bug. Onboard Trino as a driver in cubejs-testing-drivers and delete the smoke test: - fixtures/trino.json: trinodb/trino:476 against the in-memory catalog, the Presto-family cast rules, and the same pre-aggregation set the other fixtures declare. The container raises query.max-stage-count because the fixture tables are built from ~1000-branch UNION ALL chains, which blow past Trino's 150-stage default. - runEnvironment: wait on the image's HEALTHCHECK for Trino. The compose environment overwrites each strategy's own timeout with the global one, so the global value is what has to be raised. - Both planners are wired into drivers-tests.yml. Skip lists are split by planner and annotated with the Trino limitation behind each entry: no WEEK interval unit, PrestodbQuery rejecting multi-part intervals, ordinal GROUP BY in SQL API rollup push-down, and unordered results that cannot be snapshotted. Locally: 128 passed / 11 skipped with Tesseract, 109 passed / 30 skipped on the legacy planner, no failures on either. The shared suite also picks up startsWith and endsWith percent-sign cases plus a pre-aggregated notContains, so the operator coverage the smoke test had is kept, now running for every driver rather than Trino alone. Verified green on Postgres under both planners. buildPreaggs now rejects with the response body when the jobs endpoint answers with an error instead of a token array. That path previously died on an opaque TypeError inside a polling interval and failed 120s later complaining about `tokens`, which hid the pre-aggregation the fixture was actually missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDVoT5YNCrewPZ87jyzmkF
1 parent 627f089 commit 9aa2401

12 files changed

Lines changed: 16769 additions & 239 deletions

File tree

.github/actions/smoke.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ echo "::endgroup::"
4444

4545
echo "::group::Trino"
4646
yarn lerna run --concurrency 1 --stream --no-prefix smoke:trino
47-
yarn lerna run --concurrency 1 --stream --no-prefix smoke:trino-like-escaping
4847
echo "::endgroup::"
4948

5049
echo "::group::MS SQL"

.github/workflows/drivers-tests.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ on:
2727
- 'packages/cubejs-postgres-driver/**'
2828
- 'packages/cubejs-questdb-driver/**'
2929
- 'packages/cubejs-redshift-driver/**'
30+
- 'packages/cubejs-prestodb-driver/**'
3031
- 'packages/cubejs-snowflake-driver/**'
32+
- 'packages/cubejs-trino-driver/**'
3133
- 'packages/cubejs-vertica-driver/**'
3234

3335
- 'packages/cubejs-backend-native/**'
@@ -57,7 +59,9 @@ on:
5759
- 'packages/cubejs-postgres-driver/**'
5860
- 'packages/cubejs-questdb-driver/**'
5961
- 'packages/cubejs-redshift-driver/**'
62+
- 'packages/cubejs-prestodb-driver/**'
6063
- 'packages/cubejs-snowflake-driver/**'
64+
- 'packages/cubejs-trino-driver/**'
6165
- 'packages/cubejs-vertica-driver/**'
6266

6367
- 'packages/cubejs-backend-native/**'
@@ -289,6 +293,7 @@ jobs:
289293
- snowflake-export-bucket-azure-via-storage-integration
290294
- snowflake-export-bucket-gcs
291295
- snowflake-export-bucket-gcs-prefix
296+
- trino
292297
use_tesseract_sql_planner: [ true ]
293298
include:
294299
- database: postgres
@@ -315,6 +320,8 @@ jobs:
315320
use_tesseract_sql_planner: false
316321
- database: questdb
317322
use_tesseract_sql_planner: false
323+
- database: trino
324+
use_tesseract_sql_planner: false
318325
fail-fast: false
319326

320327
steps:
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
{
2+
"cube": {
3+
"environment": {
4+
"CUBEJS_API_SECRET": "mysupersecret",
5+
"CUBEJS_CACHE_AND_QUEUE_DRIVER": "cubestore",
6+
"CUBEJS_CUBESTORE_HOST": "store",
7+
"CUBEJS_DB_TYPE": "trino",
8+
"CUBEJS_DB_HOST": "data",
9+
"CUBEJS_DB_PORT": "8080",
10+
"CUBEJS_DB_PRESTO_CATALOG": "memory",
11+
"CUBEJS_DB_NAME": "default",
12+
"CUBEJS_DB_USER": "test",
13+
"CUBEJS_PG_SQL_PORT": "5656",
14+
"CUBEJS_SQL_USER": "admin",
15+
"CUBEJS_SQL_PASSWORD": "admin_password",
16+
"CUBESQL_SQL_PUSH_DOWN": "true",
17+
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
18+
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
19+
},
20+
"depends_on": [
21+
"data"
22+
],
23+
"links": [
24+
"data"
25+
],
26+
"ports": [
27+
"4000",
28+
"5656"
29+
]
30+
},
31+
"data": {
32+
"image": "trinodb/trino:476",
33+
"ports": [
34+
"8080"
35+
],
36+
"entrypoint": [
37+
"/bin/bash",
38+
"-c",
39+
"echo 'query.max-stage-count=1000' >> /etc/trino/config.properties && exec /usr/lib/trino/bin/run-trino"
40+
]
41+
},
42+
"cast": {
43+
"SELECT_PREFIX": "",
44+
"SELECT_SUFFIX": "",
45+
"DATE_PREFIX": "date_parse(",
46+
"DATE_SUFFIX": ", '%Y-%m-%d')",
47+
"CREATE_TBL_PREFIX": "CREATE TABLE ",
48+
"CREATE_TBL_SUFFIX": " AS ",
49+
"CREATE_SUB_PREFIX": "(",
50+
"CREATE_SUB_SUFFIX": ")"
51+
},
52+
"tables": {
53+
"products": "products",
54+
"customers": "customers",
55+
"ecommerce": "ecommerce",
56+
"bigecommerce": "bigecommerce",
57+
"retailcalendar": "retailcalendar"
58+
},
59+
"preAggregations": {
60+
"Products": [],
61+
"Customers": [
62+
{
63+
"name": "RA",
64+
"measures": [
65+
"CUBE.count",
66+
"CUBE.runningTotal"
67+
]
68+
}
69+
],
70+
"ECommerce": [
71+
{
72+
"name": "SA",
73+
"dimensions": [
74+
"CUBE.productName"
75+
],
76+
"measures": [
77+
"CUBE.totalQuantity",
78+
"CUBE.avgDiscount",
79+
"CUBE.totalSales",
80+
"CUBE.totalProfit"
81+
]
82+
},
83+
{
84+
"name": "TA",
85+
"time_dimension": "CUBE.orderDate",
86+
"granularity": "month",
87+
"partition_granularity": "month",
88+
"dimensions": [
89+
"CUBE.productName"
90+
],
91+
"measures": [
92+
"CUBE.totalQuantity",
93+
"CUBE.avgDiscount",
94+
"CUBE.totalSales",
95+
"CUBE.totalProfit"
96+
]
97+
},
98+
{
99+
"name": "MultiTimeDimForCount",
100+
"time_dimensions": [
101+
{
102+
"dimension": "CUBE.completedDate",
103+
"granularity": "day"
104+
},
105+
{
106+
"dimension": "CUBE.orderDate",
107+
"granularity": "day"
108+
}
109+
],
110+
"partition_granularity": "month",
111+
"dimensions": [
112+
"CUBE.productName"
113+
],
114+
"measures": [
115+
"CUBE.count"
116+
]
117+
},
118+
{
119+
"name": "CountByProduct",
120+
"time_dimension": "CUBE.orderDate",
121+
"granularity": "month",
122+
"partition_granularity": "month",
123+
"dimensions": [
124+
"CUBE.productName"
125+
],
126+
"measures": [
127+
"CUBE.countApproxByCustomer"
128+
]
129+
},
130+
{
131+
"name": "TBuildOnlyHalfYear",
132+
"time_dimension": "CUBE.orderDate",
133+
"granularity": "build_only_half_year",
134+
"partition_granularity": "year",
135+
"dimensions": [
136+
"CUBE.productName"
137+
],
138+
"measures": [
139+
"CUBE.totalQuantity"
140+
]
141+
}
142+
],
143+
"BigECommerce": [
144+
{
145+
"name": "TA",
146+
"time_dimension": "CUBE.orderDate",
147+
"granularity": "month",
148+
"partition_granularity": "year",
149+
"dimensions": [
150+
"CUBE.productName",
151+
"CUBE.id"
152+
],
153+
"measures": [
154+
"CUBE.totalQuantity",
155+
"CUBE.avgDiscount",
156+
"CUBE.totalSales",
157+
"CUBE.totalProfit"
158+
]
159+
},
160+
{
161+
"name": "MultiTimeDimForCount",
162+
"time_dimensions": [
163+
{
164+
"dimension": "CUBE.completedDate",
165+
"granularity": "day"
166+
},
167+
{
168+
"dimension": "CUBE.orderDate",
169+
"granularity": "day"
170+
}
171+
],
172+
"partition_granularity": "month",
173+
"dimensions": [],
174+
"measures": [
175+
"CUBE.count"
176+
]
177+
},
178+
{
179+
"name": "CountByProduct",
180+
"time_dimension": "CUBE.orderDate",
181+
"granularity": "month",
182+
"partition_granularity": "month",
183+
"dimensions": [
184+
"CUBE.productName"
185+
],
186+
"measures": [
187+
"CUBE.countApproxByCustomer"
188+
]
189+
},
190+
{
191+
"name": "CategoryFlat",
192+
"dimensions": [
193+
"CUBE.productName",
194+
"CUBE.id",
195+
"CUBE.category"
196+
],
197+
"measures": [
198+
"CUBE.totalProfit"
199+
]
200+
}
201+
]
202+
},
203+
"skip": [
204+
"---------------------------------------",
205+
"SKIPPED FOR ALL ",
206+
"---------------------------------------",
207+
"Trino returns rows in a different order between runs when the query does not",
208+
"order them itself, so these two cannot be pinned to a snapshot.",
209+
"---------------------------------------",
210+
"querying Products: dimensions -- doesn't work wo ordering",
211+
"SQL API: Nested Rollup over asterisk",
212+
"---------------------------------------",
213+
"Requires Tesseract. ",
214+
"---------------------------------------",
215+
"querying BigECommerce: rolling window by 2 day without date range",
216+
"querying BigECommerce: rolling window by 2 month without date range",
217+
"querying BigECommerce: rolling window YTD without date range",
218+
"querying custom granularities ECommerce: count by two_mo_by_feb + no dimension + rollingCountByLeading without date range",
219+
"querying SwitchSourceTest: simple cross join",
220+
"querying SwitchSourceTest: full cross join",
221+
"querying SwitchSourceTest: filter by switch dimensions",
222+
"---------------------------------------",
223+
"Trino rejects the ordinal GROUP BY references (\"GROUP BY expression must be a",
224+
"column reference: 1\") that the SQL API pushes down for a rollup.",
225+
"---------------------------------------",
226+
"SQL API: Simple Rollup",
227+
"SQL API: Complex Rollup",
228+
"SQL API: Rollup with aliases",
229+
"SQL API: Rollup over exprs",
230+
"SQL API: Nested Rollup",
231+
"SQL API: Nested Rollup with aliases",
232+
"SQL API: Extended nested Rollup over asterisk",
233+
"---------------------------------------",
234+
"Requires Tesseract: the legacy planner produces different numbers here, so",
235+
"the committed snapshots are the Tesseract ones (same split as the other",
236+
"fixtures). ",
237+
"---------------------------------------",
238+
"querying BigECommerce: multi-stage group by time dimension",
239+
"querying BigECommerce: SeveralMultiStageMeasures",
240+
"querying BigECommerce: two multi-stage branches sharing one pre-aggregation",
241+
"querying BigECommerce: base measure plus multi-stage over non-partitioned pre-aggregation",
242+
"querying BigECommerce with Retail Calendar: totalCountRetailYearAgo",
243+
"querying BigECommerce with Retail Calendar: totalCountRetailMonthAgo",
244+
"Tesseract: querying BigECommerce with Retail Calendar: totalCountRetailMonthAgo",
245+
"SQL API: Timeshift measure from cube",
246+
"Tesseract: SQL API: Timeshift measure from cube",
247+
"---------------------------------------",
248+
"Trino has no WEEK interval unit and PrestodbQuery rejects multi-part",
249+
"intervals; both planners hit these, see the tesseractSkip notes below.",
250+
"---------------------------------------",
251+
"querying BigECommerce: rolling window by 2 week",
252+
"querying BigECommerce with Retail Calendar: totalCountRetailWeekAgo",
253+
"Tesseract: querying BigECommerce with Retail Calendar: totalCountRetailWeekAgo",
254+
"querying custom granularities ECommerce: count by three_months_by_march + no dimension",
255+
"querying custom granularities ECommerce: count by three_months_by_march + dimension"
256+
],
257+
"tesseractSkip": [
258+
"---------------------------------------",
259+
"SKIPPED FOR ALL ",
260+
"---------------------------------------",
261+
"Trino returns rows in a different order between runs when the query does not",
262+
"order them itself, so these two cannot be pinned to a snapshot.",
263+
"---------------------------------------",
264+
"querying Products: dimensions -- doesn't work wo ordering",
265+
"SQL API: Nested Rollup over asterisk",
266+
"---------------------------------------",
267+
"Trino has no WEEK interval unit: `INTERVAL '2' week` is a parse error",
268+
"(\"mismatched input 'week'\"), so every rolling window measured in weeks fails.",
269+
"---------------------------------------",
270+
"querying BigECommerce: rolling window by 2 week",
271+
"querying BigECommerce: rolling window YTD (month + week)",
272+
"querying BigECommerce: rolling window YTD (month + week + no gran)",
273+
"querying BigECommerce with Retail Calendar: totalCountRetailWeekAgo",
274+
"Tesseract: querying BigECommerce with Retail Calendar: totalCountRetailWeekAgo",
275+
"---------------------------------------",
276+
"PrestodbQuery rejects intervals with more than one date part outright",
277+
"(\"Athena/Presto supports only simple intervals with one date part\"), which is",
278+
"what the three_months_by_march custom granularity offset needs.",
279+
"---------------------------------------",
280+
"querying custom granularities ECommerce: count by three_months_by_march + no dimension",
281+
"querying custom granularities ECommerce: count by three_months_by_march + dimension",
282+
"---------------------------------------",
283+
"Trino rejects the ordinal GROUP BY references (\"GROUP BY expression must be a",
284+
"column reference: 1\") that the SQL API pushes down for a rollup.",
285+
"---------------------------------------",
286+
"SQL API: Nested Rollup",
287+
"SQL API: Nested Rollup with aliases"
288+
]
289+
}

packages/cubejs-testing-drivers/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"oracle-driver": "yarn test-driver -i dist/test/oracle-driver.test.js",
5353
"oracle-core": "yarn test-driver -i dist/test/oracle-core.test.js",
5454
"oracle-full": "yarn test-driver -i dist/test/oracle-full.test.js",
55+
"trino-full": "yarn test-driver -i dist/test/trino-full.test.js",
5556
"postgres-driver": "yarn test-driver -i dist/test/postgres-driver.test.js",
5657
"postgres-core": "yarn test-driver -i dist/test/postgres-core.test.js",
5758
"postgres-full": "yarn test-driver -i dist/test/postgres-full.test.js",
@@ -106,6 +107,7 @@
106107
"@cubejs-backend/shared": "1.7.19",
107108
"@cubejs-backend/snowflake-driver": "1.7.19",
108109
"@cubejs-backend/testing-shared": "1.7.19",
110+
"@cubejs-backend/trino-driver": "1.7.19",
109111
"@cubejs-client/core": "1.7.19",
110112
"@cubejs-client/ws-transport": "1.7.19",
111113
"@jest/globals": "^29",

packages/cubejs-testing-drivers/src/helpers/buildPreaggs.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,18 @@ export async function buildPreaggs(
5959
token,
6060
{ action: 'post', selector },
6161
).then((post) => {
62-
readData(post).then((_jobs) => {
63-
const jobs = <string[]>JSON.parse(_jobs.toString());
62+
readData(post).then((_body) => {
63+
const body = _body.toString();
64+
const jobs = <string[]>JSON.parse(body);
65+
// A rejected selector (e.g. naming a pre-aggregation the fixture does not
66+
// declare) answers with an error object rather than a token array. Without
67+
// this check that object is forwarded as `tokens` on the next request and
68+
// the build dies 120s later on an unrelated complaint about `tokens`, hiding
69+
// the selector that actually failed.
70+
if (!Array.isArray(jobs)) {
71+
reject(`Cube pre-aggregations build failed: ${body}`);
72+
return;
73+
}
6474
if (jobs.length === 0) {
6575
resolve(true);
6676
} else {
@@ -72,7 +82,17 @@ export async function buildPreaggs(
7282
token,
7383
{ action: 'get', resType: 'object', tokens: jobs },
7484
);
75-
const statuses = JSON.parse((await readData(get)).toString());
85+
const statusBody = (await readData(get)).toString();
86+
const statuses = JSON.parse(statusBody);
87+
// An error response ({"error": "..."}) has no per-token job objects, so
88+
// reading `status` off it would throw an opaque TypeError from inside this
89+
// interval and leave the build to fail by timeout with the actual cause
90+
// never surfacing. Reject with the body instead.
91+
if (statuses.error) {
92+
clearInterval(interval);
93+
reject(`Cube pre-aggregations build failed: ${statusBody}`);
94+
return;
95+
}
7696
Object.keys(statuses).forEach((t: string) => {
7797
const { status } = statuses[t];
7898
if (status.indexOf('failure') >= 0) {

0 commit comments

Comments
 (0)