@@ -111,6 +111,10 @@ type conn struct {
111
111
// receiving query results from prepared statements. Only provided for
112
112
// debugging.
113
113
disablePreparedBinaryResult bool
114
+
115
+ // Whether to always send []byte parameters over as binary. Enables single
116
+ // round-trip mode for non-prepared Query calls.
117
+ binaryParameters bool
114
118
}
115
119
116
120
// Handle driver-side settings in parsed connection string.
@@ -122,7 +126,7 @@ func (c *conn) handleDriverSettings(o values) (err error) {
122
126
} else if value == "no" {
123
127
* val = false
124
128
} else {
125
- return fmt .Errorf ("unrecognized value %q for disable_prepared_binary_result " , value )
129
+ return fmt .Errorf ("unrecognized value %q for %s " , value , key )
126
130
}
127
131
}
128
132
return nil
@@ -132,6 +136,10 @@ func (c *conn) handleDriverSettings(o values) (err error) {
132
136
if err != nil {
133
137
return err
134
138
}
139
+ err = boolSetting ("binary_parameters" , & c .binaryParameters )
140
+ if err != nil {
141
+ return err
142
+ }
135
143
return nil
136
144
}
137
145
@@ -234,6 +242,7 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
234
242
cn .ssl (o )
235
243
cn .buf = bufio .NewReader (cn .c )
236
244
cn .startup (o )
245
+
237
246
// reset the deadline, in case one was set (see dial)
238
247
if timeout := o .Get ("connect_timeout" ); timeout != "" && timeout != "0" {
239
248
err = cn .c .SetDeadline (time.Time {})
@@ -696,18 +705,29 @@ func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err err
696
705
return cn .simpleQuery (query )
697
706
}
698
707
699
- st := cn .prepareTo (query , "" )
700
- st .exec (args )
701
- return & rows {
702
- cn : cn ,
703
- colNames : st .colNames ,
704
- colTyps : st .colTyps ,
705
- colFmts : st .colFmts ,
706
- }, nil
708
+ if cn .binaryParameters {
709
+ cn .sendBinaryModeQuery (query , args )
710
+
711
+ cn .readParseResponse ()
712
+ cn .readBindResponse ()
713
+ rows := & rows {cn : cn }
714
+ rows .colNames , rows .colFmts , rows .colTyps = cn .readPortalDescribeResponse ()
715
+ cn .postExecuteWorkaround ()
716
+ return rows , nil
717
+ } else {
718
+ st := cn .prepareTo (query , "" )
719
+ st .exec (args )
720
+ return & rows {
721
+ cn : cn ,
722
+ colNames : st .colNames ,
723
+ colTyps : st .colTyps ,
724
+ colFmts : st .colFmts ,
725
+ }, nil
726
+ }
707
727
}
708
728
709
729
// Implement the optional "Execer" interface for one-shot queries
710
- func (cn * conn ) Exec (query string , args []driver.Value ) (_ driver.Result , err error ) {
730
+ func (cn * conn ) Exec (query string , args []driver.Value ) (res driver.Result , err error ) {
711
731
if cn .bad {
712
732
return nil , driver .ErrBadConn
713
733
}
@@ -721,17 +741,26 @@ func (cn *conn) Exec(query string, args []driver.Value) (_ driver.Result, err er
721
741
return r , err
722
742
}
723
743
724
- // Use the unnamed statement to defer planning until bind
725
- // time, or else value-based selectivity estimates cannot be
726
- // used.
727
- st := cn .prepareTo (query , "" )
744
+ if cn .binaryParameters {
745
+ cn .sendBinaryModeQuery (query , args )
728
746
729
- r , err := st .Exec (args )
730
- if err != nil {
731
- panic (err )
747
+ cn .readParseResponse ()
748
+ cn .readBindResponse ()
749
+ cn .readPortalDescribeResponse ()
750
+ cn .postExecuteWorkaround ()
751
+ res , _ , err = cn .readExecuteResponse ("Execute" )
752
+ return res , err
753
+ } else {
754
+ // Use the unnamed statement to defer planning until bind
755
+ // time, or else value-based selectivity estimates cannot be
756
+ // used.
757
+ st := cn .prepareTo (query , "" )
758
+ r , err := st .Exec (args )
759
+ if err != nil {
760
+ panic (err )
761
+ }
762
+ return r , err
732
763
}
733
-
734
- return r , err
735
764
}
736
765
737
766
func (cn * conn ) send (m * writeBuf ) {
@@ -1026,6 +1055,8 @@ func isDriverSetting(key string) bool {
1026
1055
return true
1027
1056
case "disable_prepared_binary_result" :
1028
1057
return true
1058
+ case "binary_parameters" :
1059
+ return true
1029
1060
1030
1061
default :
1031
1062
return false
@@ -1375,6 +1406,65 @@ func md5s(s string) string {
1375
1406
return fmt .Sprintf ("%x" , h .Sum (nil ))
1376
1407
}
1377
1408
1409
+ func (cn * conn ) sendBinaryModeQuery (query string , args []driver.Value ) {
1410
+ if len (args ) >= 65536 {
1411
+ errorf ("got %d parameters but PostgreSQL only supports 65535 parameters" , len (args ))
1412
+ }
1413
+
1414
+ b := cn .writeBuf ('P' )
1415
+ b .byte (0 ) // unnamed statement
1416
+ b .string (query )
1417
+ b .int16 (0 )
1418
+
1419
+ b .next ('B' )
1420
+ b .int16 (0 ) // unnamed portal and statement
1421
+
1422
+ // Do one pass over the parameters to see if we're going to send any of
1423
+ // them over in binary. If we are, create a paramFormats array at the
1424
+ // same time.
1425
+ var paramFormats []int
1426
+ for i , x := range args {
1427
+ _ , ok := x .([]byte )
1428
+ if ok {
1429
+ if paramFormats == nil {
1430
+ paramFormats = make ([]int , len (args ))
1431
+ }
1432
+ paramFormats [i ] = 1
1433
+ }
1434
+ }
1435
+ if paramFormats == nil {
1436
+ b .int16 (0 )
1437
+ } else {
1438
+ b .int16 (len (paramFormats ))
1439
+ for _ , x := range paramFormats {
1440
+ b .int16 (x )
1441
+ }
1442
+ }
1443
+
1444
+ b .int16 (len (args ))
1445
+ for _ , x := range args {
1446
+ if x == nil {
1447
+ b .int32 (- 1 )
1448
+ } else {
1449
+ datum := binaryEncode (& cn .parameterStatus , x )
1450
+ b .int32 (len (datum ))
1451
+ b .bytes (datum )
1452
+ }
1453
+ }
1454
+ b .int16 (0 )
1455
+
1456
+ b .next ('D' )
1457
+ b .byte ('P' )
1458
+ b .byte (0 ) // unnamed portal
1459
+
1460
+ b .next ('E' )
1461
+ b .byte (0 )
1462
+ b .int32 (0 )
1463
+
1464
+ b .next ('S' )
1465
+ cn .send (b )
1466
+ }
1467
+
1378
1468
func (c * conn ) processParameterStatus (r * readBuf ) {
1379
1469
var err error
1380
1470
@@ -1457,6 +1547,24 @@ func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames [
1457
1547
}
1458
1548
}
1459
1549
1550
+ func (cn * conn ) readPortalDescribeResponse () (colNames []string , colFmts []format , colTyps []oid.Oid ) {
1551
+ t , r := cn .recv1 ()
1552
+ switch t {
1553
+ case 'T' :
1554
+ return parsePortalRowDescribe (r )
1555
+ case 'n' :
1556
+ return nil , nil , nil
1557
+ case 'E' :
1558
+ err := parseError (r )
1559
+ cn .readReadyForQuery ()
1560
+ panic (err )
1561
+ default :
1562
+ cn .bad = true
1563
+ errorf ("unexpected Describe response %q" , t )
1564
+ }
1565
+ panic ("not reached" )
1566
+ }
1567
+
1460
1568
func (cn * conn ) readBindResponse () {
1461
1569
t , r := cn .recv1 ()
1462
1570
switch t {
0 commit comments