/* KInterbasDB Python Package - Implementation of BLOB Conversion (both ways) ** ** Version 3.1 ** ** The following contributors hold Copyright (C) over their respective ** portions of code (see license.txt for details): ** ** [Original Author (maintained through version 2.0-0.3.1):] ** 1998-2001 [alex] Alexander Kuznetsov ** [Maintainers (after version 2.0-0.3.1):] ** 2001-2002 [maz] Marek Isalski ** 2002-2004 [dsr] David Rushby ** [Contributors:] ** 2001 [eac] Evgeny A. Cherkashin ** 2001-2002 [janez] Janez Jere */ /* This source file is designed to be directly included in _kiconversion.c, ** without the involvement of a header file. */ /******************** FUNCTION PROTOTYPES:BEGIN ********************/ static int _blob_info_total_size_and_max_segment_size( ISC_STATUS *status_vector, isc_blob_handle *blob_handle_ptr, ISC_LONG *total_size, unsigned short *max_segment_size ); /******************** FUNCTION PROTOTYPES:END ********************/ /******************** INPUT FUNCTIONS:BEGIN ********************/ /* This implementation of conv_in_blob_from_pybuffer uses the Python raw buffer ** interface rather than slicing and converting each segment to a string before ** passing it to isc_put_segment, as the previous implementation did. */ static int conv_in_blob_from_pybuffer( PyObject *py_buf, ISC_QUAD *blob_id, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ) { isc_blob_handle blob_handle = NULL; isc_blob_handle *blob_handle_ptr = &blob_handle; PyBufferProcs *bufferProcs; char *py_buf_start_ptr; int bytes_written_so_far; unsigned short bytes_to_write_this_time; int total_size = PySequence_Size(py_buf); if (total_size == -1) { return INPUT_ERROR; } /* Get a pointer to the PyBufferObject's getreadbuffer method, then call ** that method, which will make py_buf_start_ptr point to the start of ** the PyBufferObject's raw data buffer. */ bufferProcs = py_buf->ob_type->tp_as_buffer; /* Since this function is only called by kinterbasdb's internals, it's ** acceptable to check for a NULL bf_getreadbuffer only in non-production ** builds. */ assert (bufferProcs->bf_getreadbuffer != NULL); (*bufferProcs->bf_getreadbuffer)(py_buf, 0, (void **) &py_buf_start_ptr); /* Create a blob and retrieve its handle into blob_handle. */ ENTER_DB isc_create_blob2(status_vector, &db_handle, &trans_handle, blob_handle_ptr, blob_id, 0, /* Last two params indicate "no blob parameter buffer supplied". */ NULL ); LEAVE_DB if (DB_API_ERROR(status_vector)) { raise_sql_exception(OperationalError, "conv_in_blob_from_pybuffer.isc_create_blob2: ", status_vector ); return INPUT_ERROR; } /* Within this ENTER/LEAVE_DB block, a Python object (py_buf) is manipulated, ** even though the GIL is not held. However, no Python API calls are made; ** in fact, py_buf is only manipulated in the sense that its internal binary ** buffer (pointed to by py_buf_start_ptr) is read. Since the code ** surrounding the ENTER/LEAVE_DB block holds a reference to py_buf, and ** thereby ensures that py_buf will be destroyed prematurely, this code ** should be safe. */ ENTER_DB /* Copy the data from py_buf's internal byte buffer into the database in ** chunks of size MAX_BLOB_SEGMENT_SIZE (all but the last chunk, which may be ** smaller). */ bytes_written_so_far = 0; bytes_to_write_this_time = MAX_BLOB_SEGMENT_SIZE; while (bytes_written_so_far < total_size) { if (total_size - bytes_written_so_far < MAX_BLOB_SEGMENT_SIZE) { bytes_to_write_this_time = total_size - bytes_written_so_far; } isc_put_segment(status_vector, blob_handle_ptr, bytes_to_write_this_time, py_buf_start_ptr + bytes_written_so_far ); if (DB_API_ERROR(status_vector)) { isc_cancel_blob(status_vector, blob_handle_ptr); /* We must exit from the middle of an ENTER/LEAVE_DB block at this point; ** use an unconventional macro to release the DB API lock and reacquire ** the GIL, before setting a Python exception. */ LEAVE_DB_WITHOUT_ENDING_CODE_BLOCK raise_sql_exception(OperationalError, "conv_in_blob_from_pybuffer.isc_put_segment: ", status_vector ); return INPUT_ERROR; } bytes_written_so_far += bytes_to_write_this_time; } isc_close_blob(status_vector, blob_handle_ptr); LEAVE_DB return INPUT_OK; } /* conv_in_blob_from_pybuffer */ /* DSR created this version of conv_in_blob_from_pystring on 2002.02.23 to ** replace the previous implementation, which broke with strings of length ** >= 2^16. ** This function just creates a Python buffer object from the PyString ** pointer it receives. This "conversion" is QUITE A CHEAP OPERATION. ** It involved no memory copying because it simply creates a "read-only ** reference" into the string's existing character buffer. */ static int conv_in_blob_from_pystring( PyObject *str, ISC_QUAD *blob_id, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ) { PyObject *pyBuffer; int result; /* This function is only called by kinterbasdb's internals, so it's ** acceptable to apply the type check only in non-production builds. */ assert (PyString_Check(str)); pyBuffer = PyBuffer_FromObject(str, 0, PyString_GET_SIZE(str)); if (pyBuffer == NULL) { return INPUT_ERROR; /* PyBuffer_FromObject will have set an exception. */ } result = conv_in_blob_from_pybuffer(pyBuffer, blob_id, status_vector, db_handle, trans_handle ); /* *Must* DECREF the buffer we've created; even though its creation doesn't ** involve copying the string's internal buffer, the string will never be ** garbage collected if the buffer is not DECREFed. */ Py_DECREF(pyBuffer); /* conv_in_blob_from_pybuffer will take care of raising an exception if it ** must; we'll just pass its return value upward. */ return result; } /* conv_in_blob_from_pystring */ /******************** INPUT FUNCTIONS:END ********************/ /******************** OUTPUT FUNCTIONS:BEGIN ********************/ static PyObject *conv_out_blob( ISC_QUAD *blob_id, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ) { isc_blob_handle blob_handle = NULL; isc_blob_handle *blob_handle_ptr = &blob_handle; ISC_LONG total_size = -1; unsigned short max_segment_size = 0; ISC_LONG bytes_read_so_far; unsigned short bytes_actually_read; PyObject *py_str; char *py_str_start_ptr; int blob_stat; /* Get a handle to the blob. */ ENTER_DB isc_open_blob2(status_vector, &db_handle, &trans_handle, blob_handle_ptr, blob_id, /* Last two params indicate "no blob parameter buffer supplied": */ 0, NULL ); LEAVE_DB if (DB_API_ERROR(status_vector)) { raise_sql_exception( OperationalError, "conv_out_blob.isc_open_blob2: ", status_vector ); return NULL; } /* Before actually reading any of the blob's contents, determine the total ** size of the blob and the size of its largest segment. */ if (0 != _blob_info_total_size_and_max_segment_size( status_vector, blob_handle_ptr, &total_size, &max_segment_size ) ) { /* _blob_info_total_size_and_max_segment_size has already set an exception. */ return NULL; } /* Create an empty PyStringObject large enough to hold the entire blob. */ /* Handle the very remote possibility that passing an ISC_LONG to ** PyString_FromStringAndSize would cause an overflow (on most current ** platforms, ISC_LONG and int are actually identical, so no overflow is ** possible). */ if (total_size > INT_MAX) { raise_exception(InternalError, "conv_out_blob: blob too large; kinterbasdb only supports blob sizes" " up to the system-defined INT_MAX." ); return NULL; } py_str = PyString_FromStringAndSize(NULL, (int)total_size); if (py_str == NULL) { return NULL; } /* Set py_str_start_ptr to point the the beginning of py_str's internal ** buffer. */ py_str_start_ptr = PyString_AS_STRING(py_str); /* DSR documented his concerns about this GIL-handling scheme in a lengthy ** comment in function conv_in_blob_from_pybuffer. */ ENTER_DB /* Now, transfer the blob's contents from the database into the preallocated ** Python string named py_str. Use repeated calls to isc_get_segment to ** effect the transfer. */ bytes_read_so_far = 0; while (bytes_read_so_far < total_size) { blob_stat = isc_get_segment( status_vector, blob_handle_ptr, &bytes_actually_read, (unsigned short) MIN( (long)max_segment_size, total_size - bytes_read_so_far ), py_str_start_ptr + bytes_read_so_far ); /* It is possible for isc_get_segment to return non-zero values under ** normal circumstances, but not under circumstances that would be normal ** *in this situation*, because we already knew the exact size of the blob ** and the length of its longest segment before we started this transfer ** loop. Therefore, there is no need to check specifically for return ** values isc_segment or isc_segstr_eof, which will never happen here under ** non-erroneous circumstances. */ assert (blob_stat != isc_segment); assert (blob_stat != isc_segstr_eof); if (blob_stat != 0) { LEAVE_DB_WITHOUT_ENDING_CODE_BLOCK raise_sql_exception(OperationalError, "conv_out_blob.isc_get_segment, segment retrieval error: ", status_vector ); Py_DECREF(py_str); return NULL; } bytes_read_so_far += bytes_actually_read; } isc_close_blob(status_vector, blob_handle_ptr); LEAVE_DB return py_str; } /* conv_out_blob */ /******************** OUTPUT FUNCTIONS:END ********************/ /******************** UTILITY FUNCTIONS:BEGIN ********************/ /* _blob_info_total_size_and_max_segment_size inserts into its arguments ** total_size and max_segment_size the total size and maximum segment size ** (respectively) of the specified blob. ** Returns 0 if successful, otherwise -1. ** ** See IB6 API Guide chapter entitled "Working with Blob Data". */ static int _blob_info_total_size_and_max_segment_size( ISC_STATUS *status_vector, isc_blob_handle *blob_handle_ptr, ISC_LONG *total_size, unsigned short *max_segment_size ) { char blob_info_items[] = { isc_info_blob_total_length, isc_info_blob_max_segment }; char result_buffer[ISC_INFO_BUFFER_SIZE]; short length; char *ptr; char item; ENTER_DB isc_blob_info(status_vector, blob_handle_ptr, sizeof(blob_info_items), blob_info_items, sizeof(result_buffer), result_buffer ); LEAVE_DB if (DB_API_ERROR(status_vector)) { raise_sql_exception(InternalError, "_blob_info_total_size_and_max_segment_size.isc_blob_info: ", status_vector ); return -1; }; /* Extract the values returned in the result buffer. */ ptr = result_buffer; while (*ptr != isc_info_end) { item = *ptr++; ENTER_DB length = (short) isc_vax_integer(ptr, 2); LEAVE_DB ptr += 2; switch (item) { case isc_info_blob_total_length: ENTER_DB *total_size = isc_vax_integer(ptr, length); LEAVE_DB break; case isc_info_blob_max_segment: ENTER_DB *max_segment_size = (unsigned short) isc_vax_integer(ptr, length); LEAVE_DB break; case isc_info_truncated: raise_sql_exception(InternalError, "_blob_info_total_size_and_max_segment_size: isc_blob_info return" " truncated: ", status_vector ); return -1; } ptr += length; } return 0; } /* _blob_info_total_size_and_max_segment_size */ /******************** UTILITY FUNCTIONS:END ********************/