SQLRETURN EsSQLSetDescFieldW()

in driver/handles.c [2426:2802]


SQLRETURN EsSQLSetDescFieldW(
	SQLHDESC        DescriptorHandle,
	SQLSMALLINT     RecNumber,
	SQLSMALLINT     FieldIdentifier,
	SQLPOINTER      ValuePtr,
	SQLINTEGER      BufferLength)
{
	esodbc_desc_st *desc = DSCH(DescriptorHandle);
	esodbc_state_et state;
	esodbc_rec_st *rec;
	wstr_st *wstrp;
	SQLSMALLINT *wordp;
	SQLINTEGER *intp;
	SQLSMALLINT count, type, chk_type, chk_code;
	SQLULEN ulen;
	SQLLEN slen;
	size_t wlen;

	if (! check_access(desc, FieldIdentifier, O_RDWR)) {
		/* "The SQL_DESC_DATA_PTR field of an IPD is not normally set;
		 * however, an application can do so to force a consistency check of
		 * IPD fields."
		 * TODO: the above won't work with the generic check implementation:
		 * is it worth hacking an exception here? (since IPD/.data_ptr is
		 * marked RO) */
		ERRH(desc, "field access check failed: not defined or RO for "
			"desciptor.");
		RET_HDIAGS(desc, SQL_STATE_HY091);
	}

	state = check_buff(FieldIdentifier, ValuePtr, BufferLength, FALSE);
	if (state != SQL_STATE_00000) {
		ERRH(desc, "buffer/~ length check failed (%d).", state);
		RET_HDIAGS(desc, state);
	}

	/* header fields */
	switch (FieldIdentifier) {
		case SQL_DESC_ARRAY_SIZE:
			ulen = (SQLULEN)(uintptr_t)ValuePtr;
			DBGH(desc, "setting desc array size to: %llu.", (uint64_t)ulen);
			if (DESC_TYPE_IS_RECORD(desc->type)) {
				if (ESODBC_MAX_ROW_ARRAY_SIZE < ulen) {
					WARNH(desc, "provided desc array size (%llu) larger than "
						"allowed max (%hu) -- set value adjusted to max.",
						(uint64_t)ulen, ESODBC_MAX_ROW_ARRAY_SIZE);
					desc->array_size = ESODBC_MAX_ROW_ARRAY_SIZE;
					RET_HDIAGS(desc, SQL_STATE_01S02);
				} else if (ulen < 1) {
					ERRH(desc, "can't set the array size to less than 1.");
					RET_HDIAGS(desc, SQL_STATE_HY092);
				}
			} else { /* IS_PARAMETER */
				/* no support for param arrays (yet) TODO */
				if (1 < ulen) {
					ERRH(desc, "no support for arrays of parameters.");
					RET_HDIAG(desc, SQL_STATE_HYC00,
						"Parameter arrays not implemented", 0);
				}
			}
			desc->array_size = ulen;
			return SQL_SUCCESS;

		case SQL_DESC_ARRAY_STATUS_PTR:
			DBGH(desc, "setting desc array status ptr to: 0x%p.", ValuePtr);
			/* deferred */
			desc->array_status_ptr = (SQLUSMALLINT *)ValuePtr;
			return SQL_SUCCESS;

		case SQL_DESC_BIND_OFFSET_PTR:
			DBGH(desc, "setting binding offset ptr to: 0x%p.", ValuePtr);
			/* deferred */
			desc->bind_offset_ptr = (SQLLEN *)ValuePtr;
			return SQL_SUCCESS;

		case SQL_DESC_BIND_TYPE:
			DBGH(desc, "setting bind type to: %lu.",
				(SQLUINTEGER)(uintptr_t)ValuePtr);
			desc->bind_type = (SQLUINTEGER)(uintptr_t)ValuePtr;
			return SQL_SUCCESS;

		/*
		 * "Specifies the 1-based index of the highest-numbered record that
		 * contains data."
		 * "Is not a count of all data columns or of all parameters that are
		 * bound, but the number of the highest-numbered record."
		 *
		 * "If the highest-numbered column or parameter is unbound, then
		 * SQL_DESC_COUNT is changed to the number of the next
		 * highest-numbered column or parameter. If a column or a parameter
		 * with a number that is less than the number of the highest-numbered
		 * column is unbound, SQL_DESC_COUNT is not changed. If additional
		 * columns or parameters are bound with numbers greater than the
		 * highest-numbered record that contains data, the driver
		 * automatically increases the value in the SQL_DESC_COUNT field."
		 *
		 * "If the value in SQL_DESC_COUNT is explicitly decreased, all
		 * records with numbers greater than the new value in SQL_DESC_COUNT
		 * are effectively removed. If the value in SQL_DESC_COUNT is
		 * explicitly set to 0 and the field is in an ARD, all data buffers
		 * except a bound bookmark column are released."
		 */
		case SQL_DESC_COUNT:
			return update_rec_count(desc, (SQLSMALLINT)(intptr_t)ValuePtr);

		case SQL_DESC_ROWS_PROCESSED_PTR:
			DBGH(desc, "setting desc rows processed ptr to: 0x%p.", ValuePtr);
			desc->rows_processed_ptr = (SQLULEN *)ValuePtr;
			return SQL_SUCCESS;
	}

	/*
	 * The field is a record field -> get the record to apply the field to.
	 */
	if (RecNumber < 0) { /* TODO: need to check also if AxD, as per spec?? */
		ERRH(desc, "negative record number provided (%d) with record field "
			"(%d).", RecNumber, FieldIdentifier);
		RET_HDIAG(desc, SQL_STATE_07009,
			"Negative record number provided with record field", 0);
	} else if (RecNumber == 0) {
		ERRH(desc, "unsupported record number 0."); /* TODO: bookmarks? */
		RET_HDIAG(desc, SQL_STATE_07009,
			"Unsupported record number 0", 0);
	} else { /* apparently one can set a record before the count is set */
		rec = get_record(desc, RecNumber, TRUE);
		if (! rec) {
			ERRH(desc, "can't get record with number %d.", RecNumber);
			RET_STATE(desc->hdr.diag.state);
		}
		DBGH(desc, "setting field %d of record #%d @ 0x%p.", FieldIdentifier,
			RecNumber, rec);
	}

	/*
	 * "If the application changes the data type or attributes after setting
	 * the SQL_DESC_DATA_PTR field, the driver sets SQL_DESC_DATA_PTR to a
	 * null pointer, unbinding the record."
	 *
	 * NOTE: the record can actually still be bound by the length/indicator
	 * buffer(s), so the above "binding" definition is incomplete.
	 */
	if (FieldIdentifier != SQL_DESC_DATA_PTR) {
		DBGH(desc, "attribute to set is different than data ptr (%d) => "
			"unbinding data buffer (was 0x%p).", SQL_DESC_DATA_PTR,
			rec->data_ptr);
		rec->data_ptr = NULL;
	}

	/*INDENT-OFF*/
	/* record fields */
	switch (FieldIdentifier) {
		/* "For datetime and interval data types, however, a verbose type
		 * (SQL_DATETIME or SQL_INTERVAL) is stored in SQL_DESC_TYPE, a
		 * concise type is stored in SQL_DESC_CONCISE_TYPE, and a subcode for
		 * each concise type is stored in SQL_DESC_DATETIME_INTERVAL_CODE." */
		case SQL_DESC_TYPE:
			type = (SQLSMALLINT)(intptr_t)ValuePtr;
			DBGH(desc, "setting type of rec@0x%p to %d.", rec, type);
			/* Note: SQL_[C_]DATE == SQL_DATETIME (== 9) =>
			 * 1. one needs to always use SQL_DESC_CONCISE_TYPE for setting
			 * the types from within the driver (binding cols, params):
			 * "SQL_DESC_CONCISE_TYPE can be set by a call to SQLBindCol or
			 * SQLBindParameter, or SQLSetDescField. SQL_DESC_TYPE can be set
			 * by a call to SQLSetDescField or SQLSetDescRec."
			 * 2. SQL_DESC_TYPE can only be used when setting the type record
			 * fields (.type, .concise_type, datetime_interval_code)
			 * individually. */
			if (type == SQL_DATETIME || type == SQL_INTERVAL) {
				/* "When the application sets the SQL_DESC_TYPE field, the
				 * driver checks that other fields that specify the type are
				 * valid and consistent." */
				/* setting the verbose type only */
				concise_to_type_code(rec->concise_type, &chk_type, &chk_code);
				if (chk_type != type ||
						chk_code != rec->datetime_interval_code ||
						(! rec->datetime_interval_code)) {
					ERRH(desc, "type fields found inconsistent when setting "
						"the type to %hd: concise: %hd, datetime_code: %hd.",
						(SQLSMALLINT)(intptr_t)ValuePtr,
						rec->concise_type, rec->datetime_interval_code);
					RET_HDIAGS(desc, SQL_STATE_HY021);
				} else {
					rec->type = type;
				}
				break;
			}
			/* no break! */
		case SQL_DESC_CONCISE_TYPE:
			DBGH(desc, "setting concise type of rec 0x%p to %d.", rec,
					(SQLSMALLINT)(intptr_t)ValuePtr);
			rec->concise_type = (SQLSMALLINT)(intptr_t)ValuePtr;

			concise_to_type_code(rec->concise_type, &rec->type,
					&rec->datetime_interval_code);
			rec->meta_type = concise_to_meta(rec->concise_type, desc->type);
			if (rec->meta_type == METATYPE_UNKNOWN) {
				ERRH(desc, "REC@0x%p: incorrect concise type %d for rec #%d.",
						rec, rec->concise_type, RecNumber);
				RET_HDIAGS(desc, DESC_TYPE_IS_APPLICATION(desc->type) ?
						SQL_STATE_HY003 : SQL_STATE_HY004);
			}
			/* "When the SQL_DESC_TYPE or SQL_DESC_CONCISE_TYPE field is set
			 * for some data types, the SQL_DESC_DATETIME_INTERVAL_PRECISION,
			 * SQL_DESC_LENGTH, SQL_DESC_PRECISION, and SQL_DESC_SCALE fields
			 * are automatically set to default values". */
			set_defaults_from_meta_type(rec);
			DBGH(desc, "REC@0x%p types: concise: %d, verbose: %d, code: %d.",
					rec, rec->concise_type, rec->type,
					rec->datetime_interval_code);
			break;

		case SQL_DESC_DATA_PTR:
			DBGH(desc, "setting data ptr to 0x%p of type %d.", ValuePtr,
					BufferLength);
			/* deferred */
			rec->data_ptr = ValuePtr;
			if (rec->data_ptr) {
				/* "A consistency check is performed by the driver
				 * automatically whenever an application sets the
				 * SQL_DESC_DATA_PTR field of an APD, ARD, or IPD."
				 * "The SQL_DESC_DATA_PTR field of an IPD is not normally set;
				 * however, an application can do so to force a consistency
				 * check of IPD fields. A consistency check cannot be
				 * performed on an IRD." */
				if ((desc->type != DESC_TYPE_IRD) &&
						(! consistency_check(rec))) {
					ERRH(desc, "consistency check failed on rec@0x%p.", rec);
					RET_HDIAGS(desc, SQL_STATE_HY021);
				} else {
					DBGH(desc, "rec@0x%p: bound data ptr@0x%p.", rec,
							rec->data_ptr);
				}
			} else {
				/* "If the highest-numbered column or parameter is unbound,
				 * then SQL_DESC_COUNT is changed to the number of the next
				 * highest-numbered column or parameter. " */
				if (DESC_TYPE_IS_APPLICATION(desc->type) &&
						/* see function-top comments on when to unbind */
						(! REC_IS_BOUND(rec))) {
					DBGH(desc, "rec 0x%p of desc type %d unbound.", rec,
							desc->type);
					if (RecNumber == desc->count) {
						count = count_bound(desc);
						/* worst case: trying to unbind a not-yet-bound rec */
						if (count != desc->count) {
							DBGH(desc, "adjusting rec count from %hd to %hd.",
									desc->count, count);
							return update_rec_count(desc, count);
						}
					}
				}
			}
			break;

		case SQL_DESC_NAME:
			WARNH(desc, "stored procedure params (to set to `"LWPD"`) not "
					"supported.", ValuePtr ? (SQLWCHAR *)ValuePtr : TWS_NULL);
			RET_HDIAG(desc, SQL_STATE_HYC00,
					"stored procedure params not supported", 0);

		/* <SQLWCHAR *> */
		do {
		case SQL_DESC_BASE_COLUMN_NAME: wstrp = &rec->base_column_name; break;
		case SQL_DESC_BASE_TABLE_NAME: wstrp = &rec->base_table_name; break;
		case SQL_DESC_CATALOG_NAME: wstrp = &rec->catalog_name; break;
		case SQL_DESC_LABEL: wstrp = &rec->label; break;
		/* R/O fields: literal_prefix/_suffix, local_type_name, type_name */
		case SQL_DESC_SCHEMA_NAME: wstrp = &rec->schema_name; break;
		case SQL_DESC_TABLE_NAME: wstrp = &rec->table_name; break;
		} while (0);
			if (BufferLength == SQL_NTS) {
				wlen = ValuePtr ? wcslen((SQLWCHAR *)ValuePtr) : 0;
			} else {
				wlen = BufferLength;
			}
			DBGH(desc, "setting SQLWCHAR field %d to `" LWPDL "`(@0x%p).",
				FieldIdentifier, wlen, ValuePtr, wlen, ValuePtr);
			if (wstrp->str) {
				DBGH(desc, "freeing previously allocated value for field %d "
					"(`" LWPDL "`).", FieldIdentifier, LWSTR(wstrp));
				free(wstrp->str);
				wstrp->str = NULL;
				wstrp->cnt = 0;
			}
			if (! ValuePtr) {
				DBGH(desc, "field %d reset to NULL.", FieldIdentifier);
				break;
			}
			if (! (wstrp->str = (SQLWCHAR *)malloc((wlen + /*0-term*/1)
					* sizeof(SQLWCHAR)))) {
				ERRH(desc, "failed to alloc w-string buffer of len %zd.",
					wlen + 1);
				RET_HDIAGS(desc, SQL_STATE_HY001);
			}
			memcpy(wstrp->str, ValuePtr, wlen * sizeof(SQLWCHAR));
			wstrp->str[wlen] = 0;
			wstrp->cnt = wlen;
			break;

		/* <SQLLEN *>, deferred */
		case SQL_DESC_INDICATOR_PTR:
			DBGH(desc, "setting indicator pointer to 0x%p.", ValuePtr);
			rec->indicator_ptr = (SQLLEN *)ValuePtr;
			break;
		case SQL_DESC_OCTET_LENGTH_PTR:
			DBGH(desc, "setting octet length pointer to 0x%p.", ValuePtr);
			rec->octet_length_ptr = (SQLLEN *)ValuePtr;
			break;

		/* <SQLLEN> */
		/* R/O fields: display_size */
		case SQL_DESC_OCTET_LENGTH:
			slen = (SQLLEN)ValuePtr;
			DBGH(desc, "setting octet length: %lld.", (int64_t)slen);
			/* rec field's type is signed; a negative can be dangerous */
			if (slen < 0) {
				WARNH(desc, "negative octet length provided (%lld)",
						(int64_t)slen);
				/* no eror returned: in non-str/binary, it is to be ignorred */
			}
			rec->octet_length = slen;
			break;

		/* <SQLULEN> */
		case SQL_DESC_LENGTH:
			DBGH(desc, "setting length: %llu.", (uint64_t)ValuePtr);
			rec->length = (SQLULEN)ValuePtr;
			break;

		/* <SQLSMALLINT> */
		do {
		case SQL_DESC_DATETIME_INTERVAL_CODE:
			wordp = &rec->datetime_interval_code; break;
		case SQL_DESC_PARAMETER_TYPE: wordp = &rec->parameter_type; break;
		case SQL_DESC_PRECISION: wordp = &rec->precision; break;
		case SQL_DESC_ROWVER: wordp = &rec->rowver; break;
		case SQL_DESC_SCALE: wordp = &rec->scale; break;
		case SQL_DESC_UNNAMED:
			/* only driver can set this value */
			if ((SQLSMALLINT)(intptr_t)ValuePtr == SQL_NAMED) {
				ERRH(desc, "only the driver can set %d field to 'SQL_NAMED'.",
						FieldIdentifier);
				RET_HDIAGS(desc, SQL_STATE_HY091);
			}
			wordp = &rec->unnamed;
			break;
		/* R/O field: fixed_prec_scale, nullable, searchable, unsigned  */
		case SQL_DESC_UPDATABLE: wordp = &rec->updatable; break;
		} while (0);
			DBGH(desc, "setting record field %d to %d.", FieldIdentifier,
					(SQLSMALLINT)(intptr_t)ValuePtr);
			*wordp = (SQLSMALLINT)(intptr_t)ValuePtr;
			break;

		/* <SQLINTEGER> */
		do {
		/* R/O field: auto_unique_value, case_sensitive  */
		case SQL_DESC_DATETIME_INTERVAL_PRECISION:
			intp = &rec->datetime_interval_precision;
			break;
		case SQL_DESC_NUM_PREC_RADIX:
			intp = &rec->num_prec_radix;
			break;
		} while (0);
			DBGH(desc, "returning record field %d as %d.", FieldIdentifier,
					(SQLINTEGER)(intptr_t)ValuePtr);
			*intp = (SQLINTEGER)(intptr_t)ValuePtr;
			break;

		default:
			ERRH(desc, "unknown FieldIdentifier: %d.", FieldIdentifier);
			RET_HDIAGS(desc, SQL_STATE_HY091);
	}
	/*INDENT-ON*/

	return SQL_SUCCESS;
}