@@ -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