Skip to content

Commit 813b3a6

Browse files
Merge contributions manually (#2086)
* Add these changes from #2072 and #2074 * Need to also change the way arrays are unnested. * mtoy's take on the changes ... passes ci-snowflake * add fix and test for array names ending in numbers * Add changes from #2088 * delete line for linter --------- Co-authored-by: Michael Toy <66150587+mtoy-googly-moogly@users.noreply.github.com>
1 parent 56d0ce9 commit 813b3a6

File tree

5 files changed

+73
-18
lines changed

5 files changed

+73
-18
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"test-bigquery": "MALLOY_DATABASE=bigquery jest --runInBand",
3737
"test-postgres": "MALLOY_DATABASE=postgres jest --runInBand",
3838
"test-duckdb": "JEST_SILENT_REPORTER_SHOW_PATHS=true MALLOY_DATABASE=duckdb jest --runInBand --reporters jest-silent-reporter",
39+
"ci-snowflake": "JEST_SILENT_REPORTER_SHOW_PATHS=true jest --config jest.snowflake.config.ts --reporters jest-silent-reporter --reporters summary",
3940
"test-silent": "JEST_SILENT_REPORTER_SHOW_PATHS=true jest --runInBand --reporters jest-silent-reporter --no-color",
4041
"test-deps": "npm run build && npx jest -t dependencies",
4142
"third-party-licenses": "ts-node scripts/third_party_licenses",

packages/malloy-db-snowflake/src/snowflake_connection.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,17 @@ describe('db:Snowflake', () => {
105105
expect(res.rows[0]['rand']).toBeGreaterThanOrEqual(0);
106106
expect(res.rows[0]['rand']).toBeLessThanOrEqual(1);
107107
});
108+
109+
it('variant parser is not confused by arrays with numbers in name', async () => {
110+
const x: malloy.SQLSourceDef = {
111+
type: 'sql_select',
112+
name: 'one_two_three',
113+
connection: conn.name,
114+
dialect: conn.dialectName,
115+
selectStr: 'SELECT [1,2,3] as one_23',
116+
fields: [],
117+
};
118+
const y = await conn.fetchSelectSchema(x);
119+
expect(y.fields[0].name).toEqual('ONE_23');
120+
});
108121
});

packages/malloy-db-snowflake/src/snowflake_connection.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ export interface SnowflakeConnectionOptions {
6363
scratchSpace?: namespace;
6464

6565
queryOptions?: RunSQLOptions;
66+
67+
// Timeout for the statement
68+
timeoutMs?: number;
6669
}
6770

6871
type PathChain =
@@ -207,6 +210,11 @@ class SnowArray extends SnowField {
207210
}
208211
}
209212

213+
/**
214+
* Default statement timeoutMs value, 10 Mins
215+
*/
216+
const TIMEOUT_MS = 1000 * 60 * 10;
217+
210218
export class SnowflakeConnection
211219
extends BaseConnection
212220
implements
@@ -221,6 +229,7 @@ export class SnowflakeConnection
221229
// the database & schema where we do temporary operations like creating a temp table
222230
private scratchSpace?: namespace;
223231
private queryOptions: RunSQLOptions;
232+
private timeoutMs: number;
224233

225234
constructor(
226235
public readonly name: string,
@@ -235,6 +244,7 @@ export class SnowflakeConnection
235244
this.executor = new SnowflakeExecutor(connOptions, options?.poolOptions);
236245
this.scratchSpace = options?.scratchSpace;
237246
this.queryOptions = options?.queryOptions ?? {};
247+
this.timeoutMs = options?.timeoutMs ?? TIMEOUT_MS;
238248
}
239249

240250
get dialectName(): string {
@@ -273,10 +283,10 @@ export class SnowflakeConnection
273283

274284
public async runSQL(
275285
sql: string,
276-
options?: RunSQLOptions
286+
options: RunSQLOptions = {}
277287
): Promise<MalloyQueryData> {
278288
const rowLimit = options?.rowLimit ?? this.queryOptions?.rowLimit;
279-
let rows = await this.executor.batch(sql);
289+
let rows = await this.executor.batch(sql, options, this.timeoutMs);
280290
if (rowLimit !== undefined && rows.length > rowLimit) {
281291
rows = rows.slice(0, rowLimit);
282292
}
@@ -329,12 +339,24 @@ export class SnowflakeConnection
329339
}
330340
// For these things, we need to sample the data to know the schema
331341
if (variants.length > 0) {
342+
// * remove null values
343+
// * remove fields for which we have multiple types
344+
// ( requires folding decimal to integer )
332345
const sampleQuery = `
333-
SELECT regexp_replace(PATH, '\\[[0-9]+\\]', '[*]') as PATH, lower(TYPEOF(value)) as type
334-
FROM (select object_construct(*) o from ${tablePath} limit 100)
335-
,table(flatten(input => o, recursive => true)) as meta
336-
GROUP BY 1,2
337-
ORDER BY PATH;
346+
select path, min(type) as type
347+
from (
348+
select
349+
regexp_replace(path, '\\\\[[0-9]+\\\\]', '[*]') as path,
350+
case when typeof(value) = 'INTEGER' then 'decimal' else lower(typeof(value)) end as type
351+
from
352+
(select object_construct(*) o from ${tablePath} limit 100)
353+
,table(flatten(input => o, recursive => true)) as meta
354+
group by 1,2
355+
)
356+
where type != 'null_value'
357+
group BY 1
358+
having count(*) <=1
359+
order by path;
338360
`;
339361
const fieldPathRows = await this.executor.batch(sampleQuery);
340362

packages/malloy-db-snowflake/src/snowflake_executor.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,30 @@ export class SnowflakeExecutor {
149149
});
150150
}
151151

152-
public async _execute(sqlText: string, conn: Connection): Promise<QueryData> {
153-
return new Promise((resolve, reject) => {
154-
const _statment = conn.execute({
152+
public async _execute(
153+
sqlText: string,
154+
conn: Connection,
155+
options?: RunSQLOptions,
156+
timeoutMs?: number
157+
): Promise<QueryData> {
158+
let _statement: RowStatement | undefined;
159+
const cancel = () => {
160+
_statement?.cancel();
161+
};
162+
const timeoutId = timeoutMs ? setTimeout(cancel, timeoutMs) : undefined;
163+
options?.abortSignal?.addEventListener('abort', cancel);
164+
return await new Promise((resolve, reject) => {
165+
_statement = conn.execute({
155166
sqlText,
156167
complete: (
157168
err: SnowflakeError | undefined,
158169
_stmt: RowStatement,
159170
rows?: QueryData
160171
) => {
172+
options?.abortSignal?.removeEventListener('abort', cancel);
173+
if (timeoutId) {
174+
clearTimeout(timeoutId);
175+
}
161176
if (err) {
162177
reject(err);
163178
} else if (rows) {
@@ -186,10 +201,14 @@ export class SnowflakeExecutor {
186201
);
187202
}
188203

189-
public async batch(sqlText: string): Promise<QueryData> {
204+
public async batch(
205+
sqlText: string,
206+
options?: RunSQLOptions,
207+
timeoutMs?: number
208+
): Promise<QueryData> {
190209
return await this.pool_.use(async (conn: Connection) => {
191210
await this._setSessionParams(conn);
192-
return await this._execute(sqlText, conn);
211+
return await this._execute(sqlText, conn, options, timeoutMs);
193212
});
194213
}
195214

packages/malloy/src/dialect/snowflake/snowflake.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export class SnowflakeDialect extends Dialect {
205205
): string {
206206
const as = this.sqlMaybeQuoteIdentifier(alias);
207207
if (isArray) {
208-
return `,LATERAL FLATTEN(INPUT => ${source}) AS ${alias}_1, LATERAL (SELECT ${alias}_1.INDEX, object_construct('value', ${alias}_1.value) as value ) as ${as}`;
208+
return `LEFT JOIN lateral flatten(input => ${source}) as ${as}`;
209209
} else {
210210
// have to have a non empty row or it treats it like an inner join :barf-emoji:
211211
return `LEFT JOIN LATERAL FLATTEN(INPUT => ifnull(${source},[1])) AS ${as}`;
@@ -263,11 +263,11 @@ export class SnowflakeDialect extends Dialect {
263263
const sqlName = this.sqlMaybeQuoteIdentifier(childName);
264264
if (childName === '__row_id') {
265265
return `"${parentAlias}".INDEX::varchar`;
266-
} else if (
267-
parentType === 'array[scalar]' ||
268-
parentType === 'array[record]'
269-
) {
270-
const arrayRef = `"${parentAlias}".value:${sqlName}`;
266+
} else if (parentType.startsWith('array')) {
267+
let arrayRef = `"${parentAlias}".value`;
268+
if (parentType === 'array[record]') {
269+
arrayRef += `:${sqlName}`;
270+
}
271271
switch (childType) {
272272
case 'record':
273273
case 'array':

0 commit comments

Comments
 (0)