@@ -6,12 +6,14 @@ import (
66 "database/sql"
77 "fmt"
88 "io"
9+ "net"
910 "os/exec"
1011 "strings"
1112 "time"
1213
1314 "cloud.google.com/go/cloudsqlconn"
1415 "cloud.google.com/go/cloudsqlconn/postgres/pgxv5"
16+ "github.com/jackc/pgx/v5"
1517 "github.com/jmoiron/sqlx"
1618 "github.com/samber/lo"
1719 "github.com/slingdata-io/sling-cli/core/dbio"
@@ -28,6 +30,7 @@ type PostgresConn struct {
2830 URL string
2931 isCloudSQL bool
3032 cloudSQLCleanup func ()
33+ cloudSQLConn * pgx.Conn
3134}
3235
3336// Init initiates the object
@@ -195,6 +198,14 @@ func (conn *PostgresConn) connectCloudSQL(timeOut ...int) error {
195198 return g .Error (err , "could not ping Cloud SQL instance. Verify: 1) IAM auth flag 'cloudsql.iam_authentication=on', 2) SA '%s' has 'Cloud SQL Instance User' role, 3) SA added as IAM db user with full email, 4) DB privileges granted to '%s', 5) Credentials file valid and matches SA, 6) Cloud SQL Admin API enabled" , user , user )
196199 }
197200
201+ // Open native pgx connection for COPY operations
202+ // This connection will be reused throughout the connection lifecycle
203+ err = conn .openCloudSQLNativePgxConn (ctx , instanceName , user , dbName , dialerOpts )
204+ if err != nil {
205+ g .Warn ("could not open native pgx connection for COPY operations: %s" , err .Error ())
206+ conn .SetProp ("use_bulk" , "false" ) // set to not use COPY since IAM auth need pgx conn to COPY
207+ }
208+
198209 // Set role if provided
199210 if val := conn .GetProp ("role" ); val != "" {
200211 _ , err = conn .Exec ("SET ROLE " + val )
@@ -212,12 +223,57 @@ func (conn *PostgresConn) connectCloudSQL(timeOut ...int) error {
212223 return nil
213224}
214225
226+ // openCloudSQLNativePgxConn opens a native pgx connection for Cloud SQL COPY operations
227+ // This is called once during connectCloudSQL and the connection is reused
228+ func (conn * PostgresConn ) openCloudSQLNativePgxConn (ctx context.Context , instanceName , user , dbName string , dialerOpts []cloudsqlconn.Option ) error {
229+ // Create Cloud SQL dialer
230+ dialer , err := cloudsqlconn .NewDialer (ctx , dialerOpts ... )
231+ if err != nil {
232+ return g .Error (err , "could not create Cloud SQL dialer for native pgx connection" )
233+ }
234+
235+ // Build pgx connection config
236+ pgxConfig , err := pgx .ParseConfig (fmt .Sprintf ("user=%s dbname=%s sslmode=disable" , user , dbName ))
237+ if err != nil {
238+ return g .Error (err , "could not parse pgx config" )
239+ }
240+
241+ // Set the dialer function to use Cloud SQL connector
242+ pgxConfig .DialFunc = func (ctx context.Context , network , addr string ) (net.Conn , error ) {
243+ return dialer .Dial (ctx , instanceName )
244+ }
245+
246+ // Open native pgx connection
247+ conn .cloudSQLConn , err = pgx .ConnectConfig (ctx , pgxConfig )
248+ if err != nil {
249+ return g .Error (err , "could not open native pgx connection for Cloud SQL COPY" )
250+ }
251+
252+ g .Trace ("opened native pgx connection for Cloud SQL COPY operations" )
253+ return nil
254+ }
255+
256+ // closeCloudSQLNativeConn closes the native pgx connection
257+ func (conn * PostgresConn ) closeCloudSQLNativeConn () {
258+ if conn .cloudSQLConn != nil {
259+ ctx := conn .Context ().Ctx
260+ if err := conn .cloudSQLConn .Close (ctx ); err != nil {
261+ g .LogError (err , "error closing native Cloud SQL connection" )
262+ }
263+ conn .cloudSQLConn = nil
264+ g .Trace ("closed native pgx connection for Cloud SQL COPY operations" )
265+ }
266+ }
267+
215268// Close closes the database connection and cleans up Cloud SQL resources
216269func (conn * PostgresConn ) Close () error {
217270 // Cleanup Cloud SQL resources first
218271 if conn .isCloudSQL {
219272 g .Trace ("closing Cloud SQL connection and cleaning up resources" )
220273
274+ // Close native pgx connection if open
275+ conn .closeCloudSQLNativeConn ()
276+
221277 // Call cleanup function to unregister driver
222278 if conn .cloudSQLCleanup != nil {
223279 conn .cloudSQLCleanup ()
@@ -310,6 +366,12 @@ func (conn *PostgresConn) BulkExportStream(table Table) (ds *iop.Datastream, err
310366
311367// BulkImportStream inserts a stream into a table
312368func (conn * PostgresConn ) BulkImportStream (tableFName string , ds * iop.Datastream ) (count uint64 , err error ) {
369+ // For Cloud SQL with pgx, use a specialized implementation
370+ if conn .isCloudSQL && conn .cloudSQLConn != nil {
371+ return conn .bulkImportStreamPgx (tableFName , ds )
372+ }
373+
374+ // Standard lib/pq implementation for normal PostgreSQL
313375 var columns iop.Columns
314376
315377 mux := ds .Context .Mux
@@ -383,7 +445,6 @@ func (conn *PostgresConn) BulkImportStream(tableFName string, ds *iop.Datastream
383445 if err != nil {
384446 ds .Context .CaptureErr (g .Error (err , "could not COPY into table %s" , tableFName ))
385447 ds .Context .Cancel ()
386- g .Warn (g .Marshal (err ))
387448 g .Trace ("error for rec: %s" , g .Pretty (batch .Columns .MakeRec (row )))
388449 return g .Error (err , "could not execute statement" )
389450 }
@@ -410,7 +471,107 @@ func (conn *PostgresConn) BulkImportStream(tableFName string, ds *iop.Datastream
410471
411472 ds .SetEmpty ()
412473
413- g .Trace ("cOPY %d ROWS" , count )
474+ g .Trace ("COPY %d ROWS" , count )
475+ return count , nil
476+ }
477+
478+ // bulkImportStreamPgx handles COPY for Cloud SQL using native pgx
479+ func (conn * PostgresConn ) bulkImportStreamPgx (tableFName string , ds * iop.Datastream ) (count uint64 , err error ) {
480+ var columns iop.Columns
481+
482+ mux := ds .Context .Mux
483+
484+ table , err := ParseTableName (tableFName , conn .GetType ())
485+ if err != nil {
486+ err = g .Error (err , "could not get table name for import" )
487+ return
488+ }
489+
490+ // Verify native pgx connection is available
491+ if conn .cloudSQLConn == nil {
492+ return count , g .Error ("native pgx connection not available for Cloud SQL COPY" )
493+ }
494+
495+ // set OnSchemaChange
496+ if df := ds .Df (); df != nil && cast .ToBool (conn .GetProp ("adjust_column_type" )) {
497+ oldOnColumnChanged := df .OnColumnChanged
498+ df .OnColumnChanged = func (col iop.Column ) error {
499+
500+ // sleep to allow transaction to close
501+ time .Sleep (300 * time .Millisecond )
502+
503+ mux .Lock ()
504+ defer mux .Unlock ()
505+
506+ // use pre-defined function
507+ err = oldOnColumnChanged (col )
508+ if err != nil {
509+ return g .Error (err , "could not process ColumnChange for Postgres (pgx)" )
510+ }
511+
512+ return nil
513+ }
514+ }
515+
516+ // close the transaction from main connection to allow cloudSQLConn connection to COPY
517+ if err = conn .Commit (); err != nil {
518+ return 0 , g .Error (err , "could not commit for COPY operation" )
519+ }
520+
521+ for batch := range ds .BatchChan {
522+ if batch .ColumnsChanged () || batch .IsFirst () {
523+ mux .Lock ()
524+ columns , err = conn .GetColumns (tableFName , batch .Columns .Names ()... )
525+ mux .Unlock ()
526+ if err != nil {
527+ return count , g .Error (err , "could not get matching list of columns from table" )
528+ }
529+
530+ err = batch .Shape (columns )
531+ if err != nil {
532+ return count , g .Error (err , "could not shape batch stream" )
533+ }
534+ }
535+
536+ err = func () error {
537+ // Collect rows from batch for CopyFrom
538+ var rows [][]interface {}
539+ for row := range batch .Rows {
540+ rows = append (rows , row )
541+ }
542+
543+ if len (rows ) == 0 {
544+ return nil
545+ }
546+
547+ // Use pgx CopyFrom with pgx.Identifier for schema.table
548+ mux .Lock ()
549+ rowsInserted , err := conn .cloudSQLConn .CopyFrom (
550+ conn .Context ().Ctx ,
551+ pgx.Identifier {table .Schema , table .Name },
552+ columns .Names (),
553+ pgx .CopyFromRows (rows ),
554+ )
555+ mux .Unlock ()
556+
557+ if err != nil {
558+ return g .Error (err , "could not COPY into table %s using pgx" , tableFName )
559+ }
560+
561+ count += uint64 (rowsInserted )
562+ g .Trace ("pgx CopyFrom inserted %d rows" , rowsInserted )
563+
564+ return nil
565+ }()
566+
567+ if err != nil {
568+ return count , g .Error (err , "could not copy data (pgx)" )
569+ }
570+ }
571+
572+ ds .SetEmpty ()
573+
574+ g .Trace ("pgx COPY %d ROWS" , count )
414575 return count , nil
415576}
416577
0 commit comments