@@ -555,10 +555,13 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
555555 } `) ;
556556 }
557557
558- const types = options . query
558+ const { types, exportedCount } = options . query
559559 ? await this . unloadWithSql ( tableName , options )
560560 : await this . unloadWithTable ( tableName , options ) ;
561- const csvFile = await this . getCsvFiles ( tableName ) ;
561+ // Snowflake doesn't produce csv files if no data is exported (no data rows)
562+ // so it's important not to call getCsvFiles(), because it checks for empty files list
563+ // and throws an error.
564+ const csvFile = exportedCount > 0 ? await this . getCsvFiles ( tableName ) : [ ] ;
562565
563566 return {
564567 exportBucketCsvEscapeSymbol : this . config . exportBucketCsvEscapeSymbol ,
@@ -568,37 +571,42 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
568571 } ;
569572 }
570573
574+ private buildBucketUrl ( tableName : string ) : string {
575+ const { bucketType } = < SnowflakeDriverExportBucket > this . config . exportBucket ;
576+
577+ let bucketName : string ;
578+ let exportPrefix : string ;
579+ let path : string ;
580+
581+ if ( bucketType === 'azure' ) {
582+ ( { bucketName, path } = this . parseBucketUrl ( this . config . exportBucket ! . bucketName ) ) ;
583+ const pathArr = path . split ( '/' ) ;
584+ bucketName = `${ bucketName } /${ pathArr [ 0 ] } ` ;
585+ exportPrefix = pathArr . length > 1 ? `${ pathArr . slice ( 1 ) . join ( '/' ) } /${ tableName } ` : tableName ;
586+ } else {
587+ ( { bucketName, path } = this . parseBucketUrl ( this . config . exportBucket ! . bucketName ) ) ;
588+ exportPrefix = path ? `${ path } /${ tableName } ` : tableName ;
589+ }
590+
591+ return `${ bucketType } ://${ bucketName } /${ exportPrefix } /` ;
592+ }
593+
571594 /**
572595 * Unload data from a SQL query to an export bucket.
573596 */
574597 private async unloadWithSql (
575598 tableName : string ,
576599 options : UnloadOptions ,
577- ) : Promise < TableStructure > {
600+ ) : Promise < { types : TableStructure , exportedCount : number } > {
578601 if ( ! options . query ) {
579602 throw new Error ( 'Unload query is missed.' ) ;
580603 } else {
581604 const types = await this . queryColumnTypes ( options . query . sql , options . query . params ) ;
582605 const connection = await this . getConnection ( ) ;
583- const { bucketType } =
584- < SnowflakeDriverExportBucket > this . config . exportBucket ;
585-
586- let bucketName : string ;
587- let exportPrefix : string ;
588- let path : string ;
589-
590- if ( bucketType === 'azure' ) {
591- ( { bucketName, path } = this . parseBucketUrl ( this . config . exportBucket ! . bucketName ) ) ;
592- const pathArr = path . split ( '/' ) ;
593- bucketName = `${ bucketName } /${ pathArr [ 0 ] } ` ;
594- exportPrefix = pathArr . length > 1 ? `${ pathArr . slice ( 1 ) . join ( '/' ) } /${ tableName } ` : tableName ;
595- } else {
596- ( { bucketName, path } = this . parseBucketUrl ( this . config . exportBucket ! . bucketName ) ) ;
597- exportPrefix = path ? `${ path } /${ tableName } ` : tableName ;
598- }
606+ const bucketUrl = this . buildBucketUrl ( tableName ) ;
599607
600608 const unloadSql = `
601- COPY INTO '${ bucketType } :// ${ bucketName } / ${ exportPrefix } / '
609+ COPY INTO '${ bucketUrl } '
602610 FROM (${ options . query . sql } )
603611 ${ this . exportOptionsClause ( options ) } ` ;
604612 const result = await this . execute < UnloadResponse [ ] > (
@@ -610,7 +618,7 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
610618 if ( ! result ) {
611619 throw new Error ( 'Missing `COPY INTO` query result.' ) ;
612620 }
613- return types ;
621+ return { types, exportedCount : parseInt ( result [ 0 ] . rows_unloaded , 10 ) } ;
614622 }
615623 }
616624
@@ -641,28 +649,13 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
641649 private async unloadWithTable (
642650 tableName : string ,
643651 options : UnloadOptions ,
644- ) : Promise < TableStructure > {
652+ ) : Promise < { types : TableStructure , exportedCount : number } > {
645653 const types = await this . tableColumnTypes ( tableName ) ;
646654 const connection = await this . getConnection ( ) ;
647- const { bucketType } =
648- < SnowflakeDriverExportBucket > this . config . exportBucket ;
649-
650- let bucketName : string ;
651- let exportPrefix : string ;
652- let path : string ;
653-
654- if ( bucketType === 'azure' ) {
655- ( { bucketName, path } = this . parseBucketUrl ( this . config . exportBucket ! . bucketName ) ) ;
656- const pathArr = path . split ( '/' ) ;
657- bucketName = `${ bucketName } /${ pathArr [ 0 ] } ` ;
658- exportPrefix = pathArr . length > 1 ? `${ pathArr . slice ( 1 ) . join ( '/' ) } /${ tableName } ` : tableName ;
659- } else {
660- ( { bucketName, path } = this . parseBucketUrl ( this . config . exportBucket ! . bucketName ) ) ;
661- exportPrefix = path ? `${ path } /${ tableName } ` : tableName ;
662- }
655+ const bucketUrl = this . buildBucketUrl ( tableName ) ;
663656
664657 const unloadSql = `
665- COPY INTO '${ bucketType } :// ${ bucketName } / ${ exportPrefix } / '
658+ COPY INTO '${ bucketUrl } '
666659 FROM ${ tableName }
667660 ${ this . exportOptionsClause ( options ) } ` ;
668661 const result = await this . execute < UnloadResponse [ ] > (
@@ -671,10 +664,12 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface {
671664 [ ] ,
672665 false ,
673666 ) ;
667+
674668 if ( ! result ) {
675669 throw new Error ( 'Missing `COPY INTO` query result.' ) ;
676670 }
677- return types ;
671+
672+ return { types, exportedCount : parseInt ( result [ 0 ] . rows_unloaded , 10 ) } ;
678673 }
679674
680675 /**
0 commit comments