|
|
|
/* dynamic SQL support routines
|
|
|
|
*
|
|
|
|
* src/interfaces/ecpg/ecpglib/descriptor.c
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define POSTGRES_ECPG_INTERNAL
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
|
|
|
|
#include "catalog/pg_type_d.h"
|
|
|
|
#include "ecpg-pthread-win32.h"
|
|
|
|
#include "ecpgerrno.h"
|
|
|
|
#include "ecpglib.h"
|
|
|
|
#include "ecpglib_extern.h"
|
|
|
|
#include "ecpgtype.h"
|
|
|
|
#include "sql3types.h"
|
|
|
|
#include "sqlca.h"
|
|
|
|
#include "sqlda.h"
|
|
|
|
|
|
|
|
static void descriptor_free(struct descriptor *desc);
|
|
|
|
|
|
|
|
/* We manage descriptors separately for each thread. */
|
|
|
|
static pthread_key_t descriptor_key;
|
|
|
|
static pthread_once_t descriptor_once = PTHREAD_ONCE_INIT;
|
|
|
|
|
|
|
|
static void descriptor_deallocate_all(struct descriptor *list);
|
|
|
|
|
|
|
|
static void
|
|
|
|
descriptor_destructor(void *arg)
|
|
|
|
{
|
|
|
|
descriptor_deallocate_all(arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
descriptor_key_init(void)
|
|
|
|
{
|
|
|
|
pthread_key_create(&descriptor_key, descriptor_destructor);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct descriptor *
|
|
|
|
get_descriptors(void)
|
|
|
|
{
|
|
|
|
pthread_once(&descriptor_once, descriptor_key_init);
|
|
|
|
return (struct descriptor *) pthread_getspecific(descriptor_key);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
set_descriptors(struct descriptor *value)
|
|
|
|
{
|
|
|
|
pthread_setspecific(descriptor_key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* old internal convenience function that might go away later */
|
|
|
|
static PGresult *
|
|
|
|
ecpg_result_by_descriptor(int line, const char *name)
|
|
|
|
{
|
|
|
|
struct descriptor *desc = ecpg_find_desc(line, name);
|
|
|
|
|
|
|
|
if (desc == NULL)
|
|
|
|
return NULL;
|
|
|
|
return desc->result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int
|
|
|
|
ecpg_dynamic_type_DDT(Oid type)
|
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case DATEOID:
|
|
|
|
return SQL3_DDT_DATE;
|
|
|
|
case TIMEOID:
|
|
|
|
return SQL3_DDT_TIME;
|
|
|
|
case TIMESTAMPOID:
|
|
|
|
return SQL3_DDT_TIMESTAMP;
|
|
|
|
case TIMESTAMPTZOID:
|
|
|
|
return SQL3_DDT_TIMESTAMP_WITH_TIME_ZONE;
|
|
|
|
case TIMETZOID:
|
|
|
|
return SQL3_DDT_TIME_WITH_TIME_ZONE;
|
|
|
|
default:
|
|
|
|
return SQL3_DDT_ILLEGAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ECPGget_desc_header(int lineno, const char *desc_name, int *count)
|
|
|
|
{
|
|
|
|
PGresult *ECPGresult;
|
|
|
|
struct sqlca_t *sqlca = ECPGget_sqlca();
|
|
|
|
|
|
|
|
if (sqlca == NULL)
|
|
|
|
{
|
|
|
|
ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
|
|
|
|
ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_init_sqlca(sqlca);
|
|
|
|
ECPGresult = ecpg_result_by_descriptor(lineno, desc_name);
|
|
|
|
if (!ECPGresult)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
*count = PQnfields(ECPGresult);
|
|
|
|
sqlca->sqlerrd[2] = 1;
|
|
|
|
ecpg_log("ECPGget_desc_header: found %d attributes\n", *count);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
get_int_item(int lineno, void *var, enum ECPGttype vartype, int value)
|
|
|
|
{
|
|
|
|
switch (vartype)
|
|
|
|
{
|
|
|
|
case ECPGt_short:
|
|
|
|
*(short *) var = (short) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_int:
|
|
|
|
*(int *) var = (int) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_long:
|
|
|
|
*(long *) var = (long) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_short:
|
|
|
|
*(unsigned short *) var = (unsigned short) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_int:
|
|
|
|
*(unsigned int *) var = (unsigned int) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_long:
|
|
|
|
*(unsigned long *) var = (unsigned long) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_long_long:
|
|
|
|
*(long long int *) var = (long long int) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_long_long:
|
|
|
|
*(unsigned long long int *) var = (unsigned long long int) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_float:
|
|
|
|
*(float *) var = (float) value;
|
|
|
|
break;
|
|
|
|
case ECPGt_double:
|
|
|
|
*(double *) var = (double) value;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ecpg_raise(lineno, ECPG_VAR_NOT_NUMERIC, ECPG_SQLSTATE_RESTRICTED_DATA_TYPE_ATTRIBUTE_VIOLATION, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
set_int_item(int lineno, int *target, const void *var, enum ECPGttype vartype)
|
|
|
|
{
|
|
|
|
switch (vartype)
|
|
|
|
{
|
|
|
|
case ECPGt_short:
|
|
|
|
*target = *(const short *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_int:
|
|
|
|
*target = *(const int *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_long:
|
|
|
|
*target = *(const long *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_short:
|
|
|
|
*target = *(const unsigned short *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_int:
|
|
|
|
*target = *(const unsigned int *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_long:
|
|
|
|
*target = *(const unsigned long *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_long_long:
|
|
|
|
*target = *(const long long int *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_unsigned_long_long:
|
|
|
|
*target = *(const unsigned long long int *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_float:
|
|
|
|
*target = *(const float *) var;
|
|
|
|
break;
|
|
|
|
case ECPGt_double:
|
|
|
|
*target = *(const double *) var;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ecpg_raise(lineno, ECPG_VAR_NOT_NUMERIC, ECPG_SQLSTATE_RESTRICTED_DATA_TYPE_ATTRIBUTE_VIOLATION, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
get_char_item(int lineno, void *var, enum ECPGttype vartype, char *value, int varcharsize)
|
|
|
|
{
|
|
|
|
switch (vartype)
|
|
|
|
{
|
|
|
|
case ECPGt_char:
|
|
|
|
case ECPGt_unsigned_char:
|
|
|
|
case ECPGt_string:
|
|
|
|
strncpy(var, value, varcharsize);
|
|
|
|
break;
|
|
|
|
case ECPGt_varchar:
|
|
|
|
{
|
|
|
|
struct ECPGgeneric_varchar *variable =
|
|
|
|
(struct ECPGgeneric_varchar *) var;
|
|
|
|
|
|
|
|
if (varcharsize == 0)
|
|
|
|
memcpy(variable->arr, value, strlen(value));
|
|
|
|
else
|
|
|
|
strncpy(variable->arr, value, varcharsize);
|
|
|
|
|
|
|
|
variable->len = strlen(value);
|
|
|
|
if (varcharsize > 0 && variable->len > varcharsize)
|
|
|
|
variable->len = varcharsize;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ecpg_raise(lineno, ECPG_VAR_NOT_CHAR, ECPG_SQLSTATE_RESTRICTED_DATA_TYPE_ATTRIBUTE_VIOLATION, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define RETURN_IF_NO_DATA if (ntuples < 1) \
|
|
|
|
{ \
|
|
|
|
va_end(args); \
|
|
|
|
ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL); \
|
|
|
|
return false; \
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ECPGget_desc(int lineno, const char *desc_name, int index,...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
PGresult *ECPGresult;
|
|
|
|
enum ECPGdtype type;
|
|
|
|
int ntuples,
|
|
|
|
act_tuple;
|
|
|
|
struct variable data_var;
|
|
|
|
struct sqlca_t *sqlca = ECPGget_sqlca();
|
|
|
|
|
|
|
|
if (sqlca == NULL)
|
|
|
|
{
|
|
|
|
ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
|
|
|
|
ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
va_start(args, index);
|
|
|
|
ecpg_init_sqlca(sqlca);
|
|
|
|
ECPGresult = ecpg_result_by_descriptor(lineno, desc_name);
|
|
|
|
if (!ECPGresult)
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ntuples = PQntuples(ECPGresult);
|
|
|
|
|
|
|
|
if (index < 1 || index > PQnfields(ECPGresult))
|
|
|
|
{
|
|
|
|
ecpg_raise(lineno, ECPG_INVALID_DESCRIPTOR_INDEX, ECPG_SQLSTATE_INVALID_DESCRIPTOR_INDEX, NULL);
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: reading items for tuple %d\n", index);
|
|
|
|
--index;
|
|
|
|
|
|
|
|
type = va_arg(args, enum ECPGdtype);
|
|
|
|
|
|
|
|
memset(&data_var, 0, sizeof data_var);
|
|
|
|
data_var.type = ECPGt_EORT;
|
|
|
|
data_var.ind_type = ECPGt_NO_INDICATOR;
|
|
|
|
|
|
|
|
while (type != ECPGd_EODT)
|
|
|
|
{
|
|
|
|
char type_str[20];
|
|
|
|
long varcharsize;
|
|
|
|
long offset;
|
|
|
|
long arrsize;
|
|
|
|
enum ECPGttype vartype;
|
|
|
|
void *var;
|
|
|
|
|
|
|
|
vartype = va_arg(args, enum ECPGttype);
|
|
|
|
var = va_arg(args, void *);
|
|
|
|
varcharsize = va_arg(args, long);
|
|
|
|
arrsize = va_arg(args, long);
|
|
|
|
offset = va_arg(args, long);
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case (ECPGd_indicator):
|
|
|
|
RETURN_IF_NO_DATA;
|
|
|
|
data_var.ind_type = vartype;
|
|
|
|
data_var.ind_pointer = var;
|
|
|
|
data_var.ind_varcharsize = varcharsize;
|
|
|
|
data_var.ind_arrsize = arrsize;
|
|
|
|
data_var.ind_offset = offset;
|
|
|
|
if (data_var.ind_arrsize == 0 || data_var.ind_varcharsize == 0)
|
|
|
|
data_var.ind_value = *((void **) (data_var.ind_pointer));
|
|
|
|
else
|
|
|
|
data_var.ind_value = data_var.ind_pointer;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_data:
|
|
|
|
RETURN_IF_NO_DATA;
|
|
|
|
data_var.type = vartype;
|
|
|
|
data_var.pointer = var;
|
|
|
|
data_var.varcharsize = varcharsize;
|
|
|
|
data_var.arrsize = arrsize;
|
|
|
|
data_var.offset = offset;
|
|
|
|
if (data_var.arrsize == 0 || data_var.varcharsize == 0)
|
|
|
|
data_var.value = *((void **) (data_var.pointer));
|
|
|
|
else
|
|
|
|
data_var.value = data_var.pointer;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_name:
|
|
|
|
if (!get_char_item(lineno, var, vartype, PQfname(ECPGresult, index), varcharsize))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: NAME = %s\n", PQfname(ECPGresult, index));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_nullable:
|
|
|
|
if (!get_int_item(lineno, var, vartype, 1))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_key_member:
|
|
|
|
if (!get_int_item(lineno, var, vartype, 0))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_scale:
|
|
|
|
if (!get_int_item(lineno, var, vartype, (PQfmod(ECPGresult, index) - VARHDRSZ) & 0xffff))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: SCALE = %d\n", (PQfmod(ECPGresult, index) - VARHDRSZ) & 0xffff);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_precision:
|
|
|
|
if (!get_int_item(lineno, var, vartype, PQfmod(ECPGresult, index) >> 16))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: PRECISION = %d\n", PQfmod(ECPGresult, index) >> 16);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_octet:
|
|
|
|
if (!get_int_item(lineno, var, vartype, PQfsize(ECPGresult, index)))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: OCTET_LENGTH = %d\n", PQfsize(ECPGresult, index));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_length:
|
|
|
|
if (!get_int_item(lineno, var, vartype, PQfmod(ECPGresult, index) - VARHDRSZ))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: LENGTH = %d\n", PQfmod(ECPGresult, index) - VARHDRSZ);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_type:
|
|
|
|
if (!get_int_item(lineno, var, vartype, ecpg_dynamic_type(PQftype(ECPGresult, index))))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: TYPE = %d\n", ecpg_dynamic_type(PQftype(ECPGresult, index)));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_di_code:
|
|
|
|
if (!get_int_item(lineno, var, vartype, ecpg_dynamic_type_DDT(PQftype(ECPGresult, index))))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: TYPE = %d\n", ecpg_dynamic_type_DDT(PQftype(ECPGresult, index)));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_cardinality:
|
|
|
|
if (!get_int_item(lineno, var, vartype, PQntuples(ECPGresult)))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_log("ECPGget_desc: CARDINALITY = %d\n", PQntuples(ECPGresult));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_ret_length:
|
|
|
|
case ECPGd_ret_octet:
|
|
|
|
|
|
|
|
RETURN_IF_NO_DATA;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this is like ECPGstore_result
|
|
|
|
*/
|
|
|
|
if (arrsize > 0 && ntuples > arrsize)
|
|
|
|
{
|
|
|
|
ecpg_log("ECPGget_desc on line %d: incorrect number of matches; %d don't fit into array of %ld\n",
|
|
|
|
lineno, ntuples, arrsize);
|
|
|
|
ecpg_raise(lineno, ECPG_TOO_MANY_MATCHES, ECPG_SQLSTATE_CARDINALITY_VIOLATION, NULL);
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/* allocate storage if needed */
|
|
|
|
if (arrsize == 0 && *(void **) var == NULL)
|
|
|
|
{
|
|
|
|
void *mem = ecpg_auto_alloc(offset * ntuples, lineno);
|
|
|
|
|
|
|
|
if (!mem)
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*(void **) var = mem;
|
|
|
|
var = mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
|
|
|
|
{
|
|
|
|
if (!get_int_item(lineno, var, vartype, PQgetlength(ECPGresult, act_tuple, index)))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
var = (char *) var + offset;
|
|
|
|
ecpg_log("ECPGget_desc: RETURNED[%d] = %d\n", act_tuple, PQgetlength(ECPGresult, act_tuple, index));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
snprintf(type_str, sizeof(type_str), "%d", type);
|
|
|
|
ecpg_raise(lineno, ECPG_UNKNOWN_DESCRIPTOR_ITEM, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, type_str);
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
type = va_arg(args, enum ECPGdtype);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data_var.type != ECPGt_EORT)
|
|
|
|
{
|
|
|
|
struct statement stmt;
|
Avoid thread-safety problem in ecpglib.
ecpglib attempts to force the LC_NUMERIC locale to "C" while reading
server output, to avoid problems with strtod() and related functions.
Historically it's just issued setlocale() calls to do that, but that
has major problems if we're in a threaded application. setlocale()
itself is not required by POSIX to be thread-safe (and indeed is not,
on recent OpenBSD). Moreover, its effects are process-wide, so that
we could cause unexpected results in other threads, or another thread
could change our setting.
On platforms having uselocale(), which is required by POSIX:2008,
we can avoid these problems by using uselocale() instead. Windows
goes its own way as usual, but we can make it safe by using
_configthreadlocale(). Platforms having neither continue to use the
old code, but that should be pretty much nobody among current systems.
This should get back-patched, but let's see what the buildfarm
thinks of it first.
Michael Meskes and Tom Lane; thanks also to Takayuki Tsunakawa.
Discussion: https://postgr.es/m/31420.1547783697@sss.pgh.pa.us
7 years ago
|
|
|
|
|
|
|
memset(&stmt, 0, sizeof stmt);
|
|
|
|
stmt.lineno = lineno;
|
|
|
|
|
|
|
|
/* Make sure we do NOT honor the locale for numeric input */
|
|
|
|
/* since the database gives the standard decimal point */
|
|
|
|
/* (see comments in execute.c) */
|
|
|
|
#ifdef HAVE_USELOCALE
|
|
|
|
|
|
|
|
/*
|
|
|
|
* To get here, the above PQnfields() test must have found nonzero
|
|
|
|
* fields. One needs a connection to create such a descriptor. (EXEC
|
|
|
|
* SQL SET DESCRIPTOR can populate the descriptor's "items", but it
|
|
|
|
* can't change the descriptor's PQnfields().) Any successful
|
|
|
|
* connection initializes ecpg_clocale.
|
|
|
|
*/
|
|
|
|
Assert(ecpg_clocale);
|
|
|
|
stmt.oldlocale = uselocale(ecpg_clocale);
|
|
|
|
#else
|
|
|
|
#ifdef WIN32
|
|
|
|
stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
|
|
|
#endif
|
|
|
|
stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
|
|
|
|
setlocale(LC_NUMERIC, "C");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* desperate try to guess something sensible */
|
|
|
|
stmt.connection = ecpg_get_connection(NULL);
|
|
|
|
ecpg_store_result(ECPGresult, index, &stmt, &data_var);
|
|
|
|
|
|
|
|
#ifdef HAVE_USELOCALE
|
|
|
|
if (stmt.oldlocale != (locale_t) 0)
|
|
|
|
uselocale(stmt.oldlocale);
|
|
|
|
#else
|
|
|
|
if (stmt.oldlocale)
|
|
|
|
{
|
|
|
|
setlocale(LC_NUMERIC, stmt.oldlocale);
|
|
|
|
ecpg_free(stmt.oldlocale);
|
|
|
|
}
|
|
|
|
#ifdef WIN32
|
|
|
|
if (stmt.oldthreadlocale != -1)
|
|
|
|
_configthreadlocale(stmt.oldthreadlocale);
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ind_type != NO_INDICATOR should always have ind_pointer != NULL but
|
|
|
|
* since this might be changed manually in the .c file let's play it
|
|
|
|
* safe
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* this is like ECPGstore_result but since we don't have a data
|
|
|
|
* variable at hand, we can't call it
|
|
|
|
*/
|
|
|
|
if (data_var.ind_arrsize > 0 && ntuples > data_var.ind_arrsize)
|
|
|
|
{
|
|
|
|
ecpg_log("ECPGget_desc on line %d: incorrect number of matches (indicator); %d don't fit into array of %ld\n",
|
|
|
|
lineno, ntuples, data_var.ind_arrsize);
|
|
|
|
ecpg_raise(lineno, ECPG_TOO_MANY_MATCHES, ECPG_SQLSTATE_CARDINALITY_VIOLATION, NULL);
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate storage if needed */
|
|
|
|
if (data_var.ind_arrsize == 0 && data_var.ind_value == NULL)
|
|
|
|
{
|
|
|
|
void *mem = ecpg_auto_alloc(data_var.ind_offset * ntuples, lineno);
|
|
|
|
|
|
|
|
if (!mem)
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*(void **) data_var.ind_pointer = mem;
|
|
|
|
data_var.ind_value = mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (act_tuple = 0; act_tuple < ntuples; act_tuple++)
|
|
|
|
{
|
|
|
|
if (!get_int_item(lineno, data_var.ind_value, data_var.ind_type, -PQgetisnull(ECPGresult, act_tuple, index)))
|
|
|
|
{
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
data_var.ind_value = (char *) data_var.ind_value + data_var.ind_offset;
|
|
|
|
ecpg_log("ECPGget_desc: INDICATOR[%d] = %d\n", act_tuple, -PQgetisnull(ECPGresult, act_tuple, index));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sqlca->sqlerrd[2] = ntuples;
|
|
|
|
va_end(args);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef RETURN_IF_NO_DATA
|
|
|
|
|
|
|
|
bool
|
|
|
|
ECPGset_desc_header(int lineno, const char *desc_name, int count)
|
|
|
|
{
|
|
|
|
struct descriptor *desc = ecpg_find_desc(lineno, desc_name);
|
|
|
|
|
|
|
|
if (desc == NULL)
|
|
|
|
return false;
|
|
|
|
desc->count = count;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
set_desc_attr(struct descriptor_item *desc_item, struct variable *var,
|
|
|
|
char *tobeinserted)
|
|
|
|
{
|
|
|
|
if (var->type != ECPGt_bytea)
|
|
|
|
desc_item->is_binary = false;
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
struct ECPGgeneric_bytea *variable =
|
|
|
|
(struct ECPGgeneric_bytea *) (var->value);
|
|
|
|
|
|
|
|
desc_item->is_binary = true;
|
|
|
|
desc_item->data_len = variable->len;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_free(desc_item->data); /* free() takes care of a potential NULL value */
|
|
|
|
desc_item->data = tobeinserted;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
ECPGset_desc(int lineno, const char *desc_name, int index,...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
struct descriptor *desc;
|
|
|
|
struct descriptor_item *desc_item;
|
|
|
|
struct variable *var;
|
|
|
|
|
|
|
|
desc = ecpg_find_desc(lineno, desc_name);
|
|
|
|
if (desc == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (desc_item = desc->items; desc_item; desc_item = desc_item->next)
|
|
|
|
{
|
|
|
|
if (desc_item->num == index)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (desc_item == NULL)
|
|
|
|
{
|
|
|
|
desc_item = (struct descriptor_item *) ecpg_alloc(sizeof(*desc_item), lineno);
|
|
|
|
if (!desc_item)
|
|
|
|
return false;
|
|
|
|
desc_item->num = index;
|
|
|
|
if (desc->count < index)
|
|
|
|
desc->count = index;
|
|
|
|
desc_item->next = desc->items;
|
|
|
|
desc->items = desc_item;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(var = (struct variable *) ecpg_alloc(sizeof(struct variable), lineno)))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
va_start(args, index);
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
enum ECPGdtype itemtype;
|
|
|
|
char *tobeinserted = NULL;
|
|
|
|
|
|
|
|
itemtype = va_arg(args, enum ECPGdtype);
|
|
|
|
|
|
|
|
if (itemtype == ECPGd_EODT)
|
|
|
|
break;
|
|
|
|
|
|
|
|
var->type = va_arg(args, enum ECPGttype);
|
|
|
|
var->pointer = va_arg(args, char *);
|
|
|
|
|
|
|
|
var->varcharsize = va_arg(args, long);
|
|
|
|
var->arrsize = va_arg(args, long);
|
|
|
|
var->offset = va_arg(args, long);
|
|
|
|
|
|
|
|
if (var->arrsize == 0 || var->varcharsize == 0)
|
|
|
|
var->value = *((char **) (var->pointer));
|
|
|
|
else
|
|
|
|
var->value = var->pointer;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* negative values are used to indicate an array without given bounds
|
|
|
|
*/
|
|
|
|
/* reset to zero for us */
|
|
|
|
if (var->arrsize < 0)
|
|
|
|
var->arrsize = 0;
|
|
|
|
if (var->varcharsize < 0)
|
|
|
|
var->varcharsize = 0;
|
|
|
|
|
|
|
|
var->next = NULL;
|
|
|
|
|
|
|
|
switch (itemtype)
|
|
|
|
{
|
|
|
|
case ECPGd_data:
|
|
|
|
{
|
|
|
|
if (!ecpg_store_input(lineno, true, var, &tobeinserted, false))
|
|
|
|
{
|
|
|
|
ecpg_free(var);
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_desc_attr(desc_item, var, tobeinserted);
|
|
|
|
tobeinserted = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ECPGd_indicator:
|
|
|
|
set_int_item(lineno, &desc_item->indicator, var->pointer, var->type);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_length:
|
|
|
|
set_int_item(lineno, &desc_item->length, var->pointer, var->type);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_precision:
|
|
|
|
set_int_item(lineno, &desc_item->precision, var->pointer, var->type);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_scale:
|
|
|
|
set_int_item(lineno, &desc_item->scale, var->pointer, var->type);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ECPGd_type:
|
|
|
|
set_int_item(lineno, &desc_item->type, var->pointer, var->type);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
char type_str[20];
|
|
|
|
|
|
|
|
snprintf(type_str, sizeof(type_str), "%d", itemtype);
|
|
|
|
ecpg_raise(lineno, ECPG_UNKNOWN_DESCRIPTOR_ITEM, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, type_str);
|
|
|
|
ecpg_free(var);
|
|
|
|
va_end(args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ecpg_free(var);
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free the descriptor and items in it. */
|
|
|
|
static void
|
|
|
|
descriptor_free(struct descriptor *desc)
|
|
|
|
{
|
|
|
|
struct descriptor_item *desc_item;
|
|
|
|
|
|
|
|
for (desc_item = desc->items; desc_item;)
|
|
|
|
{
|
|
|
|
struct descriptor_item *di;
|
|
|
|
|
|
|
|
ecpg_free(desc_item->data);
|
|
|
|
di = desc_item;
|
|
|
|
desc_item = desc_item->next;
|
|
|
|
ecpg_free(di);
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_free(desc->name);
|
|
|
|
PQclear(desc->result);
|
|
|
|
ecpg_free(desc);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ECPGdeallocate_desc(int line, const char *name)
|
|
|
|
{
|
|
|
|
struct descriptor *desc;
|
|
|
|
struct descriptor *prev;
|
|
|
|
struct sqlca_t *sqlca = ECPGget_sqlca();
|
|
|
|
|
|
|
|
if (sqlca == NULL)
|
|
|
|
{
|
|
|
|
ecpg_raise(line, ECPG_OUT_OF_MEMORY,
|
|
|
|
ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_init_sqlca(sqlca);
|
|
|
|
for (desc = get_descriptors(), prev = NULL; desc; prev = desc, desc = desc->next)
|
|
|
|
{
|
|
|
|
if (strcmp(name, desc->name) == 0)
|
|
|
|
{
|
|
|
|
if (prev)
|
|
|
|
prev->next = desc->next;
|
|
|
|
else
|
|
|
|
set_descriptors(desc->next);
|
|
|
|
descriptor_free(desc);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ecpg_raise(line, ECPG_UNKNOWN_DESCRIPTOR, ECPG_SQLSTATE_INVALID_SQL_DESCRIPTOR_NAME, name);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Deallocate all descriptors in the list */
|
|
|
|
static void
|
|
|
|
descriptor_deallocate_all(struct descriptor *list)
|
|
|
|
{
|
|
|
|
while (list)
|
|
|
|
{
|
|
|
|
struct descriptor *next = list->next;
|
|
|
|
|
|
|
|
descriptor_free(list);
|
|
|
|
list = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ECPGallocate_desc(int line, const char *name)
|
|
|
|
{
|
|
|
|
struct descriptor *new;
|
|
|
|
struct sqlca_t *sqlca = ECPGget_sqlca();
|
|
|
|
|
|
|
|
if (sqlca == NULL)
|
|
|
|
{
|
|
|
|
ecpg_raise(line, ECPG_OUT_OF_MEMORY,
|
|
|
|
ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_init_sqlca(sqlca);
|
|
|
|
new = (struct descriptor *) ecpg_alloc(sizeof(struct descriptor), line);
|
|
|
|
if (!new)
|
|
|
|
return false;
|
|
|
|
new->next = get_descriptors();
|
|
|
|
new->name = ecpg_alloc(strlen(name) + 1, line);
|
|
|
|
if (!new->name)
|
|
|
|
{
|
|
|
|
ecpg_free(new);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
new->count = -1;
|
|
|
|
new->items = NULL;
|
|
|
|
new->result = PQmakeEmptyPGresult(NULL, 0);
|
|
|
|
if (!new->result)
|
|
|
|
{
|
|
|
|
ecpg_free(new->name);
|
|
|
|
ecpg_free(new);
|
|
|
|
ecpg_raise(line, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
strcpy(new->name, name);
|
|
|
|
set_descriptors(new);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find descriptor with name in the connection. */
|
|
|
|
struct descriptor *
|
|
|
|
ecpg_find_desc(int line, const char *name)
|
|
|
|
{
|
|
|
|
struct descriptor *desc;
|
|
|
|
|
|
|
|
for (desc = get_descriptors(); desc; desc = desc->next)
|
|
|
|
{
|
|
|
|
if (strcmp(name, desc->name) == 0)
|
|
|
|
return desc;
|
|
|
|
}
|
|
|
|
|
|
|
|
ecpg_raise(line, ECPG_UNKNOWN_DESCRIPTOR, ECPG_SQLSTATE_INVALID_SQL_DESCRIPTOR_NAME, name);
|
|
|
|
return NULL; /* not found */
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ECPGdescribe(int line, int compat, bool input, const char *connection_name, const char *stmt_name,...)
|
|
|
|
{
|
|
|
|
bool ret = false;
|
|
|
|
struct connection *con;
|
|
|
|
struct prepared_statement *prep;
|
|
|
|
PGresult *res;
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
/* DESCRIBE INPUT is not yet supported */
|
|
|
|
if (input)
|
|
|
|
{
|
|
|
|
ecpg_raise(line, ECPG_UNSUPPORTED, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, "DESCRIBE INPUT");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
con = ecpg_get_connection(connection_name);
|
|
|
|
if (!con)
|
|
|
|
{
|
|
|
|
ecpg_raise(line, ECPG_NO_CONN, ECPG_SQLSTATE_CONNECTION_DOES_NOT_EXIST,
|
|
|
|
connection_name ? connection_name : ecpg_gettext("NULL"));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
prep = ecpg_find_prepared_statement(stmt_name, con, NULL);
|
|
|
|
if (!prep)
|
|
|
|
{
|
|
|
|
ecpg_raise(line, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, stmt_name);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
va_start(args, stmt_name);
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
enum ECPGttype type;
|
|
|
|
void *ptr;
|
|
|
|
|
|
|
|
/* variable type */
|
|
|
|
type = va_arg(args, enum ECPGttype);
|
|
|
|
|
|
|
|
if (type == ECPGt_EORT)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* rest of variable parameters */
|
|
|
|
ptr = va_arg(args, void *);
|
Phase 2 of pgindent updates.
Change pg_bsd_indent to follow upstream rules for placement of comments
to the right of code, and remove pgindent hack that caused comments
following #endif to not obey the general rule.
Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using
the published version of pg_bsd_indent, but a hacked-up version that
tried to minimize the amount of movement of comments to the right of
code. The situation of interest is where such a comment has to be
moved to the right of its default placement at column 33 because there's
code there. BSD indent has always moved right in units of tab stops
in such cases --- but in the previous incarnation, indent was working
in 8-space tab stops, while now it knows we use 4-space tabs. So the
net result is that in about half the cases, such comments are placed
one tab stop left of before. This is better all around: it leaves
more room on the line for comment text, and it means that in such
cases the comment uniformly starts at the next 4-space tab stop after
the code, rather than sometimes one and sometimes two tabs after.
Also, ensure that comments following #endif are indented the same
as comments following other preprocessor commands such as #else.
That inconsistency turns out to have been self-inflicted damage
from a poorly-thought-through post-indent "fixup" in pgindent.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
8 years ago
|
|
|
(void) va_arg(args, long); /* skip args */
|
|
|
|
(void) va_arg(args, long);
|
|
|
|
(void) va_arg(args, long);
|
|
|
|
|
|
|
|
/* variable indicator */
|
|
|
|
(void) va_arg(args, enum ECPGttype);
|
|
|
|
(void) va_arg(args, void *); /* skip args */
|
|
|
|
(void) va_arg(args, long);
|
|
|
|
(void) va_arg(args, long);
|
|
|
|
(void) va_arg(args, long);
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case ECPGt_descriptor:
|
|
|
|
{
|
|
|
|
char *name = ptr;
|
|
|
|
struct descriptor *desc = ecpg_find_desc(line, name);
|
|
|
|
|
|
|
|
if (desc == NULL)
|
|
|
|
break;
|
|
|
|
|
|
|
|
res = PQdescribePrepared(con->connection, stmt_name);
|
|
|
|
if (!ecpg_check_PQresult(res, line, con->connection, compat))
|
|
|
|
break;
|
|
|
|
|
|
|
|
PQclear(desc->result);
|
|
|
|
|
|
|
|
desc->result = res;
|
|
|
|
ret = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ECPGt_sqlda:
|
|
|
|
{
|
|
|
|
if (INFORMIX_MODE(compat))
|
|
|
|
{
|
|
|
|
struct sqlda_compat **_sqlda = ptr;
|
|
|
|
struct sqlda_compat *sqlda;
|
|
|
|
|
|
|
|
res = PQdescribePrepared(con->connection, stmt_name);
|
|
|
|
if (!ecpg_check_PQresult(res, line, con->connection, compat))
|
|
|
|
break;
|
|
|
|
|
|
|
|
sqlda = ecpg_build_compat_sqlda(line, res, -1, compat);
|
|
|
|
if (sqlda)
|
|
|
|
{
|
|
|
|
struct sqlda_compat *sqlda_old = *_sqlda;
|
|
|
|
struct sqlda_compat *sqlda_old1;
|
|
|
|
|
|
|
|
while (sqlda_old)
|
|
|
|
{
|
|
|
|
sqlda_old1 = sqlda_old->desc_next;
|
|
|
|
free(sqlda_old);
|
|
|
|
sqlda_old = sqlda_old1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*_sqlda = sqlda;
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
PQclear(res);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
struct sqlda_struct **_sqlda = ptr;
|
|
|
|
struct sqlda_struct *sqlda;
|
|
|
|
|
|
|
|
res = PQdescribePrepared(con->connection, stmt_name);
|
|
|
|
if (!ecpg_check_PQresult(res, line, con->connection, compat))
|
|
|
|
break;
|
|
|
|
|
|
|
|
sqlda = ecpg_build_native_sqlda(line, res, -1, compat);
|
|
|
|
if (sqlda)
|
|
|
|
{
|
|
|
|
struct sqlda_struct *sqlda_old = *_sqlda;
|
|
|
|
struct sqlda_struct *sqlda_old1;
|
|
|
|
|
|
|
|
while (sqlda_old)
|
|
|
|
{
|
|
|
|
sqlda_old1 = sqlda_old->desc_next;
|
|
|
|
free(sqlda_old);
|
|
|
|
sqlda_old = sqlda_old1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*_sqlda = sqlda;
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
PQclear(res);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
/* nothing else may come */
|
|
|
|
;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|