1998-06-03 22:33:45 +02:00
|
|
|
|
|
|
|
/* Module: qresult.c
|
|
|
|
*
|
|
|
|
* Description: This module contains functions related to
|
|
|
|
* managing result information (i.e, fetching rows from the backend,
|
|
|
|
* managing the tuple cache, etc.) and retrieving it.
|
|
|
|
* Depending on the situation, a QResultClass will hold either data
|
|
|
|
* from the backend or a manually built result (see "qresult.h" to
|
|
|
|
* see which functions/macros are for manual or backend results.
|
|
|
|
* For manually built results, the QResultClass simply points to
|
|
|
|
* TupleList and ColumnInfo structures, which actually hold the data.
|
|
|
|
*
|
|
|
|
* Classes: QResultClass (Functions prefix: "QR_")
|
|
|
|
*
|
|
|
|
* API functions: none
|
|
|
|
*
|
|
|
|
* Comments: See "notice.txt" for copyright and license information.
|
|
|
|
*
|
|
|
|
*/
|
1998-04-13 17:02:05 +02:00
|
|
|
|
|
|
|
#include "qresult.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include <stdio.h>
|
1998-07-23 01:47:48 +02:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#ifndef TRUE
|
|
|
|
#define TRUE (BOOL)1
|
|
|
|
#endif
|
|
|
|
#ifndef FALSE
|
|
|
|
#define FALSE (BOOL)0
|
|
|
|
#endif
|
1998-04-13 17:02:05 +02:00
|
|
|
|
|
|
|
extern GLOBAL_VALUES globals;
|
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
/* Used for building a Manual Result only */
|
1998-04-13 17:02:05 +02:00
|
|
|
/* All info functions call this function to create the manual result set. */
|
|
|
|
void
|
|
|
|
QR_set_num_fields(QResultClass *self, int new_num_fields)
|
|
|
|
{
|
|
|
|
mylog("in QR_set_num_fields\n");
|
|
|
|
|
|
|
|
CI_set_num_fields(self->fields, new_num_fields);
|
|
|
|
if(self->manual_tuples)
|
|
|
|
TL_Destructor(self->manual_tuples);
|
|
|
|
|
|
|
|
self->manual_tuples = TL_Constructor(new_num_fields);
|
|
|
|
|
|
|
|
mylog("exit QR_set_num_fields\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************/
|
|
|
|
/* CLASS QResult */
|
|
|
|
/************************************/
|
|
|
|
|
|
|
|
QResultClass *
|
|
|
|
QR_Constructor()
|
|
|
|
{
|
|
|
|
QResultClass *rv;
|
|
|
|
|
|
|
|
mylog("in QR_Constructor\n");
|
|
|
|
rv = (QResultClass *) malloc(sizeof(QResultClass));
|
|
|
|
|
|
|
|
if (rv != NULL) {
|
|
|
|
rv->status = PGRES_EMPTY_QUERY;
|
|
|
|
|
|
|
|
/* construct the column info */
|
|
|
|
if ( ! (rv->fields = CI_Constructor())) {
|
|
|
|
free(rv);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
rv->manual_tuples = NULL;
|
|
|
|
rv->backend_tuples = NULL;
|
|
|
|
rv->message = NULL;
|
|
|
|
rv->command = NULL;
|
|
|
|
rv->notice = NULL;
|
|
|
|
rv->conn = NULL;
|
|
|
|
rv->inTuples = FALSE;
|
|
|
|
rv->fcount = 0;
|
|
|
|
rv->fetch_count = 0;
|
|
|
|
rv->num_fields = 0;
|
|
|
|
rv->tupleField = NULL;
|
|
|
|
rv->cursor = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mylog("exit QR_Constructor\n");
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
QR_Destructor(QResultClass *self)
|
|
|
|
{
|
|
|
|
mylog("QResult: in DESTRUCTOR\n");
|
|
|
|
|
|
|
|
/* manual result set tuples */
|
1998-06-03 22:33:45 +02:00
|
|
|
if (self->manual_tuples)
|
1998-04-13 17:02:05 +02:00
|
|
|
TL_Destructor(self->manual_tuples);
|
|
|
|
|
|
|
|
// If conn is defined, then we may have used "backend_tuples",
|
|
|
|
// so in case we need to, free it up. Also, close the cursor.
|
|
|
|
if (self->conn && self->conn->sock && CC_is_in_trans(self->conn))
|
|
|
|
QR_close(self); // close the cursor if there is one
|
|
|
|
|
|
|
|
QR_free_memory(self); // safe to call anyway
|
|
|
|
|
|
|
|
// Should have been freed in the close() but just in case...
|
|
|
|
if (self->cursor)
|
|
|
|
free(self->cursor);
|
|
|
|
|
|
|
|
/* Free up column info */
|
|
|
|
if (self->fields)
|
|
|
|
CI_Destructor(self->fields);
|
|
|
|
|
|
|
|
/* Free command info (this is from strdup()) */
|
|
|
|
if (self->command)
|
|
|
|
free(self->command);
|
|
|
|
|
|
|
|
/* Free notice info (this is from strdup()) */
|
|
|
|
if (self->notice)
|
|
|
|
free(self->notice);
|
|
|
|
|
|
|
|
free(self);
|
|
|
|
|
|
|
|
mylog("QResult: exit DESTRUCTOR\n");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
QR_set_command(QResultClass *self, char *msg)
|
|
|
|
{
|
|
|
|
if (self->command)
|
|
|
|
free(self->command);
|
|
|
|
|
|
|
|
self->command = msg ? strdup(msg) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
QR_set_notice(QResultClass *self, char *msg)
|
|
|
|
{
|
|
|
|
if (self->notice)
|
|
|
|
free(self->notice);
|
|
|
|
|
|
|
|
self->notice = msg ? strdup(msg) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
QR_free_memory(QResultClass *self)
|
|
|
|
{
|
|
|
|
register int lf, row;
|
|
|
|
register TupleField *tuple = self->backend_tuples;
|
|
|
|
int fcount = self->fcount;
|
|
|
|
int num_fields = self->num_fields;
|
|
|
|
|
|
|
|
mylog("QResult: free memory in, fcount=%d\n", fcount);
|
|
|
|
|
|
|
|
if ( self->backend_tuples) {
|
|
|
|
|
|
|
|
for (row = 0; row < fcount; row++) {
|
|
|
|
mylog("row = %d, num_fields = %d\n", row, num_fields);
|
|
|
|
for (lf=0; lf < num_fields; lf++) {
|
|
|
|
if (tuple[lf].value != NULL) {
|
|
|
|
mylog("free [lf=%d] %u\n", lf, tuple[lf].value);
|
|
|
|
free(tuple[lf].value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tuple += num_fields; // next row
|
|
|
|
}
|
|
|
|
|
|
|
|
free(self->backend_tuples);
|
|
|
|
self->backend_tuples = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->fcount = 0;
|
|
|
|
|
|
|
|
mylog("QResult: free memory out\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function is called by send_query()
|
|
|
|
char
|
|
|
|
QR_fetch_tuples(QResultClass *self, ConnectionClass *conn, char *cursor)
|
|
|
|
{
|
|
|
|
// If called from send_query the first time (conn != NULL),
|
|
|
|
// then set the inTuples state,
|
|
|
|
// and read the tuples. If conn is NULL,
|
|
|
|
// it implies that we are being called from next_tuple(),
|
|
|
|
// like to get more rows so don't call next_tuple again!
|
|
|
|
if (conn != NULL) {
|
|
|
|
self->conn = conn;
|
|
|
|
|
1998-11-11 22:54:01 +01:00
|
|
|
mylog("QR_fetch_tuples: cursor = '%s', self->cursor=%u\n", cursor, self->cursor);
|
1998-04-13 17:02:05 +02:00
|
|
|
|
|
|
|
if (self->cursor)
|
|
|
|
free(self->cursor);
|
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
if ( globals.use_declarefetch) {
|
|
|
|
if (! cursor || cursor[0] == '\0') {
|
|
|
|
self->status = PGRES_INTERNAL_ERROR;
|
|
|
|
QR_set_message(self, "Internal Error -- no cursor for fetch");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
self->cursor = strdup(cursor);
|
1998-04-13 17:02:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read the field attributes.
|
|
|
|
// $$$$ Should do some error control HERE! $$$$
|
1998-11-11 22:54:01 +01:00
|
|
|
if ( CI_read_fields(self->fields, self->conn)) {
|
1998-04-13 17:02:05 +02:00
|
|
|
self->status = PGRES_FIELDS_OK;
|
|
|
|
self->num_fields = CI_get_num_fields(self->fields);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
self->status = PGRES_BAD_RESPONSE;
|
|
|
|
QR_set_message(self, "Error reading field information");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
mylog("QR_fetch_tuples: past CI_read_fields: num_fields = %d\n", self->num_fields);
|
|
|
|
|
|
|
|
/* allocate memory for the tuple cache */
|
1998-06-03 22:33:45 +02:00
|
|
|
mylog("MALLOC: fetch_max = %d, size = %d\n", globals.fetch_max, self->num_fields * sizeof(TupleField) * globals.fetch_max);
|
1998-04-13 17:02:05 +02:00
|
|
|
self->backend_tuples = (TupleField *) malloc(self->num_fields * sizeof(TupleField) * globals.fetch_max);
|
|
|
|
if ( ! self->backend_tuples) {
|
|
|
|
self->status = PGRES_FATAL_ERROR;
|
|
|
|
QR_set_message(self, "Could not get memory for tuple cache.");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->inTuples = TRUE;
|
|
|
|
|
|
|
|
/* Force a read to occur in next_tuple */
|
|
|
|
self->fcount = globals.fetch_max+1;
|
|
|
|
self->fetch_count = globals.fetch_max+1;
|
|
|
|
|
|
|
|
return QR_next_tuple(self);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Always have to read the field attributes.
|
|
|
|
// But we dont have to reallocate memory for them!
|
|
|
|
|
1998-11-11 22:54:01 +01:00
|
|
|
if ( ! CI_read_fields(NULL, self->conn)) {
|
1998-04-13 17:02:05 +02:00
|
|
|
self->status = PGRES_BAD_RESPONSE;
|
|
|
|
QR_set_message(self, "Error reading field information");
|
|
|
|
return FALSE;
|
|
|
|
}
|
1998-07-23 01:47:48 +02:00
|
|
|
return TRUE;
|
1998-04-13 17:02:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
// Close the cursor and end the transaction (if no cursors left)
|
1998-04-13 17:02:05 +02:00
|
|
|
// We only close cursor/end the transaction if a cursor was used.
|
|
|
|
int
|
|
|
|
QR_close(QResultClass *self)
|
|
|
|
{
|
|
|
|
QResultClass *res;
|
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
if (globals.use_declarefetch && self->conn && self->cursor) {
|
1998-04-13 17:02:05 +02:00
|
|
|
char buf[64];
|
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
sprintf(buf, "close %s", self->cursor);
|
1998-04-13 17:02:05 +02:00
|
|
|
mylog("QResult: closing cursor: '%s'\n", buf);
|
|
|
|
|
|
|
|
res = CC_send_query(self->conn, buf, NULL, NULL);
|
|
|
|
|
|
|
|
self->inTuples = FALSE;
|
1998-06-03 22:33:45 +02:00
|
|
|
|
1998-04-13 17:02:05 +02:00
|
|
|
free(self->cursor);
|
|
|
|
self->cursor = NULL;
|
|
|
|
|
|
|
|
if (res == NULL) {
|
|
|
|
self->status = PGRES_FATAL_ERROR;
|
|
|
|
QR_set_message(self, "Error closing cursor.");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
/* End the transaction if there are no cursors left on this conn */
|
|
|
|
if (CC_cursor_count(self->conn) == 0) {
|
|
|
|
mylog("QResult: END transaction on conn=%u\n", self->conn);
|
|
|
|
|
|
|
|
res = CC_send_query(self->conn, "END", NULL, NULL);
|
|
|
|
|
|
|
|
CC_set_no_trans(self->conn);
|
|
|
|
|
|
|
|
if (res == NULL) {
|
|
|
|
self->status = PGRES_FATAL_ERROR;
|
|
|
|
QR_set_message(self, "Error ending transaction.");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1998-04-13 17:02:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function is called by fetch_tuples() AND SQLFetch()
|
|
|
|
int
|
|
|
|
QR_next_tuple(QResultClass *self)
|
|
|
|
{
|
|
|
|
int id;
|
|
|
|
QResultClass *res;
|
|
|
|
SocketClass *sock;
|
|
|
|
/* Speed up access */
|
|
|
|
int fetch_count = self->fetch_count;
|
|
|
|
int fcount = self->fcount;
|
|
|
|
TupleField *the_tuples = self->backend_tuples;
|
|
|
|
static char msgbuffer[MAX_MESSAGE_LEN+1];
|
|
|
|
char cmdbuffer[MAX_MESSAGE_LEN+1]; // QR_set_command() dups this string so dont need static
|
|
|
|
|
|
|
|
if (fetch_count < fcount) { /* return a row from cache */
|
|
|
|
mylog("next_tuple: fetch_count < fcount: returning tuple %d, fcount = %d\n", fetch_count, fcount);
|
|
|
|
self->tupleField = the_tuples + (fetch_count * self->num_fields); /* next row */
|
|
|
|
self->fetch_count++;
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
else if (self->fcount < globals.fetch_max) { /* last row from cache */
|
|
|
|
// We are done because we didn't even get FETCH_MAX tuples
|
|
|
|
mylog("next_tuple: fcount < FETCH_MAX: fcount = %d, fetch_count = %d\n", fcount, fetch_count);
|
|
|
|
self->tupleField = NULL;
|
|
|
|
self->status = PGRES_END_TUPLES;
|
|
|
|
return -1; /* end of tuples */
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* See if we need to fetch another group of rows.
|
|
|
|
We may be being called from send_query(), and
|
|
|
|
if so, don't send another fetch, just fall through
|
|
|
|
and read the tuples.
|
|
|
|
*/
|
|
|
|
self->tupleField = NULL;
|
|
|
|
if ( ! self->inTuples) {
|
|
|
|
char fetch[128];
|
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
if ( ! globals.use_declarefetch) {
|
|
|
|
mylog("next_tuple: ALL_ROWS: done, fcount = %d, fetch_count = %d\n", fcount, fetch_count);
|
|
|
|
self->tupleField = NULL;
|
|
|
|
self->status = PGRES_END_TUPLES;
|
|
|
|
return -1; /* end of tuples */
|
|
|
|
}
|
|
|
|
|
1998-04-13 17:02:05 +02:00
|
|
|
sprintf(fetch, "fetch %d in %s", globals.fetch_max, self->cursor);
|
|
|
|
|
|
|
|
mylog("next_tuple: sending actual fetch (%d) query '%s'\n", globals.fetch_max, fetch);
|
|
|
|
|
|
|
|
// don't read ahead for the next tuple (self) !
|
|
|
|
res = CC_send_query(self->conn, fetch, self, NULL);
|
|
|
|
if (res == NULL) {
|
|
|
|
self->status = PGRES_FATAL_ERROR;
|
|
|
|
QR_set_message(self, "Error fetching next group.");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
self->inTuples = TRUE;
|
|
|
|
/* This is a true fetch, like SQLFetch() */
|
|
|
|
self->fetch_count = 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mylog("next_tuple: inTuples = true, falling through: fcount = %d, fetch_count = %d\n", self->fcount, self->fetch_count);
|
|
|
|
/* This is a pre-fetch (fetching rows right after query
|
|
|
|
but before any real SQLFetch() calls. This is done
|
|
|
|
so the field attributes are available.
|
|
|
|
*/
|
|
|
|
self->fetch_count = 0;
|
|
|
|
}
|
|
|
|
// fall through and read the next group
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self->fcount = 0;
|
|
|
|
sock = CC_get_socket(self->conn);
|
|
|
|
self->tupleField = NULL;
|
|
|
|
|
|
|
|
for ( ; ;) {
|
|
|
|
|
|
|
|
id = SOCK_get_char(sock);
|
1998-11-11 22:54:01 +01:00
|
|
|
|
1998-04-13 17:02:05 +02:00
|
|
|
switch (id) {
|
1998-06-03 22:33:45 +02:00
|
|
|
case 'T': /* Tuples within tuples cannot be handled */
|
|
|
|
self->status = PGRES_BAD_RESPONSE;
|
|
|
|
QR_set_message(self, "Tuples within tuples cannot be handled");
|
|
|
|
return FALSE;
|
|
|
|
case 'B': /* Tuples in binary format */
|
|
|
|
case 'D': /* Tuples in ASCII format */
|
|
|
|
|
|
|
|
if ( ! globals.use_declarefetch && self->fcount > 0 && ! (self->fcount % globals.fetch_max)) {
|
|
|
|
size_t old_size = self->fcount * self->num_fields * sizeof(TupleField);
|
|
|
|
mylog("REALLOC: old_size = %d\n", old_size);
|
|
|
|
|
|
|
|
self->backend_tuples = (TupleField *) realloc(self->backend_tuples, old_size + (self->num_fields * sizeof(TupleField) * globals.fetch_max));
|
|
|
|
if ( ! self->backend_tuples) {
|
|
|
|
self->status = PGRES_FATAL_ERROR;
|
|
|
|
QR_set_message(self, "Out of memory while reading tuples.");
|
|
|
|
return FALSE;
|
1998-04-13 17:02:05 +02:00
|
|
|
}
|
1998-06-03 22:33:45 +02:00
|
|
|
}
|
1998-04-13 17:02:05 +02:00
|
|
|
|
1998-06-03 22:33:45 +02:00
|
|
|
if ( ! QR_read_tuple(self, (char) (id == 0))) {
|
|
|
|
self->status = PGRES_BAD_RESPONSE;
|
|
|
|
QR_set_message(self, "Error reading the tuple");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->fcount++;
|
|
|
|
break; // continue reading
|
|
|
|
|
|
|
|
|
|
|
|
case 'C': /* End of tuple list */
|
|
|
|
SOCK_get_string(sock, cmdbuffer, MAX_MESSAGE_LEN);
|
|
|
|
QR_set_command(self, cmdbuffer);
|
|
|
|
|
|
|
|
mylog("end of tuple list -- setting inUse to false: this = %u\n", self);
|
|
|
|
|
|
|
|
self->inTuples = FALSE;
|
|
|
|
if (self->fcount > 0) {
|
|
|
|
|
|
|
|
qlog(" [ fetched %d rows ]\n", self->fcount);
|
|
|
|
mylog("_next_tuple: 'C' fetch_max && fcount = %d\n", self->fcount);
|
|
|
|
|
|
|
|
/* set to first row */
|
|
|
|
self->tupleField = self->backend_tuples; // the_tuples;
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
else { // We are surely done here (we read 0 tuples)
|
|
|
|
qlog(" [ fetched 0 rows ]\n");
|
|
|
|
mylog("_next_tuple: 'C': DONE (fcount == 0)\n");
|
|
|
|
return -1; /* end of tuples */
|
|
|
|
}
|
|
|
|
|
|
|
|
case 'E': /* Error */
|
|
|
|
SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
|
|
|
|
QR_set_message(self, msgbuffer);
|
|
|
|
self->status = PGRES_FATAL_ERROR;
|
|
|
|
|
|
|
|
if ( ! strncmp(msgbuffer, "FATAL", 5))
|
|
|
|
CC_set_no_trans(self->conn);
|
|
|
|
|
|
|
|
qlog("ERROR from backend in next_tuple: '%s'\n", msgbuffer);
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
case 'N': /* Notice */
|
|
|
|
SOCK_get_string(sock, msgbuffer, ERROR_MSG_LENGTH);
|
|
|
|
QR_set_message(self, msgbuffer);
|
|
|
|
self->status = PGRES_NONFATAL_ERROR;
|
|
|
|
qlog("NOTICE from backend in next_tuple: '%s'\n", msgbuffer);
|
|
|
|
continue;
|
|
|
|
|
|
|
|
default: /* this should only happen if the backend dumped core */
|
1998-11-11 22:54:01 +01:00
|
|
|
mylog("QR_next_tuple: Unexpected result from backend: id = '%c' (%d)\n", id, id);
|
|
|
|
qlog("QR_next_tuple: Unexpected result from backend: id = '%c' (%d)\n", id, id);
|
1998-06-03 22:33:45 +02:00
|
|
|
QR_set_message(self, "Unexpected result from backend. It probably crashed");
|
|
|
|
self->status = PGRES_FATAL_ERROR;
|
|
|
|
CC_set_no_trans(self->conn);
|
|
|
|
return FALSE;
|
1998-04-13 17:02:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
char
|
|
|
|
QR_read_tuple(QResultClass *self, char binary)
|
|
|
|
{
|
|
|
|
Int2 field_lf;
|
|
|
|
TupleField *this_tuplefield;
|
|
|
|
char bmp, bitmap[MAX_FIELDS]; /* Max. len of the bitmap */
|
|
|
|
Int2 bitmaplen; /* len of the bitmap in bytes */
|
|
|
|
Int2 bitmap_pos;
|
|
|
|
Int2 bitcnt;
|
|
|
|
Int4 len;
|
|
|
|
char *buffer;
|
|
|
|
int num_fields = self->num_fields; // speed up access
|
|
|
|
SocketClass *sock = CC_get_socket(self->conn);
|
1998-06-03 22:33:45 +02:00
|
|
|
ColumnInfoClass *flds;
|
|
|
|
|
1998-04-13 17:02:05 +02:00
|
|
|
|
|
|
|
/* set the current row to read the fields into */
|
|
|
|
this_tuplefield = self->backend_tuples + (self->fcount * num_fields);
|
|
|
|
|
|
|
|
bitmaplen = (Int2) num_fields / BYTELEN;
|
|
|
|
if ((num_fields % BYTELEN) > 0)
|
|
|
|
bitmaplen++;
|
|
|
|
|
|
|
|
/*
|
|
|
|
At first the server sends a bitmap that indicates which
|
|
|
|
database fields are null
|
|
|
|
*/
|
|
|
|
SOCK_get_n_char(sock, bitmap, bitmaplen);
|
|
|
|
|
|
|
|
bitmap_pos = 0;
|
|
|
|
bitcnt = 0;
|
|
|
|
bmp = bitmap[bitmap_pos];
|
|
|
|
|
|
|
|
for(field_lf = 0; field_lf < num_fields; field_lf++) {
|
|
|
|
/* Check if the current field is NULL */
|
|
|
|
if(!(bmp & 0200)) {
|
|
|
|
/* YES, it is NULL ! */
|
|
|
|
this_tuplefield[field_lf].len = 0;
|
|
|
|
this_tuplefield[field_lf].value = 0;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
NO, the field is not null. so get at first the
|
|
|
|
length of the field (four bytes)
|
|
|
|
*/
|
|
|
|
len = SOCK_get_int(sock, VARHDRSZ);
|
|
|
|
if (!binary)
|
|
|
|
len -= VARHDRSZ;
|
|
|
|
|
|
|
|
buffer = (char *)malloc(len+1);
|
|
|
|
SOCK_get_n_char(sock, buffer, len);
|
|
|
|
buffer[len] = '\0';
|
|
|
|
|
1998-10-06 07:58:41 +02:00
|
|
|
mylog("qresult: len=%d, buffer='%s'\n", len, buffer);
|
1998-04-13 17:02:05 +02:00
|
|
|
|
|
|
|
this_tuplefield[field_lf].len = len;
|
|
|
|
this_tuplefield[field_lf].value = buffer;
|
1998-06-03 22:33:45 +02:00
|
|
|
|
|
|
|
/* This can be used to set the longest length of the column for any
|
|
|
|
row in the tuple cache. It would not be accurate for varchar and
|
|
|
|
text fields to use this since a tuple cache is only 100 rows.
|
|
|
|
Bpchar can be handled since the strlen of all rows is fixed,
|
|
|
|
assuming there are not 100 nulls in a row!
|
|
|
|
*/
|
|
|
|
|
|
|
|
flds = self->fields;
|
|
|
|
if (flds->display_size[field_lf] < len)
|
|
|
|
flds->display_size[field_lf] = len;
|
1998-04-13 17:02:05 +02:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
Now adjust for the next bit to be scanned in the
|
|
|
|
next loop.
|
|
|
|
*/
|
|
|
|
bitcnt++;
|
|
|
|
if (BYTELEN == bitcnt) {
|
|
|
|
bitmap_pos++;
|
|
|
|
bmp = bitmap[bitmap_pos];
|
|
|
|
bitcnt = 0;
|
|
|
|
} else
|
|
|
|
bmp <<= 1;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|