Merge pull request #52 from m6w6/issue-pointers
[m6w6/ext-pq] / src / php_pqlob.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: pq |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2013, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <libpq-events.h>
18 #include <libpq/libpq-fs.h>
19 #include <php.h>
20
21 #include "php_pq.h"
22 #include "php_pq_misc.h"
23 #include "php_pq_object.h"
24 #include "php_pqexc.h"
25 #include "php_pqlob.h"
26
27 zend_class_entry *php_pqlob_class_entry;
28 static zend_object_handlers php_pqlob_object_handlers;
29 static HashTable php_pqlob_object_prophandlers;
30
31 static void php_pqlob_object_free(zend_object *o)
32 {
33 php_pqlob_object_t *obj = PHP_PQ_OBJ(NULL, o);
34 #if DBG_GC
35 fprintf(stderr, "FREE lob(#%d) %p (txn(#%d): %p)\n", obj->zo.handle, obj, obj->intern->txn->zo.handle, obj->intern->txn);
36 #endif
37 if (obj->intern) {
38 if (obj->intern->lofd) {
39 lo_close(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd);
40 }
41 /* invalidate the stream */
42 if (obj->intern->stream) {
43 zend_list_delete(obj->intern->stream->res);
44 obj->intern->stream = NULL;
45 }
46 php_pq_object_delref(obj->intern->txn);
47 efree(obj->intern);
48 obj->intern = NULL;
49 }
50 php_pq_object_dtor(o);
51 }
52
53 php_pqlob_object_t *php_pqlob_create_object_ex(zend_class_entry *ce, php_pqlob_t *intern)
54 {
55 return php_pq_object_create(ce, intern, sizeof(php_pqlob_object_t),
56 &php_pqlob_object_handlers, &php_pqlob_object_prophandlers);
57 }
58
59 static zend_object *php_pqlob_create_object(zend_class_entry *class_type)
60 {
61 return &php_pqlob_create_object_ex(class_type, NULL)->zo;
62 }
63
64 static void php_pqlob_object_read_transaction(void *o, zval *return_value)
65 {
66 php_pqlob_object_t *obj = o;
67
68 php_pq_object_to_zval(obj->intern->txn, return_value);
69 }
70
71 static void php_pqlob_object_gc_transaction(void *o, zval *return_value)
72 {
73 php_pqlob_object_t *obj = o;
74 zval ztxn;
75
76 php_pq_object_to_zval_no_addref(obj->intern->txn, &ztxn);
77 add_next_index_zval(return_value, &ztxn);
78 }
79
80 static void php_pqlob_object_read_oid(void *o, zval *return_value)
81 {
82 php_pqlob_object_t *obj = o;
83
84 RETVAL_LONG(obj->intern->loid);
85 }
86
87 static void php_pqlob_object_update_stream(php_pqlob_object_t *obj, zval *zstream);
88
89 static void php_pqlob_object_read_stream(void *o, zval *return_value)
90 {
91 php_pqlob_object_t *obj = o;
92 zval zstream;
93
94 if (!obj->intern->stream) {
95 php_pqlob_object_update_stream(obj, &zstream);
96 } else {
97 php_stream_to_zval(obj->intern->stream, &zstream);
98 }
99
100 RETVAL_ZVAL(&zstream, 1, 0);
101 }
102
103 #if PHP_VERSION_ID < 70400
104 static size_t php_pqlob_stream_write(php_stream *stream, const char *buffer, size_t length)
105 #else
106 static ssize_t php_pqlob_stream_write(php_stream *stream, const char *buffer, size_t length)
107 #endif
108 {
109 php_pqlob_object_t *obj = stream->abstract;
110 ssize_t written = 0;
111
112 if (obj) {
113 written = lo_write(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, buffer, length);
114
115 if (written < 0) {
116 php_error_docref(NULL, E_WARNING, "Failed to write to LOB with oid=%u (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
117 }
118
119 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
120 }
121
122 #if PHP_VERSION_ID < 70400
123 return (written < 0 ? 0 : written);
124 #else
125 return written;
126 #endif
127 }
128
129 #if PHP_VERSION_ID < 70400
130 static size_t php_pqlob_stream_read(php_stream *stream, char *buffer, size_t length)
131 #else
132 static ssize_t php_pqlob_stream_read(php_stream *stream, char *buffer, size_t length)
133 #endif
134 {
135 php_pqlob_object_t *obj = stream->abstract;
136 ssize_t read = 0;
137
138 if (obj) {
139
140 if (!buffer && !length) {
141 if (lo_tell(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd) == lo_lseek(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, 0, SEEK_CUR)) {
142 return EOF;
143 }
144 } else {
145 read = lo_read(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, buffer, length);
146
147 if (read < 0) {
148 php_error_docref(NULL, E_WARNING, "Failed to read from LOB with oid=%d (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
149 }
150 }
151
152 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
153 }
154
155 #if PHP_VERSION_ID < 70400
156 return (read < 0 ? 0 : read);
157 #else
158 return read;
159 #endif
160 }
161
162 static ZEND_RESULT_CODE php_pqlob_stream_close(php_stream *stream, int close_handle)
163 {
164 return SUCCESS;
165 }
166
167 static int php_pqlob_stream_flush(php_stream *stream)
168 {
169 return SUCCESS;
170 }
171
172 static ZEND_RESULT_CODE php_pqlob_stream_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset)
173 {
174 ZEND_RESULT_CODE rv = FAILURE;
175 php_pqlob_object_t *obj = stream->abstract;
176
177 if (obj) {
178 int position = lo_lseek(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, offset, whence);
179
180 if (position < 0) {
181 php_error_docref(NULL, E_WARNING, "Failed to seek offset in LOB with oid=%d (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
182 rv = FAILURE;
183 } else {
184 *newoffset = position;
185 rv = SUCCESS;
186 }
187
188 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
189 }
190
191 return rv;
192 }
193
194 static php_stream_ops php_pqlob_stream_ops = {
195 /* stdio like functions - these are mandatory! */
196 php_pqlob_stream_write,
197 php_pqlob_stream_read,
198 php_pqlob_stream_close,
199 php_pqlob_stream_flush,
200
201 "pq\\LOB stream",
202
203 /* these are optional */
204 php_pqlob_stream_seek,
205 NULL, /* cast */
206 NULL, /* stat */
207 NULL, /* set_option */
208 };
209
210 static void php_pqlob_object_update_stream(php_pqlob_object_t *obj, zval *zstream)
211 {
212 zval zobj, zmember;
213
214 ZVAL_STRINGL(&zmember, "stream", sizeof("stream")-1);
215
216 obj->intern->stream = php_stream_alloc(&php_pqlob_stream_ops, obj, NULL, "r+b");
217 obj->intern->stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
218 php_stream_to_zval(obj->intern->stream, zstream);
219
220 #if PHP_VERSION_ID >= 80000
221 zend_std_write_property(&obj->zo, Z_STR(zmember), zstream, NULL);
222 #else
223 ZVAL_OBJ(&zobj, &obj->zo);
224 zend_std_write_property(&zobj, &zmember, zstream, NULL);
225 #endif
226 zval_ptr_dtor(&zmember);
227 }
228
229 ZEND_BEGIN_ARG_INFO_EX(ai_pqlob_construct, 0, 0, 1)
230 ZEND_ARG_OBJ_INFO(0, transaction, pq\\Transaction, 0)
231 ZEND_ARG_INFO(0, oid)
232 ZEND_ARG_INFO(0, mode)
233 ZEND_END_ARG_INFO();
234 static PHP_METHOD(pqlob, __construct) {
235 zend_error_handling zeh;
236 zval *ztxn;
237 zend_long mode = INV_WRITE|INV_READ, loid = InvalidOid;
238 ZEND_RESULT_CODE rv;
239
240 zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh);
241 rv = zend_parse_parameters(ZEND_NUM_ARGS(), "O|ll", &ztxn, php_pqtxn_class_entry, &loid, &mode);
242 zend_restore_error_handling(&zeh);
243
244 if (SUCCESS == rv) {
245 php_pqlob_object_t *obj = PHP_PQ_OBJ(getThis(), NULL);
246 php_pqtxn_object_t *txn_obj = PHP_PQ_OBJ(ztxn, NULL);
247
248 if (obj->intern) {
249 throw_exce(EX_BAD_METHODCALL, "pq\\LOB already initialized");
250 } else if (!txn_obj->intern) {
251 throw_exce(EX_UNINITIALIZED, "pq\\Transaction not initialized");
252 } else if (!txn_obj->intern->open) {
253 throw_exce(EX_RUNTIME, "pq\\Transation already closed");
254 } else {
255 if (loid == InvalidOid) {
256 loid = lo_creat(txn_obj->intern->conn->intern->conn, mode);
257 }
258
259 if (loid == InvalidOid) {
260 throw_exce(EX_RUNTIME, "Failed to create large object with mode '%s' (%s)", php_pq_strmode(mode), PHP_PQerrorMessage(txn_obj->intern->conn->intern->conn));
261 } else {
262 int lofd = lo_open(txn_obj->intern->conn->intern->conn, loid, mode);
263
264 if (lofd < 0) {
265 throw_exce(EX_RUNTIME, "Failed to open large object with oid=%u with mode '%s' (%s)", loid, php_pq_strmode(mode), PHP_PQerrorMessage(txn_obj->intern->conn->intern->conn));
266 } else {
267 obj->intern = ecalloc(1, sizeof(*obj->intern));
268 obj->intern->lofd = lofd;
269 obj->intern->loid = loid;
270 php_pq_object_addref(txn_obj);
271 obj->intern->txn = txn_obj;
272 }
273 }
274
275 php_pqconn_notify_listeners(txn_obj->intern->conn);
276 }
277 }
278 }
279
280 ZEND_BEGIN_ARG_INFO_EX(ai_pqlob_write, 0, 0, 1)
281 ZEND_ARG_INFO(0, data)
282 ZEND_END_ARG_INFO();
283 static PHP_METHOD(pqlob, write) {
284 zend_error_handling zeh;
285 char *data_str;
286 size_t data_len;
287 ZEND_RESULT_CODE rv;
288
289 zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh);
290 rv = zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data_str, &data_len);
291 zend_restore_error_handling(&zeh);
292
293 if (SUCCESS == rv) {
294 php_pqlob_object_t *obj = PHP_PQ_OBJ(getThis(), NULL);
295
296 if (!obj->intern) {
297 throw_exce(EX_UNINITIALIZED, "pq\\LOB not initialized");
298 } else {
299 int written = lo_write(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, data_str, data_len);
300
301 if (written < 0) {
302 throw_exce(EX_RUNTIME, "Failed to write to LOB with oid=%u (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
303 } else {
304 RETVAL_LONG(written);
305 }
306
307 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
308 }
309 }
310 }
311
312 ZEND_BEGIN_ARG_INFO_EX(ai_pqlob_read, 0, 0, 0)
313 ZEND_ARG_INFO(0, length)
314 ZEND_ARG_INFO(1, read)
315 ZEND_END_ARG_INFO();
316 static PHP_METHOD(pqlob, read) {
317 zend_error_handling zeh;
318 zend_long length = 0x1000;
319 zval *zread = NULL;
320 ZEND_RESULT_CODE rv;
321
322 zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh);
323 rv = zend_parse_parameters(ZEND_NUM_ARGS(), "|lz!", &length, &zread);
324 zend_restore_error_handling(&zeh);
325
326 if (SUCCESS == rv) {
327 php_pqlob_object_t *obj = PHP_PQ_OBJ(getThis(), NULL);
328
329 if (!obj->intern) {
330 throw_exce(EX_UNINITIALIZED, "pq\\LOB not initialized");
331 } else {
332 zend_string *buffer = zend_string_alloc(length, 0);
333 int read = lo_read(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, &buffer->val[0], length);
334
335 if (read < 0) {
336 zend_string_release(buffer);
337 throw_exce(EX_RUNTIME, "Failed to read from LOB with oid=%d (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
338 } else {
339 if (zread) {
340 ZVAL_DEREF(zread);
341 zval_dtor(zread);
342 ZVAL_LONG(zread, read);
343 }
344 buffer->val[buffer->len = read] = '\0';
345 RETVAL_STR(buffer);
346 }
347
348 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
349 }
350 }
351 }
352
353 ZEND_BEGIN_ARG_INFO_EX(ai_pqlob_seek, 0, 0, 1)
354 ZEND_ARG_INFO(0, offset)
355 ZEND_ARG_INFO(0, whence)
356 ZEND_END_ARG_INFO();
357 static PHP_METHOD(pqlob, seek) {
358 zend_error_handling zeh;
359 zend_long offset, whence = SEEK_SET;
360 ZEND_RESULT_CODE rv;
361
362 zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh);
363 rv = zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &offset, &whence);
364 zend_restore_error_handling(&zeh);
365
366 if (SUCCESS == rv) {
367 php_pqlob_object_t *obj = PHP_PQ_OBJ(getThis(), NULL);
368
369 if (!obj->intern) {
370 throw_exce(EX_UNINITIALIZED, "pq\\LOB not initialized");
371 } else {
372 int position = lo_lseek(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, offset, whence);
373
374 if (position < 0) {
375 throw_exce(EX_RUNTIME, "Failed to seek offset in LOB with oid=%d (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
376 } else {
377 RETVAL_LONG(position);
378 }
379
380 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
381 }
382 }
383 }
384
385 ZEND_BEGIN_ARG_INFO_EX(ai_pqlob_tell, 0, 0, 0)
386 ZEND_END_ARG_INFO();
387 static PHP_METHOD(pqlob, tell) {
388 zend_error_handling zeh;
389 ZEND_RESULT_CODE rv;
390
391 zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh);
392 rv = zend_parse_parameters_none();
393 zend_restore_error_handling(&zeh);
394
395 if (SUCCESS == rv) {
396 php_pqlob_object_t *obj = PHP_PQ_OBJ(getThis(), NULL);
397
398 if (!obj->intern) {
399 throw_exce(EX_UNINITIALIZED, "pq\\LOB not initialized");
400 } else {
401 int position = lo_tell(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd);
402
403 if (position < 0) {
404 throw_exce(EX_RUNTIME, "Failed to tell offset in LOB with oid=%d (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
405 } else {
406 RETVAL_LONG(position);
407 }
408
409 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
410 }
411 }
412 }
413
414 ZEND_BEGIN_ARG_INFO_EX(ai_pqlob_truncate, 0, 0, 0)
415 ZEND_ARG_INFO(0, length)
416 ZEND_END_ARG_INFO();
417 static PHP_METHOD(pqlob, truncate) {
418 zend_error_handling zeh;
419 zend_long length = 0;
420 ZEND_RESULT_CODE rv;
421
422 zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh);
423 rv = zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &length);
424 zend_restore_error_handling(&zeh);
425
426 if (SUCCESS == rv) {
427 php_pqlob_object_t *obj = PHP_PQ_OBJ(getThis(), NULL);
428
429 if (!obj->intern) {
430 throw_exce(EX_UNINITIALIZED, "pq\\LOB not initialized");
431 } else {
432 int rc = lo_truncate(obj->intern->txn->intern->conn->intern->conn, obj->intern->lofd, length);
433
434 if (rc != 0) {
435 throw_exce(EX_RUNTIME, "Failed to truncate LOB with oid=%d (%s)", obj->intern->loid, PHP_PQerrorMessage(obj->intern->txn->intern->conn->intern->conn));
436 }
437
438 php_pqconn_notify_listeners(obj->intern->txn->intern->conn);
439 }
440 }
441 }
442
443 static zend_function_entry php_pqlob_methods[] = {
444 PHP_ME(pqlob, __construct, ai_pqlob_construct, ZEND_ACC_PUBLIC)
445 PHP_ME(pqlob, write, ai_pqlob_write, ZEND_ACC_PUBLIC)
446 PHP_ME(pqlob, read, ai_pqlob_read, ZEND_ACC_PUBLIC)
447 PHP_ME(pqlob, seek, ai_pqlob_seek, ZEND_ACC_PUBLIC)
448 PHP_ME(pqlob, tell, ai_pqlob_tell, ZEND_ACC_PUBLIC)
449 PHP_ME(pqlob, truncate, ai_pqlob_truncate, ZEND_ACC_PUBLIC)
450 {0}
451 };
452
453 PHP_MSHUTDOWN_FUNCTION(pqlob)
454 {
455 zend_hash_destroy(&php_pqlob_object_prophandlers);
456 return SUCCESS;
457 }
458
459 PHP_MINIT_FUNCTION(pqlob)
460 {
461 zend_class_entry ce = {0};
462 php_pq_object_prophandler_t ph = {0};
463
464 INIT_NS_CLASS_ENTRY(ce, "pq", "LOB", php_pqlob_methods);
465 php_pqlob_class_entry = zend_register_internal_class_ex(&ce, NULL);
466 php_pqlob_class_entry->create_object = php_pqlob_create_object;
467
468 memcpy(&php_pqlob_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
469 php_pqlob_object_handlers.offset = XtOffsetOf(php_pqlob_object_t, zo);
470 php_pqlob_object_handlers.free_obj = php_pqlob_object_free;
471 php_pqlob_object_handlers.read_property = php_pq_object_read_prop;
472 php_pqlob_object_handlers.write_property = php_pq_object_write_prop;
473 php_pqlob_object_handlers.clone_obj = NULL;
474 php_pqlob_object_handlers.get_property_ptr_ptr = php_pq_object_get_prop_ptr_null;
475 php_pqlob_object_handlers.get_gc = php_pq_object_get_gc;
476 php_pqlob_object_handlers.get_properties = php_pq_object_properties;
477 php_pqlob_object_handlers.get_debug_info = php_pq_object_debug_info;
478
479 zend_hash_init(&php_pqlob_object_prophandlers, 3, NULL, php_pq_object_prophandler_dtor, 1);
480
481 zend_declare_property_null(php_pqlob_class_entry, ZEND_STRL("transaction"), ZEND_ACC_PUBLIC);
482 ph.read = php_pqlob_object_read_transaction;
483 ph.gc = php_pqlob_object_gc_transaction;
484 zend_hash_str_add_mem(&php_pqlob_object_prophandlers, "transaction", sizeof("transaction")-1, (void *) &ph, sizeof(ph));
485 ph.gc = NULL;
486
487 zend_declare_property_long(php_pqlob_class_entry, ZEND_STRL("oid"), InvalidOid, ZEND_ACC_PUBLIC);
488 ph.read = php_pqlob_object_read_oid;
489 zend_hash_str_add_mem(&php_pqlob_object_prophandlers, "oid", sizeof("oid")-1, (void *) &ph, sizeof(ph));
490
491 zend_declare_property_null(php_pqlob_class_entry, ZEND_STRL("stream"), ZEND_ACC_PUBLIC);
492 ph.read = php_pqlob_object_read_stream;
493 zend_hash_str_add_mem(&php_pqlob_object_prophandlers, "stream", sizeof("stream")-1, (void *) &ph, sizeof(ph));
494
495 zend_declare_class_constant_long(php_pqlob_class_entry, ZEND_STRL("INVALID_OID"), InvalidOid);
496 zend_declare_class_constant_long(php_pqlob_class_entry, ZEND_STRL("R"), INV_READ);
497 zend_declare_class_constant_long(php_pqlob_class_entry, ZEND_STRL("W"), INV_WRITE);
498 zend_declare_class_constant_long(php_pqlob_class_entry, ZEND_STRL("RW"), INV_READ|INV_WRITE);
499
500 return SUCCESS;
501 }
502
503 /*
504 * Local variables:
505 * tab-width: 4
506 * c-basic-offset: 4
507 * End:
508 * vim600: noet sw=4 ts=4 fdm=marker
509 * vim<600: noet sw=4 ts=4
510 */