/* KInterbasDB Python Package - Implementation of Array 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. */ /* YYY: The conversion code should be able to implicitly transform array shape ** like this: ** - input: Python seq [1,2,3] supplied for a SMALLINT[4] DB array -> (1,2,3,NULL) ** - output: a SMALLINT[4] DB array (1,2,3,NULL) -> Python list [1,2,3] ** ** Note 2003.02.12: ** IB 6 API Guide page 153 says: ** """ ** The following array operations are not supported: ** ... ** - Setting individual array elements to NULL. ** """ ** So I guess there's no possibility of "implicitly transforming" array shape. */ /******************** HARD-CODED LIMITS:BEGIN ********************/ /* MAXIMUM_NUMBER_OF_ARRAY_DIMENSIONS is an IB/Firebird engine constraint, not ** something we could overcome here in kinterbasdb. */ #define MAXIMUM_NUMBER_OF_ARRAY_DIMENSIONS 16 /******************** HARD-CODED LIMITS:END ********************/ /******************** CONVENIENCE DEFS:BEGIN ********************/ #define ARRAY_ROW_MAJOR 0 #define ARRAY_COLUMN_MAJOR 1 #define DIMENSION_SIZE_END_MARKER -1 /* YYY: VARCHAR array elements seem to be stored differently from the way ** conventional VARCHAR fields are stored. Instead of 2 bytes at the beginning ** containing the size of the string value, array-element VARCHARs apparently ** have 2 null bytes at the end that are not used. */ #define _ADJUST_ELEMENT_SIZE_FOR_VARCHAR_IF_NECESSARY(data_type, size_of_el) \ if (data_type == blr_varying || data_type == blr_varying2) { \ size_of_el += 2; \ } /* 2003.03.30: */ #define SQLSUBTYPE_DETERMINATION_ERROR -999 /******************** CONVENIENCE DEFS:END ********************/ /******************** FUNCTION PROTOTYPES:BEGIN ********************/ /* Output functions: */ static PyObject *conv_out_array( CursorObject *cursor, ISC_QUAD *array_id, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ); static PyObject *conv_out_array_element( CursorObject *cursor, char *data, short data_type, int size_of_single_element, short scale, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ); static PyObject *_extract_db_array_buffer_to_pyseq( CursorObject *cursor, char **data_slot, short *dimension_sizes_ptr, /* Boilerplate parameters: */ short data_type, int size_of_single_element, short scale, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ); /* Input functions: */ static int conv_in_array( PyObject *py_input, ISC_QUAD **array_id_slot, CursorObject *cursor, char *table_name, short table_name_length, char *field_name, short field_name_length ); static int conv_in_array_element( PyObject *py_input, char **data_slot, unsigned short dialect, short data_type, short data_subtype, int size_of_single_element, short scale, PyObject *converter, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ); static int _extract_pyseq_to_db_array_buffer( PyObject *py_seq, short *dimension_sizes_ptr, /* Boilerplate parameters: */ char **data_slot, unsigned short dialect, short data_type, short data_subtype, int size_of_single_element, short scale, PyObject *converter, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ); /* Functions common to both input and output: */ static ISC_ARRAY_DESC *_populate_array_descriptor( /* These strings aren't null-terminated: */ char *sqlvar_table_name, short sqlvar_table_name_length, char *sqlvar_field_name, short sqlvar_field_name_length, /* Boilerplate params: */ ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ); static short *_extract_dimensions_sizes( ISC_ARRAY_DESC *desc, /* output param: */ int *total_number_of_elements ); /* 2003.03.30: */ static short _determine_sqlsubtype_for_array( ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ); #ifdef KIDB_DEBUGGERING static void dump_array_descriptor(ISC_ARRAY_DESC *desc); #endif /* KIDB_DEBUGGERING */ /******************** FUNCTION PROTOTYPES:END ********************/ /******************** INPUT FUNCTIONS:BEGIN ********************/ #define _CONV_IN_ARRAY_MEMORY_ERROR_IF_NULL(pointer) \ if (pointer == NULL) { \ PyErr_NoMemory(); \ goto CONV_IN_ARRAY_CLEANUP; \ } #define _CONV_IN_ARRAY_GENERIC_ERROR_IF_NECESSARY(status_vector) \ if ( DB_API_ERROR(status_vector) ) { \ goto CONV_IN_ARRAY_GENERIC_ERROR; \ } static int conv_in_array( PyObject *py_input, ISC_QUAD **array_id_slot, CursorObject *cursor, char *table_name, short table_name_length, char *field_name, short field_name_length ) { int status = INPUT_OK; ISC_ARRAY_DESC *desc; short *dimensions = NULL; unsigned short number_of_dimensions; int total_number_of_elements = 0; short data_type = -1; unsigned short size_of_single_element; char *source_buf = NULL, *source_buf_walker; ISC_LONG source_buf_size; ISC_STATUS *status_vector = cursor->status_vector; isc_db_handle db_handle = cursor->connection->db_handle; isc_tr_handle trans_handle = CON_GET_TRANS_HANDLE(cursor->connection); /* 2003.10.15a:OK */ PyObject *converter = NULL; short data_subtype = -1; /* Read the database array descriptor for this field. */ desc = _populate_array_descriptor( table_name, table_name_length, field_name, field_name_length, status_vector, db_handle, trans_handle ); if (desc == NULL) { /* The _populate_array_descriptor function will already have set exception. */ status = INPUT_ERROR; goto CONV_IN_ARRAY_CLEANUP; } data_type = desc->array_desc_dtype; number_of_dimensions = desc->array_desc_dimensions; size_of_single_element = desc->array_desc_length; _ADJUST_ELEMENT_SIZE_FOR_VARCHAR_IF_NECESSARY(data_type, size_of_single_element); /* Populate the short-array named dimensions. (The function also sets ** total_number_of_elements to its appropriate value.) */ dimensions = _extract_dimensions_sizes(desc, &total_number_of_elements); _CONV_IN_ARRAY_MEMORY_ERROR_IF_NULL(dimensions); /* The database engine doesn't allow zero-element arrays. */ assert (total_number_of_elements > 0); /* Validate the incoming Python sequence to ensure that its shape matches ** that defined by the database array descriptor for this field. */ source_buf_size = size_of_single_element * total_number_of_elements; source_buf = kimem_main_malloc(source_buf_size); _CONV_IN_ARRAY_MEMORY_ERROR_IF_NULL(source_buf); source_buf_walker = source_buf; assert (data_type != -1); data_subtype = _determine_sqlsubtype_for_array( status_vector, db_handle, trans_handle, table_name, table_name_length, field_name, field_name_length ); if (data_subtype == SQLSUBTYPE_DETERMINATION_ERROR) { goto CONV_IN_ARRAY_CLEANUP; } { short scale = desc->array_desc_scale; /* Find the dynamic type translation converter (if any) for this array's type. */ converter = cursor_get_in_converter(cursor, data_type, data_subtype, scale, TRUE); if (converter == NULL) { goto CONV_IN_ARRAY_CLEANUP; } /* At this point, converter is either a Python callable, if there was a ** registered converter for this array's element type, or Py_None if there ** was not. */ status = _extract_pyseq_to_db_array_buffer( py_input, dimensions, /* For conversion: */ &source_buf_walker, cursor->connection->dialect, data_type, data_subtype, size_of_single_element, scale, converter, status_vector, db_handle, trans_handle ); if (status != INPUT_OK) { goto CONV_IN_ARRAY_CLEANUP; } } /* Successful completion requires the entire buffer to have been filled: */ assert (source_buf_walker - source_buf == source_buf_size); /* Call isc_array_put_slice to store the incoming value in the database. ** A NULL array id tells isc_array_put_slice to "create or replace" the ** existing array in the database. */ assert (*array_id_slot == NULL); { ISC_QUAD *array_id; array_id = *array_id_slot = kimem_main_malloc(sizeof(ISC_QUAD)); _CONV_IN_ARRAY_MEMORY_ERROR_IF_NULL(array_id); /* "Nullify" the array id: */ /* 2003.01.25: In FB 1.5a5, isc_quad_high/isc_quad_low no longer work, ** but gds_quad_high/gds_quad_low work with both 1.0 and 1.5a5. */ array_id->gds_quad_high = 0; array_id->gds_quad_low = 0; ENTER_DB isc_array_put_slice( status_vector, &db_handle, &trans_handle, array_id, desc, source_buf, &source_buf_size ); LEAVE_DB _CONV_IN_ARRAY_GENERIC_ERROR_IF_NECESSARY(status_vector); } /* array_id_slot (a pointer to a pointer passed in by the caller) is freshly ** initialized by isc_array_put_slice. In effect, it is "passed back" to the ** caller, so that it can be accessed in the XSQLVAR when the statement is ** executed. */ /* We've stored the array successfully; now clean up. */ goto CONV_IN_ARRAY_CLEANUP; CONV_IN_ARRAY_GENERIC_ERROR: raise_sql_exception( OperationalError, "Array input conversion: ", status_vector ); status = INPUT_ERROR; /* Fall through to generic cleanup. */ CONV_IN_ARRAY_CLEANUP: if (desc != NULL) kimem_main_free(desc); if (dimensions != NULL) kimem_main_free(dimensions); if (source_buf != NULL) kimem_main_free(source_buf); if (status != INPUT_OK && *array_id_slot != NULL) { kimem_main_free(*array_id_slot); *array_id_slot = NULL; } return status; } /* conv_in_array */ /*****************************************************************************/ #define CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION(conversion_code) \ TRY_INPUT_CONVERSION( (conversion_code), CONV_IN_ARRAY_ELEMENT_FAIL ); /* A "standard" DB type code is like SQL_LONG rather than blr_long. */ #define CONV_IN_ARRAY_ELEMENT_CONVERT_INTEGER_TYPE(standard_db_type_code) \ CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( \ conv_in_internal_integer_types_array(py_input_converted, data_slot, \ dialect, standard_db_type_code, data_subtype, scale \ ) \ ); static int conv_in_array_element( PyObject *py_input, char **data_slot, unsigned short dialect, short data_type, short data_subtype, int size_of_single_element, short scale, PyObject *converter, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ) { int status; PyObject *py_input_converted; assert (py_input != NULL); py_input_converted = dynamically_type_convert_input_obj_if_necessary( py_input, TRUE, /* it IS an array element */ dialect, data_type, data_subtype, scale, converter ); if (py_input_converted == NULL) { goto CONV_IN_ARRAY_ELEMENT_FAIL; } switch (data_type) { case blr_text: case blr_text2: CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( conv_in_text_array(data_slot, size_of_single_element, ' ') ); break; case blr_varying: case blr_varying2: CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( conv_in_text_array(data_slot, size_of_single_element, /* Unlike normal VARCHAR field values, VARCHAR array elements are ** stored at a constant length, but padded with null characters: */ '\0' ) ); break; case blr_short: CONV_IN_ARRAY_ELEMENT_CONVERT_INTEGER_TYPE(SQL_SHORT); break; case blr_long: CONV_IN_ARRAY_ELEMENT_CONVERT_INTEGER_TYPE(SQL_LONG); break; #ifdef INTERBASE6_OR_LATER case blr_int64: CONV_IN_ARRAY_ELEMENT_CONVERT_INTEGER_TYPE(SQL_INT64); break; #endif /* INTERBASE6_OR_LATER */ case blr_float: CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( conv_in_float_array(py_input_converted, data_slot) ); break; case blr_double: case blr_d_float: CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( conv_in_double_array(py_input_converted, data_slot) ); break; case blr_timestamp: CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( conv_in_timestamp_array(py_input_converted, data_slot) ); break; #ifdef INTERBASE6_OR_LATER case blr_sql_date: CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( conv_in_date_array(py_input_converted, data_slot) ); break; case blr_sql_time: CONV_IN_ARRAY_ELEMENT_TRY_INPUT_CONVERSION( conv_in_time_array(py_input_converted, data_slot) ); break; #endif /* INTERBASE6_OR_LATER */ /* Currently, none of the following types is supported, because it's not clear ** how one would create such a field via SQL DDL. As far as I can tell, this ** makes the types below useless for a client interface such as kinterbasdb: */ case blr_quad: case blr_blob: case blr_blob_id: raise_exception( NotSupportedError, "kinterbasdb does not support arrays of arrays or arrays of blobs" " because it's not clear how one would create such a field via SQL." ); goto CONV_IN_ARRAY_ELEMENT_FAIL; /* NULL-terminated string: */ case blr_cstring: case blr_cstring2: raise_exception( NotSupportedError, "kinterbasdb does not support blr_cstring arrays because it's not clear" " how one would create such a field via SQL, or even why it would be" " desirable (in light of the existence of CHAR and VARCHAR arrays)." ); goto CONV_IN_ARRAY_ELEMENT_FAIL; default: raise_exception( NotSupportedError, "Incoming conversion of array element of this type not supported " KIDB_REPORT " " KIDB_HOME_PAGE ); goto CONV_IN_ARRAY_ELEMENT_FAIL; } /* end of switch on data_type */ /* Success: */ status = INPUT_OK; goto CONV_IN_ARRAY_ELEMENT_CLEANUP; CONV_IN_ARRAY_ELEMENT_FAIL: status = INPUT_ERROR; CONV_IN_ARRAY_ELEMENT_CLEANUP: Py_XDECREF(py_input_converted); return status; } /* conv_in_array_element */ /*****************************************************************************/ #define _EXTRACT_SEQ_GET_SEQ_EL_WITH_EXCEPTION(seq, index, target) \ target = PySequence_GetItem(seq, index); \ if (target == NULL) { \ raise_exception(InterfaceError, "Array input conversion:" \ " could not retrieve element of input sequence" \ ); \ return INPUT_ERROR; \ } static int _extract_pyseq_to_db_array_buffer( /* For validation: */ PyObject *py_seq, short *dimension_sizes_ptr, /* For conversion: */ char **data_slot, unsigned short dialect, short data_type, short data_subtype, int size_of_single_element, short scale, PyObject *converter, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ) { int i; int py_seq_len; int required_length_of_this_dimension = (int) *dimension_sizes_ptr; assert (required_length_of_this_dimension > 0); if ( !PySequence_Check(py_seq) || PyString_Check(py_seq) ) { #if PYTHON_2_2_OR_LATER { PyObject *input_py_obj_type = PyObject_Type(py_seq); if (input_py_obj_type != NULL) { PyObject *input_py_obj_type_repr = PyObject_Repr(input_py_obj_type); if (input_py_obj_type_repr != NULL) { PyObject *err_msg = PyString_FromFormat( "Array input conversion: type error:" " input sequence must be Python sequence other than string, not" " %s", /* We use PyString_AsString rather than PyString_AS_STRING on ** input_py_obj_type_repr because it be a unicode object. */ PyString_AsString(input_py_obj_type_repr) ); if (err_msg != NULL) { raise_exception( InterfaceError, PyString_AS_STRING(err_msg) ); Py_DECREF(err_msg); } Py_DECREF(input_py_obj_type_repr); } Py_DECREF(input_py_obj_type); } } #else /* not PYTHON_2_2_OR_LATER */ raise_exception(InterfaceError, "Array input conversion: type error:" " input sequence must be Python sequence other than string." ); #endif /* PYTHON_2_2_OR_LATER */ return INPUT_ERROR; } /* if not appropriate seq */ py_seq_len = PySequence_Length(py_seq); if (py_seq_len == -1) { return INPUT_ERROR; } if (py_seq_len != required_length_of_this_dimension) { const char *base_err_msg = "Array input conversion: the input sequence is" " not appropriately shaped"; #if PYTHON_2_2_OR_LATER { PyObject *err_msg = PyString_FromFormat("%s (current dimension requires" " input sequences of exactly %d elements, but actual input sequence" " has%s%d elements).", base_err_msg, required_length_of_this_dimension, (py_seq_len < required_length_of_this_dimension ? " only " : ""), py_seq_len ); if (err_msg != NULL) { raise_exception( InterfaceError, PyString_AS_STRING(err_msg) ); Py_DECREF(err_msg); } } #else /* not PYTHON_2_2_OR_LATER */ raise_exception( InterfaceError, base_err_msg ); #endif /* PYTHON_2_2_OR_LATER */ return INPUT_ERROR; } else { short *next_dimension_size_ptr = dimension_sizes_ptr + 1; if (*next_dimension_size_ptr == DIMENSION_SIZE_END_MARKER) { /* py_seq contains "leaf objects" (input values rather than subsequences). ** Convert each "leaf object" from its Python repr to its DB-internal ** repr; store the result in the raw array source buffer. */ int conv_status_for_this_value; for (i = 0; i < py_seq_len; i++) { PyObject *py_input; _EXTRACT_SEQ_GET_SEQ_EL_WITH_EXCEPTION(py_seq, i, py_input); /* MEAT: */ conv_status_for_this_value = conv_in_array_element( py_input, data_slot, dialect, data_type, data_subtype, size_of_single_element, scale, converter, status_vector, db_handle, trans_handle ); Py_DECREF(py_input); /* PySequence_GetItem creates new ref; discard it. */ if (conv_status_for_this_value == INPUT_ERROR) { /* The conversion function will already have set an exception. */ return INPUT_ERROR; } /* Move the raw-array-source-buffer pointer to the next slot. */ *data_slot += size_of_single_element; } /* end of convert-each-value loop */ } else { /* py_seq does NOT contain "leaf objects", so recursively validate each ** sub-element of py_seq. */ for (i = 0; i < py_seq_len; i++) { int status_for_this_sub_el; PyObject *sub_el; _EXTRACT_SEQ_GET_SEQ_EL_WITH_EXCEPTION(py_seq, i, sub_el); /* MEAT: */ status_for_this_sub_el = _extract_pyseq_to_db_array_buffer( sub_el, next_dimension_size_ptr, data_slot, dialect, data_type, data_subtype, size_of_single_element, scale, converter, status_vector, db_handle, trans_handle ); Py_DECREF(sub_el); /* PySequence_GetItem creates new ref; discard it. */ if (status_for_this_sub_el == INPUT_ERROR) { return INPUT_ERROR; } } /* end of validate-each-subelement loop */ } /* end of this-sequence-contains-leaves if block */ } /* end of this-sequence-was-appropriate length if block */ return INPUT_OK; } /* _extract_pyseq_to_db_array_buffer */ /******************** INPUT FUNCTIONS:END ********************/ /******************** OUTPUT FUNCTIONS:BEGIN ********************/ #define _CONV_OUT_ARRAY_MEMORY_ERROR_IF_NULL(pointer) \ if (pointer == NULL) { \ PyErr_NoMemory(); \ goto CONV_OUT_ARRAY_CLEANUP; \ } #define _CONV_OUT_ARRAY_GENERIC_ERROR_IF_NECESSARY(status_vector) \ if ( DB_API_ERROR(status_vector) ) { \ goto CONV_OUT_ARRAY_GENERIC_ERROR; \ } static PyObject *conv_out_array( CursorObject *cursor, ISC_QUAD *array_id, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ) { /* YYY:I think that arrays are always stored in row-major order inside the DB, ** and are also retrieved that way unless the app requests otherwise (via ** desc->array_desc_flags). I'm not sure, though; perhaps I need to handle ** cases where: ** desc->array_desc_flags == ARRAY_COLUMN_MAJOR */ PyObject *result = NULL; char *output_buf = NULL, *output_buf_walker; ISC_LONG output_buf_size = -1; unsigned short size_of_single_element = 0; short scale = -1; short data_type = -1; ISC_ARRAY_DESC *desc; unsigned short number_of_dimensions; short *dimensions = NULL; int total_number_of_elements; /* Will be set later. */ desc = _populate_array_descriptor( table_name, table_name_length, field_name, field_name_length, status_vector, db_handle, trans_handle ); _CONV_OUT_ARRAY_MEMORY_ERROR_IF_NULL(desc); #ifdef KIDB_DEBUGGERING dump_array_descriptor(desc); #endif /* KIDB_DEBUGGERING */ number_of_dimensions = desc->array_desc_dimensions; assert (number_of_dimensions >= 1); data_type = desc->array_desc_dtype; size_of_single_element = desc->array_desc_length; _ADJUST_ELEMENT_SIZE_FOR_VARCHAR_IF_NECESSARY(data_type, size_of_single_element); scale = desc->array_desc_scale; /* Populate the short-array named dimensions. (The function also sets ** total_number_of_elements to its appropriate value.) */ dimensions = _extract_dimensions_sizes(desc, &total_number_of_elements); _CONV_OUT_ARRAY_MEMORY_ERROR_IF_NULL(dimensions); /* The database engine doesn't allow zero-element arrays. */ assert (total_number_of_elements > 0); output_buf_size = size_of_single_element * total_number_of_elements; output_buf = kimem_main_malloc(output_buf_size); _CONV_OUT_ARRAY_MEMORY_ERROR_IF_NULL(output_buf); output_buf_walker = output_buf; ENTER_DB isc_array_get_slice(status_vector, &db_handle, &trans_handle, array_id, desc, (void *) output_buf, &output_buf_size ); LEAVE_DB _CONV_OUT_ARRAY_GENERIC_ERROR_IF_NECESSARY(status_vector); /* The MEAT: */ result = _extract_db_array_buffer_to_pyseq( cursor, &output_buf_walker, /* pointer to pointer to the first element of the output buffer */ dimensions, /* pointer to array containing the element counts for successive dimensions */ /* Boilerplate parameters: */ data_type, size_of_single_element, scale, /* Pass through utility stuff from above: */ status_vector, db_handle, trans_handle, table_name, table_name_length, field_name, field_name_length ); if (result == NULL) { if (!PyErr_Occurred()) { PyErr_NoMemory(); } goto CONV_OUT_ARRAY_CLEANUP; } assert (output_buf_walker - output_buf == output_buf_size); /* We've retrieved the array successfully; now clean up. */ goto CONV_OUT_ARRAY_CLEANUP; CONV_OUT_ARRAY_GENERIC_ERROR: raise_sql_exception( OperationalError, "Array output conversion: ", status_vector ); if (result != NULL) { Py_DECREF(result); result = NULL; } /* Fall though to the rest of the cleanup. */ CONV_OUT_ARRAY_CLEANUP: if (desc != NULL) kimem_main_free(desc); if (dimensions != NULL) kimem_main_free(dimensions); if (output_buf != NULL) kimem_main_free(output_buf); /* If there was a problem, the code that ordered the jump to ** CONV_OUT_ARRAY_CLEANUP is required to have set a Python exception, and to ** have set the PyObject *result to NULL (after having deallocated it, if ** necessary). ** For these reasons, at this stage we can simply return result, regardless ** of its value. */ assert (PyErr_Occurred() ? result == NULL : result != NULL); return result; } /* conv_out_array */ /*****************************************************************************/ static PyObject *_extract_db_array_buffer_to_pyseq( CursorObject *cursor, char **data_slot, short *dimension_sizes_ptr, /* Boilerplate parameters (capitalized to differentiate them): */ short data_type, int size_of_single_element, short scale, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ) { short elCount = *dimension_sizes_ptr; short *next_dimension_size_ptr = dimension_sizes_ptr + 1; short i; PyObject *seq = PyList_New(elCount); if (seq == NULL) { PyErr_NoMemory(); goto _EXTRACT_DB_ARRAY_BUFFER_TO_PYSEQ_FAILURE; } if (*next_dimension_size_ptr == DIMENSION_SIZE_END_MARKER) { for (i = 0; i < elCount; i++) { PyObject *val = conv_out_array_element( cursor, *data_slot, data_type, size_of_single_element, scale, status_vector, db_handle, trans_handle, table_name, table_name_length, field_name, field_name_length ); if (val == NULL) { goto _EXTRACT_DB_ARRAY_BUFFER_TO_PYSEQ_FAILURE; } /* Move the raw-array-desination-buffer pointer to the next slot. */ *data_slot += size_of_single_element; /* PyList_SET_ITEM steals ref to val; no need to DECREF. */ PyList_SET_ITEM(seq, i, val); } } else { /* not the ultimate nestLev */ for (i = 0; i < elCount; i++ ) { /* Recursive call: */ PyObject *subList = _extract_db_array_buffer_to_pyseq( cursor, data_slot, next_dimension_size_ptr, /* Boilerplate parameters (capitalized to differentiate them): */ data_type, size_of_single_element, scale, status_vector, db_handle, trans_handle, table_name, table_name_length, field_name, field_name_length ); if (subList == NULL) { goto _EXTRACT_DB_ARRAY_BUFFER_TO_PYSEQ_FAILURE; } /* PyList_SET_ITEM steals ref to subList; no need to DECREF. */ PyList_SET_ITEM(seq, i, subList); } } /* end of block: if/else ultimate_nest_level */ assert (PyList_GET_SIZE(seq) == elCount); return seq; _EXTRACT_DB_ARRAY_BUFFER_TO_PYSEQ_FAILURE: Py_XDECREF(seq); return NULL; } /* _extract_db_array_buffer_to_pyseq */ /*****************************************************************************/ static PyObject *conv_out_array_element( CursorObject *cursor, char *data, short data_type, int size_of_single_element, short scale, ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ) { PyObject *result = NULL; PyObject *converter = NULL; const unsigned short dialect = cursor->connection->dialect; short data_subtype = _determine_sqlsubtype_for_array( status_vector, db_handle, trans_handle, table_name, table_name_length, field_name, field_name_length ); if (data_subtype == SQLSUBTYPE_DETERMINATION_ERROR) { return NULL; } converter = cursor_get_out_converter(cursor, data_type, data_subtype, scale, TRUE); /* cursor_get_out_converter returns NULL on error, borrowed reference to ** Py_None if there was no converter. */ if (converter == NULL) { return NULL; } switch (data_type) { case blr_text: case blr_text2: result = conv_out_char(data, size_of_single_element); break; case blr_varying: case blr_varying2: { /* YYY: VARCHAR array elements seem to be stored differently from the way ** conventional VARCHAR fields are stored (see documentary note about ** _ADJUST_ELEMENT_SIZE_FOR_VARCHAR_IF_NECESSARY). */ int len_before_null = strlen(data); result = conv_out_char(data, (len_before_null <= size_of_single_element ? len_before_null : size_of_single_element) ); } break; /* NULL-terminated string: */ case blr_cstring: case blr_cstring2: result = PyString_FromString(data); break; case blr_short: result = conv_out_short_long(data, SQL_SHORT, IS_FIXED_POINT__ARRAY_EL(dialect, data_type, data_subtype, scale), scale ); break; case blr_long: result = conv_out_short_long(data, SQL_LONG, IS_FIXED_POINT__ARRAY_EL(dialect, data_type, data_subtype, scale), scale ); break; #ifdef INTERBASE6_OR_LATER case blr_int64: result = conv_out_int64(data, IS_FIXED_POINT__ARRAY_EL(dialect, data_type, data_subtype, scale), scale ); break; #endif /* INTERBASE6_OR_LATER */ case blr_float: result = conv_out_floating(*((float *) data), dialect, scale); break; case blr_double: case blr_d_float: result = conv_out_floating(*((double *) data), dialect, scale); break; case blr_timestamp: result = conv_out_timestamp(data); break; #ifdef INTERBASE6_OR_LATER case blr_sql_date: result = conv_out_date(data); break; case blr_sql_time: result = conv_out_time(data); break; #endif /* INTERBASE6_OR_LATER */ case blr_quad: /* ISC_QUAD structure; since the DB engine doesn't support arrays of ** arrays, assume that this item refers to a blob id. */ case blr_blob: case blr_blob_id: result = conv_out_blob( (ISC_QUAD *)data, status_vector, db_handle, trans_handle ); break; default: raise_exception( NotSupportedError, "Outgoing conversion of array element of this type not supported " KIDB_REPORT " " KIDB_HOME_PAGE ); return NULL; } assert (converter != NULL); /* Can't be NULL; may be None. */ /* Obviously mustn't invoke the converter if the original value was not ** loaded properly from the database. */ if (result != NULL) { /* Replacing the PyObject pointer in result is not a refcount leak; see the ** comments in dynamically_type_convert_output_obj_if_necessary. */ result = dynamically_type_convert_output_obj_if_necessary( result, converter, data_type, data_subtype ); } return result; } /* conv_out_array_element */ /******************** OUTPUT FUNCTIONS:END ********************/ /******************** UTILITY FUNCTIONS:BEGIN ********************/ #define POPULATE_ARRAY_DESC_MEMORY_ERROR_IF_NULL(pointer) \ if (pointer == NULL) { \ PyErr_NoMemory(); \ goto POPULATE_ARRAY_DESC_ERROR; \ } static ISC_ARRAY_DESC *_populate_array_descriptor( /* These strings aren't null-terminated: */ char *sqlvar_table_name, short sqlvar_table_name_length, char *sqlvar_field_name, short sqlvar_field_name_length, /* Boilerplate params: */ ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle ) { ISC_ARRAY_DESC *desc; /* isc_array_lookup_* functions require null-terminated strings, but the ** relevant strings from the XSQLVAR structure are not null-terminated. */ char *null_terminated_table_name = NULL, *null_terminated_field_name = NULL; /* Begin initial memory allocation section. */ desc = kimem_main_malloc(sizeof(ISC_ARRAY_DESC)); POPULATE_ARRAY_DESC_MEMORY_ERROR_IF_NULL(desc); null_terminated_table_name = kimem_main_malloc(sqlvar_table_name_length + 1); POPULATE_ARRAY_DESC_MEMORY_ERROR_IF_NULL(null_terminated_table_name); null_terminated_field_name = kimem_main_malloc(sqlvar_field_name_length + 1); POPULATE_ARRAY_DESC_MEMORY_ERROR_IF_NULL(null_terminated_field_name); /* End initial memory allocation section. */ /* Copy the non-null-terminated strings table_name and field_name into ** null-terminated the strings null_terminated_*_name. */ memcpy(null_terminated_table_name, sqlvar_table_name, sqlvar_table_name_length); null_terminated_table_name[sqlvar_table_name_length] = '\0'; memcpy(null_terminated_field_name, sqlvar_field_name, sqlvar_field_name_length); null_terminated_field_name[sqlvar_field_name_length] = '\0'; ENTER_DB isc_array_lookup_bounds(status_vector, &db_handle, &trans_handle, null_terminated_table_name, null_terminated_field_name, desc ); LEAVE_DB if (DB_API_ERROR(status_vector)) { raise_sql_exception( OperationalError, "Array input conversion: ", status_vector ); goto POPULATE_ARRAY_DESC_ERROR; } /* Successfully completed. */ goto POPULATE_ARRAY_DESC_CLEANUP; POPULATE_ARRAY_DESC_ERROR: if (desc != NULL) kimem_main_free(desc); desc = NULL; /* Fall through to generic cleanup. */ POPULATE_ARRAY_DESC_CLEANUP: if (null_terminated_table_name != NULL) kimem_main_free(null_terminated_table_name); if (null_terminated_field_name != NULL) kimem_main_free(null_terminated_field_name); return desc; } /* _populate_array_descriptor */ /*****************************************************************************/ static short *_extract_dimensions_sizes( ISC_ARRAY_DESC *desc, /* output param: */ int *total_number_of_elements ) { short *dimensions; int dimension; unsigned short number_of_dimensions = desc->array_desc_dimensions; ISC_ARRAY_BOUND bounds_of_current_dimension; /* Populate the short-array named dimensions, and calculate the total number ** of elements: */ dimensions = kimem_main_malloc((number_of_dimensions + 1) * sizeof(short)); if (dimensions == NULL) { PyErr_NoMemory(); return NULL; } *total_number_of_elements = 1; for (dimension = 0; dimension < number_of_dimensions; dimension++) { bounds_of_current_dimension = desc->array_desc_bounds[dimension]; dimensions[dimension] = (bounds_of_current_dimension.array_bound_upper + 1) - bounds_of_current_dimension.array_bound_lower ; *total_number_of_elements *= dimensions[dimension]; } /* The final element is set to a flag value (for pointer-walking convenience). */ dimensions[number_of_dimensions] = DIMENSION_SIZE_END_MARKER; return dimensions; } /* _extract_dimensions_sizes */ static short _determine_sqlsubtype_for_array( ISC_STATUS *status_vector, isc_db_handle db_handle, isc_tr_handle trans_handle, char *table_name, short table_name_length, char *field_name, short field_name_length ) { /* Returns the subtype on success, or SQLSUBTYPE_DETERMINATION_ERROR on error. */ const char *subtype_determination_statement = "SELECT FIELD_SPEC.RDB$FIELD_SUB_TYPE" " FROM RDB$FIELDS FIELD_SPEC, RDB$RELATION_FIELDS REL_FIELDS" " WHERE" " FIELD_SPEC.RDB$FIELD_NAME = REL_FIELDS.RDB$FIELD_SOURCE" " AND REL_FIELDS.RDB$RELATION_NAME = ?" " AND REL_FIELDS.RDB$FIELD_NAME = ?" ; XSQLDA *out_da = NULL; XSQLDA *in_da = NULL; XSQLVAR *in_var = NULL; XSQLVAR *out_var = NULL; isc_stmt_handle stmt_handle_sqlsubtype = NULL; short sqlsubtype = SQLSUBTYPE_DETERMINATION_ERROR; /* 2003.03.31: */ in_da = kimem_xsqlda_malloc(XSQLDA_LENGTH(2)); in_da->version = SQLDA_VERSION1; in_da->sqln = 2; in_da->sqld = 2; in_da->sqlvar ->sqltype = SQL_TEXT; (in_da->sqlvar + 1)->sqltype = SQL_TEXT; /* Set the names of the relation.field for which we're determining precision. */ in_var = in_da->sqlvar; /* First input variable. */ in_var->sqllen = table_name_length; in_var->sqldata = table_name; in_var++; /* Second input variable. */ in_var->sqllen = field_name_length; in_var->sqldata = field_name; /* Set up the output structures. We know at design time exactly how they ** should be configured; there's no convoluted dance of dynamism here, as ** there is in servicing a generic Python-level query. */ /* 2003.03.31: */ out_da = (XSQLDA *) kimem_xsqlda_malloc(XSQLDA_LENGTH(1)); out_da->version = SQLDA_VERSION1; out_da->sqln = 1; out_var = out_da->sqlvar; out_var->sqldata = (char *) kimem_main_malloc(sizeof(short)); /* We won't actually use the null status of the output field (it will never ** be NULL), but the API requires that space be allocated anyway. */ out_var->sqlind = (short *) kimem_main_malloc(sizeof(short)); ENTER_DB isc_dsql_allocate_statement( status_vector, &db_handle, &stmt_handle_sqlsubtype ); LEAVE_DB if (DB_API_ERROR(status_vector)) goto _DETERMINE_SQLSUBTYPE_FOR_ARRAY_CLEANUP; ENTER_DB isc_dsql_prepare( status_vector, &trans_handle, &stmt_handle_sqlsubtype, 0, (char *) subtype_determination_statement, 3, out_da ); LEAVE_DB if (DB_API_ERROR(status_vector)) goto _DETERMINE_SQLSUBTYPE_FOR_ARRAY_CLEANUP; ENTER_DB isc_dsql_execute2( status_vector, &trans_handle, &stmt_handle_sqlsubtype, 3, in_da, out_da ); LEAVE_DB if (DB_API_ERROR(status_vector)) goto _DETERMINE_SQLSUBTYPE_FOR_ARRAY_CLEANUP; sqlsubtype = *((short *) out_var->sqldata); _DETERMINE_SQLSUBTYPE_FOR_ARRAY_CLEANUP: if (stmt_handle_sqlsubtype != NULL) { /* 2003.10.06: ** The isc_dsql_free_statement call here is relatively safe because under ** normal circumstances the connection won't be severed between the time ** the statement handle is allocated with isc_dsql_allocate_statement ** (earlier in this same function) and the time it's freed (here). */ ENTER_DB isc_dsql_free_statement( status_vector, &stmt_handle_sqlsubtype, DSQL_drop ); LEAVE_DB } if (out_da != NULL) { kimem_main_free(out_da->sqlvar->sqldata); kimem_main_free(out_da->sqlvar->sqlind); } /* 2003.03.31: */ kimem_xsqlda_free(out_da); kimem_xsqlda_free(in_da); if (DB_API_ERROR(status_vector)) { raise_sql_exception(InternalError, "_determine_sqlsubtype_for_array: ", status_vector ); return SQLSUBTYPE_DETERMINATION_ERROR; } else { return sqlsubtype; } } /* _determine_sqlsubtype_for_array */ /******************** UTILITY FUNCTIONS:END ********************/ /******************** DEBUGGING FUNCTIONS:BEGIN ********************/ #ifdef KIDB_DEBUGGERING static void dump_array_descriptor(ISC_ARRAY_DESC *desc) { int i; printf("\n--- DUMP OF ARRAY DESCRIPTOR:BEGIN ---\n"); printf("array_desc_dtype: %d\n", desc->array_desc_dtype); printf("array_desc_scale: %d\n", desc->array_desc_scale); printf("array_desc_length: %d\n", desc->array_desc_length); printf("array_desc_relation_name: %s\n", desc->array_desc_relation_name); printf("array_desc_field_name: %s\n", desc->array_desc_field_name); printf("array_desc_dimensions: %d\n", desc->array_desc_dimensions); printf("array_desc_flags: %d\n", desc->array_desc_flags); printf(" -- BOUNDS:BEGIN --\n"); for (i = 0; i < desc->array_desc_dimensions; i++) { ISC_ARRAY_BOUND bounds = (desc->array_desc_bounds)[i]; printf(" bounds set #%2d: lower: %d ; upper: %d\n", i, bounds.array_bound_lower, bounds.array_bound_upper ); } printf(" -- BOUNDS:END --\n"); printf("--- DUMP OF ARRAY DESCRIPTOR:END ---\n\n"); } #endif /* KIDB_DEBUGGERING */ /******************** DEBUGGING FUNCTIONS:END ********************/