56
56
#define FLOAT8LABEL "float8"
57
57
#define FLOAT8OID 701
58
58
59
+ #define FIN_DISCARD 0x1
60
+ #define FIN_CLOSE 0x2
61
+ #define FIN_ABORT 0x4
59
62
60
63
61
- static int pgsql_stmt_dtor (pdo_stmt_t * stmt )
64
+
65
+ static void pgsql_stmt_finish (pdo_pgsql_stmt * S , int fin_mode )
62
66
{
63
- pdo_pgsql_stmt * S = (pdo_pgsql_stmt * )stmt -> driver_data ;
64
- bool server_obj_usable = !Z_ISUNDEF (stmt -> database_object_handle )
65
- && IS_OBJ_VALID (EG (objects_store ).object_buckets [Z_OBJ_HANDLE (stmt -> database_object_handle )])
66
- && !(OBJ_FLAGS (Z_OBJ (stmt -> database_object_handle )) & IS_OBJ_FREE_CALLED );
67
+ if (S -> is_running_unbuffered && S -> result && (fin_mode & FIN_ABORT )) {
68
+ PGcancel * cancel = PQgetCancel (S -> H -> server );
69
+ char errbuf [256 ];
70
+ PQcancel (cancel , errbuf , 256 );
71
+ PQfreeCancel (cancel );
72
+ S -> is_running_unbuffered = false;
73
+ }
67
74
68
75
if (S -> result ) {
69
- /* free the resource */
70
76
PQclear (S -> result );
71
77
S -> result = NULL ;
72
78
}
73
79
74
- if (S -> stmt_name ) {
75
- if (S -> is_prepared && server_obj_usable ) {
76
- pdo_pgsql_db_handle * H = S -> H ;
77
- char * q = NULL ;
78
- PGresult * res ;
80
+ if (S -> is_running_unbuffered ) {
81
+ /* https://postgresql.org/docs/current/libpq-async.html:
82
+ * "PQsendQuery cannot be called again until PQgetResult has returned NULL"
83
+ * And as all single-row functions are connection-wise instead of statement-wise,
84
+ * any new single-row query has to make sure no preceding one is still running.
85
+ */
86
+ // @todo Implement !(fin_mode & FIN_DISCARD)
87
+ // instead of discarding results we could store them to their statement
88
+ // so that their fetch() will get them (albeit not in lazy mode anymore).
89
+ while ((S -> result = PQgetResult (S -> H -> server ))) {
90
+ PQclear (S -> result );
91
+ S -> result = NULL ;
92
+ }
93
+ S -> is_running_unbuffered = false;
94
+ }
79
95
80
- spprintf (& q , 0 , "DEALLOCATE %s" , S -> stmt_name );
81
- res = PQexec (H -> server , q );
82
- efree (q );
83
- if (res ) {
84
- PQclear (res );
85
- }
96
+ if (S -> stmt_name && S -> is_prepared && (fin_mode & FIN_CLOSE )) {
97
+ char * q = NULL ;
98
+ PGresult * res ;
99
+
100
+ spprintf (& q , 0 , "DEALLOCATE %s" , S -> stmt_name );
101
+ res = PQexec (S -> H -> server , q );
102
+ efree (q );
103
+ if (res ) {
104
+ PQclear (res );
86
105
}
106
+
107
+ S -> is_prepared = false;
108
+ }
109
+ }
110
+
111
+ static int pgsql_stmt_dtor (pdo_stmt_t * stmt )
112
+ {
113
+ pdo_pgsql_stmt * S = (pdo_pgsql_stmt * )stmt -> driver_data ;
114
+ bool server_obj_usable = !Z_ISUNDEF (stmt -> database_object_handle )
115
+ && IS_OBJ_VALID (EG (objects_store ).object_buckets [Z_OBJ_HANDLE (stmt -> database_object_handle )])
116
+ && !(OBJ_FLAGS (Z_OBJ (stmt -> database_object_handle )) & IS_OBJ_FREE_CALLED );
117
+
118
+ pgsql_stmt_finish (S , FIN_DISCARD |(server_obj_usable ? FIN_CLOSE |FIN_ABORT : 0 ));
119
+
120
+ if (S -> stmt_name ) {
87
121
efree (S -> stmt_name );
88
122
S -> stmt_name = NULL ;
89
123
}
@@ -137,14 +171,18 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
137
171
pdo_pgsql_stmt * S = (pdo_pgsql_stmt * )stmt -> driver_data ;
138
172
pdo_pgsql_db_handle * H = S -> H ;
139
173
ExecStatusType status ;
174
+ int dispatch_result = 1 ;
140
175
141
176
bool in_trans = stmt -> dbh -> methods -> in_transaction (stmt -> dbh );
142
177
143
- /* ensure that we free any previous unfetched results */
144
- if (S -> result ) {
145
- PQclear (S -> result );
146
- S -> result = NULL ;
178
+ /* in unbuffered mode, finish any running statement: libpq explicitely prohibits this
179
+ * (maybe it will change with pipeline mode in libpq 14?) */
180
+ if (S -> is_unbuffered && H -> running_stmt ) {
181
+ pgsql_stmt_finish (H -> running_stmt , FIN_CLOSE );
182
+ H -> running_stmt = NULL ;
147
183
}
184
+ /* ensure that we free any previous unfetched results */
185
+ pgsql_stmt_finish (S , 0 );
148
186
149
187
S -> current_row = 0 ;
150
188
@@ -219,6 +257,16 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
219
257
}
220
258
}
221
259
}
260
+ if (S -> is_unbuffered ) {
261
+ dispatch_result = PQsendQueryPrepared (H -> server , S -> stmt_name ,
262
+ stmt -> bound_params ?
263
+ zend_hash_num_elements (stmt -> bound_params ) :
264
+ 0 ,
265
+ (const char * * )S -> param_values ,
266
+ S -> param_lengths ,
267
+ S -> param_formats ,
268
+ 0 );
269
+ } else {
222
270
S -> result = PQexecPrepared (H -> server , S -> stmt_name ,
223
271
stmt -> bound_params ?
224
272
zend_hash_num_elements (stmt -> bound_params ) :
@@ -227,22 +275,52 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
227
275
S -> param_lengths ,
228
276
S -> param_formats ,
229
277
0 );
278
+ }
230
279
} else if (stmt -> supports_placeholders == PDO_PLACEHOLDER_NAMED ) {
231
280
/* execute query with parameters */
281
+ if (S -> is_unbuffered ) {
282
+ dispatch_result = PQsendQueryParams (H -> server , ZSTR_VAL (S -> query ),
283
+ stmt -> bound_params ? zend_hash_num_elements (stmt -> bound_params ) : 0 ,
284
+ S -> param_types ,
285
+ (const char * * )S -> param_values ,
286
+ S -> param_lengths ,
287
+ S -> param_formats ,
288
+ 0 );
289
+ } else {
232
290
S -> result = PQexecParams (H -> server , ZSTR_VAL (S -> query ),
233
291
stmt -> bound_params ? zend_hash_num_elements (stmt -> bound_params ) : 0 ,
234
292
S -> param_types ,
235
293
(const char * * )S -> param_values ,
236
294
S -> param_lengths ,
237
295
S -> param_formats ,
238
296
0 );
297
+ }
239
298
} else {
240
299
/* execute plain query (with embedded parameters) */
300
+ if (S -> is_unbuffered ) {
301
+ dispatch_result = PQsendQuery (H -> server , ZSTR_VAL (stmt -> active_query_string ));
302
+ } else {
241
303
S -> result = PQexec (H -> server , ZSTR_VAL (stmt -> active_query_string ));
304
+ }
242
305
}
306
+
307
+ if (S -> is_unbuffered ) {
308
+ if (!dispatch_result ) {
309
+ pdo_pgsql_error_stmt (stmt , 0 , NULL );
310
+ return 0 ;
311
+ }
312
+ S -> is_running_unbuffered = true;
313
+ H -> running_stmt = S ;
314
+ PQsetSingleRowMode (H -> server );
315
+ /* no matter if it returns 0: PQ then transparently fallbacks to full result fetching */
316
+
317
+ /* try a first fetch to at least have column names and so on */
318
+ S -> result = PQgetResult (S -> H -> server );
319
+ }
320
+
243
321
status = PQresultStatus (S -> result );
244
322
245
- if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK ) {
323
+ if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_SINGLE_TUPLE ) {
246
324
pdo_pgsql_error_stmt (stmt , status , pdo_pgsql_sqlstate (S -> result ));
247
325
return 0 ;
248
326
}
@@ -464,6 +542,34 @@ static int pgsql_stmt_fetch(pdo_stmt_t *stmt,
464
542
return 0 ;
465
543
}
466
544
} else {
545
+ if (S -> is_running_unbuffered && S -> current_row >= stmt -> row_count ) {
546
+ ExecStatusType status ;
547
+
548
+ /* @todo in unbuffered mode, PQ allows multiple queries to be passed:
549
+ * column_count should be recomputed on each iteration */
550
+
551
+ if (S -> result ) {
552
+ PQclear (S -> result );
553
+ S -> result = NULL ;
554
+ }
555
+
556
+ S -> result = PQgetResult (S -> H -> server );
557
+ status = PQresultStatus (S -> result );
558
+
559
+ if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_SINGLE_TUPLE ) {
560
+ pdo_pgsql_error_stmt (stmt , status , pdo_pgsql_sqlstate (S -> result ));
561
+ return 0 ;
562
+ }
563
+
564
+ stmt -> row_count = (zend_long )PQntuples (S -> result );
565
+ S -> current_row = 0 ;
566
+
567
+ if (!stmt -> row_count ) {
568
+ S -> is_running_unbuffered = false;
569
+ /* libpq requires looping until getResult returns null */
570
+ pgsql_stmt_finish (S , 0 );
571
+ }
572
+ }
467
573
if (S -> current_row < stmt -> row_count ) {
468
574
S -> current_row ++ ;
469
575
return 1 ;
0 commit comments