Skip to content

Commit a8e791c

Browse files
authored
fix(base-driver): Throw an error when the list of csv files from the bucket is empty (#9996)
* fix(base-driver): Throw an error when the list of csv files from the bucket is empty * Update snowflake driver, do not try downloading CSV files if there were no rows exported. + removed copy/paste
1 parent 9ceb2f9 commit a8e791c

File tree

2 files changed

+45
-45
lines changed

2 files changed

+45
-45
lines changed

packages/cubejs-base-driver/src/BaseDriver.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ export abstract class BaseDriver implements DriverInterface {
786786
if (!list.Contents) {
787787
return [];
788788
} else {
789-
const csvFile = await Promise.all(
789+
const csvFiles = await Promise.all(
790790
list.Contents.map(async (file) => {
791791
const command = new GetObjectCommand({
792792
Bucket: bucketName,
@@ -795,7 +795,7 @@ export abstract class BaseDriver implements DriverInterface {
795795
return getSignedUrl(storage, command, { expiresIn: 3600 });
796796
})
797797
);
798-
return csvFile;
798+
return csvFiles;
799799
}
800800
}
801801

@@ -818,16 +818,17 @@ export abstract class BaseDriver implements DriverInterface {
818818
const bucket = storage.bucket(bucketName);
819819
const [files] = await bucket.getFiles({ prefix: `${tableName}/` });
820820
if (files.length) {
821-
const csvFile = await Promise.all(files.map(async (file) => {
821+
const csvFiles = await Promise.all(files.map(async (file) => {
822822
const [url] = await file.getSignedUrl({
823823
action: 'read',
824824
expires: new Date(new Date().getTime() + 60 * 60 * 1000)
825825
});
826826
return url;
827827
}));
828-
return csvFile;
828+
829+
return csvFiles;
829830
} else {
830-
return [];
831+
throw new Error('No CSV files were obtained from the bucket');
831832
}
832833
}
833834

@@ -923,6 +924,10 @@ export abstract class BaseDriver implements DriverInterface {
923924
}
924925
}
925926

927+
if (csvFiles.length === 0) {
928+
throw new Error('No CSV files were obtained from the bucket');
929+
}
930+
926931
return csvFiles;
927932
}
928933
}

packages/cubejs-snowflake-driver/src/SnowflakeDriver.ts

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -575,10 +575,13 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
575575
}`);
576576
}
577577

578-
const types = options.query
578+
const { types, exportedCount } = options.query
579579
? await this.unloadWithSql(tableName, options)
580580
: await this.unloadWithTable(tableName, options);
581-
const csvFile = await this.getCsvFiles(tableName);
581+
// Snowflake doesn't produce csv files if no data is exported (no data rows)
582+
// so it's important not to call getCsvFiles(), because it checks for empty files list
583+
// and throws an error.
584+
const csvFile = exportedCount > 0 ? await this.getCsvFiles(tableName) : [];
582585

583586
return {
584587
exportBucketCsvEscapeSymbol: this.config.exportBucketCsvEscapeSymbol,
@@ -588,37 +591,42 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
588591
};
589592
}
590593

594+
private buildBucketUrl(tableName: string): string {
595+
const { bucketType } = <SnowflakeDriverExportBucket> this.config.exportBucket;
596+
597+
let bucketName: string;
598+
let exportPrefix: string;
599+
let path: string;
600+
601+
if (bucketType === 'azure') {
602+
({ bucketName, path } = this.parseBucketUrl(this.config.exportBucket!.bucketName));
603+
const pathArr = path.split('/');
604+
bucketName = `${bucketName}/${pathArr[0]}`;
605+
exportPrefix = pathArr.length > 1 ? `${pathArr.slice(1).join('/')}/${tableName}` : tableName;
606+
} else {
607+
({ bucketName, path } = this.parseBucketUrl(this.config.exportBucket!.bucketName));
608+
exportPrefix = path ? `${path}/${tableName}` : tableName;
609+
}
610+
611+
return `${bucketType}://${bucketName}/${exportPrefix}/`;
612+
}
613+
591614
/**
592615
* Unload data from a SQL query to an export bucket.
593616
*/
594617
private async unloadWithSql(
595618
tableName: string,
596619
options: UnloadOptions,
597-
): Promise<TableStructure> {
620+
): Promise<{ types: TableStructure, exportedCount: number }> {
598621
if (!options.query) {
599622
throw new Error('Unload query is missed.');
600623
} else {
601624
const types = await this.queryColumnTypes(options.query.sql, options.query.params);
602625
const connection = await this.getConnection();
603-
const { bucketType } =
604-
<SnowflakeDriverExportBucket> this.config.exportBucket;
605-
606-
let bucketName: string;
607-
let exportPrefix: string;
608-
let path: string;
609-
610-
if (bucketType === 'azure') {
611-
({ bucketName, path } = this.parseBucketUrl(this.config.exportBucket!.bucketName));
612-
const pathArr = path.split('/');
613-
bucketName = `${bucketName}/${pathArr[0]}`;
614-
exportPrefix = pathArr.length > 1 ? `${pathArr.slice(1).join('/')}/${tableName}` : tableName;
615-
} else {
616-
({ bucketName, path } = this.parseBucketUrl(this.config.exportBucket!.bucketName));
617-
exportPrefix = path ? `${path}/${tableName}` : tableName;
618-
}
626+
const bucketUrl = this.buildBucketUrl(tableName);
619627

620628
const unloadSql = `
621-
COPY INTO '${bucketType}://${bucketName}/${exportPrefix}/'
629+
COPY INTO '${bucketUrl}'
622630
FROM (${options.query.sql})
623631
${this.exportOptionsClause(options)}`;
624632
const result = await this.execute<UnloadResponse[]>(
@@ -630,7 +638,7 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
630638
if (!result) {
631639
throw new Error('Missing `COPY INTO` query result.');
632640
}
633-
return types;
641+
return { types, exportedCount: parseInt(result[0].rows_unloaded, 10) };
634642
}
635643
}
636644

@@ -661,28 +669,13 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
661669
private async unloadWithTable(
662670
tableName: string,
663671
options: UnloadOptions,
664-
): Promise<TableStructure> {
672+
): Promise<{ types: TableStructure, exportedCount: number }> {
665673
const types = await this.tableColumnTypes(tableName);
666674
const connection = await this.getConnection();
667-
const { bucketType } =
668-
<SnowflakeDriverExportBucket> this.config.exportBucket;
669-
670-
let bucketName: string;
671-
let exportPrefix: string;
672-
let path: string;
673-
674-
if (bucketType === 'azure') {
675-
({ bucketName, path } = this.parseBucketUrl(this.config.exportBucket!.bucketName));
676-
const pathArr = path.split('/');
677-
bucketName = `${bucketName}/${pathArr[0]}`;
678-
exportPrefix = pathArr.length > 1 ? `${pathArr.slice(1).join('/')}/${tableName}` : tableName;
679-
} else {
680-
({ bucketName, path } = this.parseBucketUrl(this.config.exportBucket!.bucketName));
681-
exportPrefix = path ? `${path}/${tableName}` : tableName;
682-
}
675+
const bucketUrl = this.buildBucketUrl(tableName);
683676

684677
const unloadSql = `
685-
COPY INTO '${bucketType}://${bucketName}/${exportPrefix}/'
678+
COPY INTO '${bucketUrl}'
686679
FROM ${tableName}
687680
${this.exportOptionsClause(options)}`;
688681
const result = await this.execute<UnloadResponse[]>(
@@ -691,10 +684,12 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
691684
[],
692685
false,
693686
);
687+
694688
if (!result) {
695689
throw new Error('Missing `COPY INTO` query result.');
696690
}
697-
return types;
691+
692+
return { types, exportedCount: parseInt(result[0].rows_unloaded, 10) };
698693
}
699694

700695
/**

0 commit comments

Comments
 (0)