mirror of
https://github.com/postgres/postgres.git
synced 2025-11-24 00:23:06 +03:00
1408 lines
31 KiB
C
1408 lines
31 KiB
C
|
|
/* Module: results.c
|
|
*
|
|
* Description: This module contains functions related to
|
|
* retrieving result information through the ODBC API.
|
|
*
|
|
* Classes: n/a
|
|
*
|
|
* API functions: SQLRowCount, SQLNumResultCols, SQLDescribeCol, SQLColAttributes,
|
|
* SQLGetData, SQLFetch, SQLExtendedFetch,
|
|
* SQLMoreResults(NI), SQLSetPos, SQLSetScrollOptions(NI),
|
|
* SQLSetCursorName, SQLGetCursorName
|
|
*
|
|
* Comments: See "notice.txt" for copyright and license information.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include "psqlodbc.h"
|
|
#include "dlg_specific.h"
|
|
#include "environ.h"
|
|
#include "connection.h"
|
|
#include "statement.h"
|
|
#include "bind.h"
|
|
#include "qresult.h"
|
|
#include "convert.h"
|
|
#include "pgtypes.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#ifndef WIN32
|
|
#include "iodbc.h"
|
|
#include "isqlext.h"
|
|
#else
|
|
#include <windows.h>
|
|
#include <sqlext.h>
|
|
#endif
|
|
|
|
extern GLOBAL_VALUES globals;
|
|
|
|
|
|
|
|
RETCODE SQL_API
|
|
SQLRowCount(
|
|
HSTMT hstmt,
|
|
SDWORD FAR *pcrow)
|
|
{
|
|
static char *func = "SQLRowCount";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *res;
|
|
char *msg,
|
|
*ptr;
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
if (stmt->manual_result)
|
|
{
|
|
if (pcrow)
|
|
*pcrow = -1;
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
if (stmt->statement_type == STMT_TYPE_SELECT)
|
|
{
|
|
if (stmt->status == STMT_FINISHED)
|
|
{
|
|
res = SC_get_Result(stmt);
|
|
|
|
if (res && pcrow)
|
|
{
|
|
*pcrow = globals.use_declarefetch ? -1 : QR_get_num_tuples(res);
|
|
return SQL_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
res = SC_get_Result(stmt);
|
|
if (res && pcrow)
|
|
{
|
|
msg = QR_get_command(res);
|
|
mylog("*** msg = '%s'\n", msg);
|
|
trim(msg); /* get rid of trailing spaces */
|
|
ptr = strrchr(msg, ' ');
|
|
if (ptr)
|
|
{
|
|
*pcrow = atoi(ptr + 1);
|
|
mylog("**** SQLRowCount(): THE ROWS: *pcrow = %d\n", *pcrow);
|
|
}
|
|
else
|
|
{
|
|
*pcrow = -1;
|
|
|
|
mylog("**** SQLRowCount(): NO ROWS: *pcrow = %d\n", *pcrow);
|
|
}
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
}
|
|
|
|
SC_log_error(func, "Bad return value", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
|
|
/* This returns the number of columns associated with the database */
|
|
/* attached to "hstmt". */
|
|
|
|
|
|
RETCODE SQL_API
|
|
SQLNumResultCols(
|
|
HSTMT hstmt,
|
|
SWORD FAR *pccol)
|
|
{
|
|
static char *func = "SQLNumResultCols";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *result;
|
|
char parse_ok;
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
SC_clear_error(stmt);
|
|
|
|
parse_ok = FALSE;
|
|
if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT)
|
|
{
|
|
|
|
if (stmt->parse_status == STMT_PARSE_NONE)
|
|
{
|
|
mylog("SQLNumResultCols: calling parse_statement on stmt=%u\n", stmt);
|
|
parse_statement(stmt);
|
|
}
|
|
|
|
if (stmt->parse_status != STMT_PARSE_FATAL)
|
|
{
|
|
parse_ok = TRUE;
|
|
*pccol = stmt->nfld;
|
|
mylog("PARSE: SQLNumResultCols: *pccol = %d\n", *pccol);
|
|
}
|
|
}
|
|
|
|
if (!parse_ok)
|
|
{
|
|
|
|
SC_pre_execute(stmt);
|
|
result = SC_get_Result(stmt);
|
|
|
|
mylog("SQLNumResultCols: result = %u, status = %d, numcols = %d\n", result, stmt->status, result != NULL ? QR_NumResultCols(result) : -1);
|
|
if ((!result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE)))
|
|
{
|
|
/* no query has been executed on this statement */
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
stmt->errormsg = "No query has been executed with that handle";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
*pccol = QR_NumResultCols(result);
|
|
}
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
|
|
/* - - - - - - - - - */
|
|
|
|
|
|
|
|
/* Return information about the database column the user wants */
|
|
/* information about. */
|
|
RETCODE SQL_API
|
|
SQLDescribeCol(
|
|
HSTMT hstmt,
|
|
UWORD icol,
|
|
UCHAR FAR *szColName,
|
|
SWORD cbColNameMax,
|
|
SWORD FAR *pcbColName,
|
|
SWORD FAR *pfSqlType,
|
|
UDWORD FAR *pcbColDef,
|
|
SWORD FAR *pibScale,
|
|
SWORD FAR *pfNullable)
|
|
{
|
|
static char *func = "SQLDescribeCol";
|
|
|
|
/* gets all the information about a specific column */
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *res;
|
|
char *col_name = NULL;
|
|
Int4 fieldtype = 0;
|
|
int precision = 0;
|
|
ConnInfo *ci;
|
|
char parse_ok;
|
|
char buf[255];
|
|
int len = 0;
|
|
RETCODE result;
|
|
|
|
|
|
mylog("%s: entering...\n", func);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
ci = &(stmt->hdbc->connInfo);
|
|
|
|
SC_clear_error(stmt);
|
|
|
|
/*
|
|
* Dont check for bookmark column. This is the responsibility of the
|
|
* driver manager.
|
|
*/
|
|
|
|
icol--; /* use zero based column numbers */
|
|
|
|
|
|
parse_ok = FALSE;
|
|
if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT)
|
|
{
|
|
|
|
if (stmt->parse_status == STMT_PARSE_NONE)
|
|
{
|
|
mylog("SQLDescribeCol: calling parse_statement on stmt=%u\n", stmt);
|
|
parse_statement(stmt);
|
|
}
|
|
|
|
|
|
mylog("PARSE: DescribeCol: icol=%d, stmt=%u, stmt->nfld=%d, stmt->fi=%u\n", icol, stmt, stmt->nfld, stmt->fi);
|
|
|
|
if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol])
|
|
{
|
|
|
|
if (icol >= stmt->nfld)
|
|
{
|
|
stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
|
|
stmt->errormsg = "Invalid column number in DescribeCol.";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
mylog("DescribeCol: getting info for icol=%d\n", icol);
|
|
|
|
fieldtype = stmt->fi[icol]->type;
|
|
col_name = stmt->fi[icol]->name;
|
|
precision = stmt->fi[icol]->precision;
|
|
|
|
mylog("PARSE: fieldtype=%d, col_name='%s', precision=%d\n", fieldtype, col_name, precision);
|
|
if (fieldtype > 0)
|
|
parse_ok = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* If couldn't parse it OR the field being described was not parsed
|
|
* (i.e., because it was a function or expression, etc, then do it the
|
|
* old fashioned way.
|
|
*/
|
|
if (!parse_ok)
|
|
{
|
|
SC_pre_execute(stmt);
|
|
|
|
res = SC_get_Result(stmt);
|
|
|
|
mylog("**** SQLDescribeCol: res = %u, stmt->status = %d, !finished=%d, !premature=%d\n", res, stmt->status, stmt->status != STMT_FINISHED, stmt->status != STMT_PREMATURE);
|
|
if ((NULL == res) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE)))
|
|
{
|
|
/* no query has been executed on this statement */
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
stmt->errormsg = "No query has been assigned to this statement.";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (icol >= QR_NumResultCols(res))
|
|
{
|
|
stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
|
|
stmt->errormsg = "Invalid column number in DescribeCol.";
|
|
sprintf(buf, "Col#=%d, #Cols=%d", icol, QR_NumResultCols(res));
|
|
SC_log_error(func, buf, stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
col_name = QR_get_fieldname(res, icol);
|
|
fieldtype = QR_get_field_type(res, icol);
|
|
|
|
precision = pgtype_precision(stmt, fieldtype, icol, globals.unknown_sizes); /* atoi(ci->unknown_sizes
|
|
* ) */
|
|
}
|
|
|
|
mylog("describeCol: col %d fieldname = '%s'\n", icol, col_name);
|
|
mylog("describeCol: col %d fieldtype = %d\n", icol, fieldtype);
|
|
mylog("describeCol: col %d precision = %d\n", icol, precision);
|
|
|
|
|
|
result = SQL_SUCCESS;
|
|
|
|
/************************/
|
|
/* COLUMN NAME */
|
|
/************************/
|
|
len = strlen(col_name);
|
|
|
|
if (pcbColName)
|
|
*pcbColName = len;
|
|
|
|
if (szColName)
|
|
{
|
|
strncpy_null(szColName, col_name, cbColNameMax);
|
|
|
|
if (len >= cbColNameMax)
|
|
{
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
stmt->errornumber = STMT_TRUNCATED;
|
|
stmt->errormsg = "The buffer was too small for the result.";
|
|
}
|
|
}
|
|
|
|
|
|
/************************/
|
|
/* SQL TYPE */
|
|
/************************/
|
|
if (pfSqlType)
|
|
{
|
|
*pfSqlType = pgtype_to_sqltype(stmt, fieldtype);
|
|
|
|
mylog("describeCol: col %d *pfSqlType = %d\n", icol, *pfSqlType);
|
|
}
|
|
|
|
/************************/
|
|
/* PRECISION */
|
|
/************************/
|
|
if (pcbColDef)
|
|
{
|
|
|
|
if (precision < 0)
|
|
precision = 0; /* "I dont know" */
|
|
|
|
*pcbColDef = precision;
|
|
|
|
mylog("describeCol: col %d *pcbColDef = %d\n", icol, *pcbColDef);
|
|
}
|
|
|
|
/************************/
|
|
/* SCALE */
|
|
/************************/
|
|
if (pibScale)
|
|
{
|
|
Int2 scale;
|
|
|
|
scale = pgtype_scale(stmt, fieldtype, icol);
|
|
if (scale == -1)
|
|
scale = 0;
|
|
|
|
*pibScale = scale;
|
|
mylog("describeCol: col %d *pibScale = %d\n", icol, *pibScale);
|
|
}
|
|
|
|
/************************/
|
|
/* NULLABILITY */
|
|
/************************/
|
|
if (pfNullable)
|
|
{
|
|
*pfNullable = (parse_ok) ? stmt->fi[icol]->nullable : pgtype_nullable(stmt, fieldtype);
|
|
|
|
mylog("describeCol: col %d *pfNullable = %d\n", icol, *pfNullable);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Returns result column descriptor information for a result set. */
|
|
|
|
RETCODE SQL_API
|
|
SQLColAttributes(
|
|
HSTMT hstmt,
|
|
UWORD icol,
|
|
UWORD fDescType,
|
|
PTR rgbDesc,
|
|
SWORD cbDescMax,
|
|
SWORD FAR *pcbDesc,
|
|
SDWORD FAR *pfDesc)
|
|
{
|
|
static char *func = "SQLColAttributes";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
Int4 field_type = 0;
|
|
ConnInfo *ci;
|
|
int unknown_sizes;
|
|
int cols = 0;
|
|
char parse_ok;
|
|
RETCODE result;
|
|
char *p = NULL;
|
|
int len = 0,
|
|
value = 0;
|
|
|
|
mylog("%s: entering...\n", func);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
ci = &(stmt->hdbc->connInfo);
|
|
|
|
/*
|
|
* Dont check for bookmark column. This is the responsibility of the
|
|
* driver manager. For certain types of arguments, the column number
|
|
* is ignored anyway, so it may be 0.
|
|
*/
|
|
|
|
icol--;
|
|
|
|
unknown_sizes = globals.unknown_sizes; /* atoi(ci->unknown_sizes);
|
|
* */
|
|
if (unknown_sizes == UNKNOWNS_AS_DONTKNOW) /* not appropriate for
|
|
* SQLColAttributes() */
|
|
unknown_sizes = UNKNOWNS_AS_MAX;
|
|
|
|
parse_ok = FALSE;
|
|
if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT)
|
|
{
|
|
|
|
if (stmt->parse_status == STMT_PARSE_NONE)
|
|
{
|
|
mylog("SQLColAttributes: calling parse_statement\n");
|
|
parse_statement(stmt);
|
|
}
|
|
|
|
cols = stmt->nfld;
|
|
|
|
/*
|
|
* Column Count is a special case. The Column number is ignored
|
|
* in this case.
|
|
*/
|
|
if (fDescType == SQL_COLUMN_COUNT)
|
|
{
|
|
if (pfDesc)
|
|
*pfDesc = cols;
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol])
|
|
{
|
|
|
|
if (icol >= cols)
|
|
{
|
|
stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
|
|
stmt->errormsg = "Invalid column number in ColAttributes.";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
field_type = stmt->fi[icol]->type;
|
|
if (field_type > 0)
|
|
parse_ok = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!parse_ok)
|
|
{
|
|
SC_pre_execute(stmt);
|
|
|
|
mylog("**** SQLColAtt: result = %u, status = %d, numcols = %d\n", stmt->result, stmt->status, stmt->result != NULL ? QR_NumResultCols(stmt->result) : -1);
|
|
|
|
if ((NULL == stmt->result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE)))
|
|
{
|
|
stmt->errormsg = "Can't get column attributes: no result found.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
cols = QR_NumResultCols(stmt->result);
|
|
|
|
/*
|
|
* Column Count is a special case. The Column number is ignored
|
|
* in this case.
|
|
*/
|
|
if (fDescType == SQL_COLUMN_COUNT)
|
|
{
|
|
if (pfDesc)
|
|
*pfDesc = cols;
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
if (icol >= cols)
|
|
{
|
|
stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
|
|
stmt->errormsg = "Invalid column number in ColAttributes.";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
field_type = QR_get_field_type(stmt->result, icol);
|
|
}
|
|
|
|
mylog("colAttr: col %d field_type = %d\n", icol, field_type);
|
|
|
|
switch (fDescType)
|
|
{
|
|
case SQL_COLUMN_AUTO_INCREMENT:
|
|
value = pgtype_auto_increment(stmt, field_type);
|
|
if (value == -1) /* non-numeric becomes FALSE (ODBC Doc) */
|
|
value = FALSE;
|
|
|
|
break;
|
|
|
|
case SQL_COLUMN_CASE_SENSITIVE:
|
|
value = pgtype_case_sensitive(stmt, field_type);
|
|
break;
|
|
|
|
/*
|
|
* This special case is handled above.
|
|
*
|
|
* case SQL_COLUMN_COUNT:
|
|
*/
|
|
|
|
case SQL_COLUMN_DISPLAY_SIZE:
|
|
value = (parse_ok) ? stmt->fi[icol]->display_size : pgtype_display_size(stmt, field_type, icol, unknown_sizes);
|
|
|
|
mylog("SQLColAttributes: col %d, display_size= %d\n", icol, value);
|
|
|
|
break;
|
|
|
|
case SQL_COLUMN_LABEL:
|
|
if (parse_ok && stmt->fi[icol]->alias[0] != '\0')
|
|
{
|
|
p = stmt->fi[icol]->alias;
|
|
|
|
mylog("SQLColAttr: COLUMN_LABEL = '%s'\n", p);
|
|
break;
|
|
|
|
} /* otherwise same as column name -- FALL
|
|
* THROUGH!!! */
|
|
|
|
case SQL_COLUMN_NAME:
|
|
|
|
p = (parse_ok) ? stmt->fi[icol]->name : QR_get_fieldname(stmt->result, icol);
|
|
|
|
mylog("SQLColAttr: COLUMN_NAME = '%s'\n", p);
|
|
break;
|
|
|
|
case SQL_COLUMN_LENGTH:
|
|
value = (parse_ok) ? stmt->fi[icol]->length : pgtype_length(stmt, field_type, icol, unknown_sizes);
|
|
|
|
mylog("SQLColAttributes: col %d, length = %d\n", icol, value);
|
|
break;
|
|
|
|
case SQL_COLUMN_MONEY:
|
|
value = pgtype_money(stmt, field_type);
|
|
break;
|
|
|
|
case SQL_COLUMN_NULLABLE:
|
|
value = (parse_ok) ? stmt->fi[icol]->nullable : pgtype_nullable(stmt, field_type);
|
|
break;
|
|
|
|
case SQL_COLUMN_OWNER_NAME:
|
|
p = "";
|
|
break;
|
|
|
|
case SQL_COLUMN_PRECISION:
|
|
value = (parse_ok) ? stmt->fi[icol]->precision : pgtype_precision(stmt, field_type, icol, unknown_sizes);
|
|
|
|
mylog("SQLColAttributes: col %d, precision = %d\n", icol, value);
|
|
break;
|
|
|
|
case SQL_COLUMN_QUALIFIER_NAME:
|
|
p = "";
|
|
break;
|
|
|
|
case SQL_COLUMN_SCALE:
|
|
value = pgtype_scale(stmt, field_type, icol);
|
|
break;
|
|
|
|
case SQL_COLUMN_SEARCHABLE:
|
|
value = pgtype_searchable(stmt, field_type);
|
|
break;
|
|
|
|
case SQL_COLUMN_TABLE_NAME:
|
|
|
|
p = (parse_ok && stmt->fi[icol]->ti) ? stmt->fi[icol]->ti->name : "";
|
|
|
|
mylog("SQLColAttr: TABLE_NAME = '%s'\n", p);
|
|
break;
|
|
|
|
case SQL_COLUMN_TYPE:
|
|
value = pgtype_to_sqltype(stmt, field_type);
|
|
break;
|
|
|
|
case SQL_COLUMN_TYPE_NAME:
|
|
p = pgtype_to_name(stmt, field_type);
|
|
break;
|
|
|
|
case SQL_COLUMN_UNSIGNED:
|
|
value = pgtype_unsigned(stmt, field_type);
|
|
if (value == -1) /* non-numeric becomes TRUE (ODBC Doc) */
|
|
value = TRUE;
|
|
|
|
break;
|
|
|
|
case SQL_COLUMN_UPDATABLE:
|
|
|
|
/*
|
|
* Neither Access or Borland care about this.
|
|
*
|
|
* if (field_type == PG_TYPE_OID) pfDesc = SQL_ATTR_READONLY;
|
|
* else
|
|
*/
|
|
|
|
value = SQL_ATTR_WRITE;
|
|
|
|
mylog("SQLColAttr: UPDATEABLE = %d\n", value);
|
|
break;
|
|
}
|
|
|
|
result = SQL_SUCCESS;
|
|
|
|
if (p)
|
|
{ /* char/binary data */
|
|
len = strlen(p);
|
|
|
|
if (rgbDesc)
|
|
{
|
|
strncpy_null((char *) rgbDesc, p, (size_t) cbDescMax);
|
|
|
|
if (len >= cbDescMax)
|
|
{
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
stmt->errornumber = STMT_TRUNCATED;
|
|
stmt->errormsg = "The buffer was too small for the result.";
|
|
}
|
|
}
|
|
|
|
if (pcbDesc)
|
|
*pcbDesc = len;
|
|
}
|
|
else
|
|
{ /* numeric data */
|
|
|
|
if (pfDesc)
|
|
*pfDesc = value;
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Returns result data for a single column in the current row. */
|
|
|
|
RETCODE SQL_API
|
|
SQLGetData(
|
|
HSTMT hstmt,
|
|
UWORD icol,
|
|
SWORD fCType,
|
|
PTR rgbValue,
|
|
SDWORD cbValueMax,
|
|
SDWORD FAR *pcbValue)
|
|
{
|
|
static char *func = "SQLGetData";
|
|
QResultClass *res;
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
int num_cols,
|
|
num_rows;
|
|
Int4 field_type;
|
|
void *value = NULL;
|
|
int result;
|
|
char get_bookmark = FALSE;
|
|
|
|
mylog("SQLGetData: enter, stmt=%u\n", stmt);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
res = stmt->result;
|
|
|
|
if (STMT_EXECUTING == stmt->status)
|
|
{
|
|
stmt->errormsg = "Can't get data while statement is still executing.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status != STMT_FINISHED)
|
|
{
|
|
stmt->errornumber = STMT_STATUS_ERROR;
|
|
stmt->errormsg = "GetData can only be called after the successful execution on a SQL statement";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (icol == 0)
|
|
{
|
|
|
|
if (stmt->options.use_bookmarks == SQL_UB_OFF)
|
|
{
|
|
stmt->errornumber = STMT_COLNUM_ERROR;
|
|
stmt->errormsg = "Attempt to retrieve bookmark with bookmark usage disabled";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/* Make sure it is the bookmark data type */
|
|
if (fCType != SQL_C_BOOKMARK)
|
|
{
|
|
stmt->errormsg = "Column 0 is not of type SQL_C_BOOKMARK";
|
|
stmt->errornumber = STMT_PROGRAM_TYPE_OUT_OF_RANGE;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
get_bookmark = TRUE;
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
/* use zero-based column numbers */
|
|
icol--;
|
|
|
|
/* make sure the column number is valid */
|
|
num_cols = QR_NumResultCols(res);
|
|
if (icol >= num_cols)
|
|
{
|
|
stmt->errormsg = "Invalid column number.";
|
|
stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
|
|
if (stmt->manual_result || !globals.use_declarefetch)
|
|
{
|
|
/* make sure we're positioned on a valid row */
|
|
num_rows = QR_get_num_tuples(res);
|
|
if ((stmt->currTuple < 0) ||
|
|
(stmt->currTuple >= num_rows))
|
|
{
|
|
stmt->errormsg = "Not positioned on a valid row for GetData.";
|
|
stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
mylog(" num_rows = %d\n", num_rows);
|
|
|
|
if (!get_bookmark)
|
|
{
|
|
if (stmt->manual_result)
|
|
value = QR_get_value_manual(res, stmt->currTuple, icol);
|
|
else
|
|
value = QR_get_value_backend_row(res, stmt->currTuple, icol);
|
|
mylog(" value = '%s'\n", value);
|
|
}
|
|
}
|
|
else
|
|
{ /* it's a SOCKET result (backend data) */
|
|
if (stmt->currTuple == -1 || !res || !res->tupleField)
|
|
{
|
|
stmt->errormsg = "Not positioned on a valid row for GetData.";
|
|
stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (!get_bookmark)
|
|
value = QR_get_value_backend(res, icol);
|
|
|
|
mylog(" socket: value = '%s'\n", value);
|
|
}
|
|
|
|
if (get_bookmark)
|
|
{
|
|
*((UDWORD *) rgbValue) = SC_get_bookmark(stmt);
|
|
|
|
if (pcbValue)
|
|
*pcbValue = 4;
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
field_type = QR_get_field_type(res, icol);
|
|
|
|
mylog("**** SQLGetData: icol = %d, fCType = %d, field_type = %d, value = '%s'\n", icol, fCType, field_type, value);
|
|
|
|
stmt->current_col = icol;
|
|
|
|
result = copy_and_convert_field(stmt, field_type, value,
|
|
fCType, rgbValue, cbValueMax, pcbValue);
|
|
|
|
stmt->current_col = -1;
|
|
|
|
switch (result)
|
|
{
|
|
case COPY_OK:
|
|
return SQL_SUCCESS;
|
|
|
|
case COPY_UNSUPPORTED_TYPE:
|
|
stmt->errormsg = "Received an unsupported type from Postgres.";
|
|
stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
|
|
case COPY_UNSUPPORTED_CONVERSION:
|
|
stmt->errormsg = "Couldn't handle the necessary data type conversion.";
|
|
stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
|
|
case COPY_RESULT_TRUNCATED:
|
|
stmt->errornumber = STMT_TRUNCATED;
|
|
stmt->errormsg = "The buffer was too small for the result.";
|
|
return SQL_SUCCESS_WITH_INFO;
|
|
|
|
case COPY_GENERAL_ERROR: /* error msg already filled in */
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
|
|
case COPY_NO_DATA_FOUND:
|
|
/* SC_log_error(func, "no data found", stmt); */
|
|
return SQL_NO_DATA_FOUND;
|
|
|
|
default:
|
|
stmt->errormsg = "Unrecognized return value from copy_and_convert_field.";
|
|
stmt->errornumber = STMT_INTERNAL_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Returns data for bound columns in the current row ("hstmt->iCursor"), */
|
|
/* advances the cursor. */
|
|
|
|
RETCODE SQL_API
|
|
SQLFetch(
|
|
HSTMT hstmt)
|
|
{
|
|
static char *func = "SQLFetch";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *res;
|
|
|
|
mylog("SQLFetch: stmt = %u, stmt->result= %u\n", stmt, stmt->result);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
SC_clear_error(stmt);
|
|
|
|
if (!(res = stmt->result))
|
|
{
|
|
stmt->errormsg = "Null statement result in SQLFetch.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/* Not allowed to bind a bookmark column when using SQLFetch. */
|
|
if (stmt->bookmark.buffer)
|
|
{
|
|
stmt->errornumber = STMT_COLNUM_ERROR;
|
|
stmt->errormsg = "Not allowed to bind a bookmark column when using SQLFetch";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status == STMT_EXECUTING)
|
|
{
|
|
stmt->errormsg = "Can't fetch while statement is still executing.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
|
|
if (stmt->status != STMT_FINISHED)
|
|
{
|
|
stmt->errornumber = STMT_STATUS_ERROR;
|
|
stmt->errormsg = "Fetch can only be called after the successful execution on a SQL statement";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->bindings == NULL)
|
|
{
|
|
/* just to avoid a crash if the user insists on calling this */
|
|
/* function even if SQL_ExecDirect has reported an Error */
|
|
stmt->errormsg = "Bindings were not allocated properly.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
QR_set_rowset_size(res, 1);
|
|
QR_inc_base(res, stmt->last_fetch_count);
|
|
|
|
return SC_fetch(stmt);
|
|
}
|
|
|
|
/* This fetchs a block of data (rowset). */
|
|
|
|
RETCODE SQL_API
|
|
SQLExtendedFetch(
|
|
HSTMT hstmt,
|
|
UWORD fFetchType,
|
|
SDWORD irow,
|
|
UDWORD FAR *pcrow,
|
|
UWORD FAR *rgfRowStatus)
|
|
{
|
|
static char *func = "SQLExtendedFetch";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *res;
|
|
int num_tuples,
|
|
i,
|
|
save_rowset_size;
|
|
RETCODE result;
|
|
char truncated,
|
|
error;
|
|
|
|
mylog("SQLExtendedFetch: stmt=%u\n", stmt);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
if (globals.use_declarefetch && !stmt->manual_result)
|
|
{
|
|
if (fFetchType != SQL_FETCH_NEXT)
|
|
{
|
|
stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
|
|
stmt->errormsg = "Unsupported fetch type for SQLExtendedFetch with UseDeclareFetch option.";
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
|
|
SC_clear_error(stmt);
|
|
|
|
if (!(res = stmt->result))
|
|
{
|
|
stmt->errormsg = "Null statement result in SQLExtendedFetch.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* If a bookmark colunmn is bound but bookmark usage is off, then
|
|
* error
|
|
*/
|
|
if (stmt->bookmark.buffer && stmt->options.use_bookmarks == SQL_UB_OFF)
|
|
{
|
|
stmt->errornumber = STMT_COLNUM_ERROR;
|
|
stmt->errormsg = "Attempt to retrieve bookmark with bookmark usage disabled";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status == STMT_EXECUTING)
|
|
{
|
|
stmt->errormsg = "Can't fetch while statement is still executing.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status != STMT_FINISHED)
|
|
{
|
|
stmt->errornumber = STMT_STATUS_ERROR;
|
|
stmt->errormsg = "ExtendedFetch can only be called after the successful execution on a SQL statement";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->bindings == NULL)
|
|
{
|
|
/* just to avoid a crash if the user insists on calling this */
|
|
/* function even if SQL_ExecDirect has reported an Error */
|
|
stmt->errormsg = "Bindings were not allocated properly.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/* Initialize to no rows fetched */
|
|
if (rgfRowStatus)
|
|
for (i = 0; i < stmt->options.rowset_size; i++)
|
|
*(rgfRowStatus + i) = SQL_ROW_NOROW;
|
|
|
|
if (pcrow)
|
|
*pcrow = 0;
|
|
|
|
num_tuples = QR_get_num_tuples(res);
|
|
|
|
/* Save and discard the saved rowset size */
|
|
save_rowset_size = stmt->save_rowset_size;
|
|
stmt->save_rowset_size = -1;
|
|
|
|
switch (fFetchType)
|
|
{
|
|
case SQL_FETCH_NEXT:
|
|
|
|
/*
|
|
* From the odbc spec... If positioned before the start of the
|
|
* RESULT SET, then this should be equivalent to
|
|
* SQL_FETCH_FIRST.
|
|
*/
|
|
|
|
if (stmt->rowset_start < 0)
|
|
stmt->rowset_start = 0;
|
|
|
|
else
|
|
{
|
|
|
|
stmt->rowset_start += (save_rowset_size > 0 ? save_rowset_size : stmt->options.rowset_size);
|
|
}
|
|
|
|
mylog("SQL_FETCH_NEXT: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);
|
|
break;
|
|
|
|
case SQL_FETCH_PRIOR:
|
|
mylog("SQL_FETCH_PRIOR: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);
|
|
|
|
|
|
/*
|
|
* From the odbc spec... If positioned after the end of the
|
|
* RESULT SET, then this should be equivalent to
|
|
* SQL_FETCH_LAST.
|
|
*/
|
|
|
|
if (stmt->rowset_start >= num_tuples)
|
|
{
|
|
stmt->rowset_start = num_tuples <= 0 ? 0 : (num_tuples - stmt->options.rowset_size);
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
stmt->rowset_start -= stmt->options.rowset_size;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SQL_FETCH_FIRST:
|
|
mylog("SQL_FETCH_FIRST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);
|
|
|
|
stmt->rowset_start = 0;
|
|
break;
|
|
|
|
case SQL_FETCH_LAST:
|
|
mylog("SQL_FETCH_LAST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple);
|
|
|
|
stmt->rowset_start = num_tuples <= 0 ? 0 : (num_tuples - stmt->options.rowset_size);
|
|
break;
|
|
|
|
case SQL_FETCH_ABSOLUTE:
|
|
mylog("SQL_FETCH_ABSOLUTE: num_tuples=%d, currtuple=%d, irow=%d\n", num_tuples, stmt->currTuple, irow);
|
|
|
|
/* Position before result set, but dont fetch anything */
|
|
if (irow == 0)
|
|
{
|
|
stmt->rowset_start = -1;
|
|
stmt->currTuple = -1;
|
|
return SQL_NO_DATA_FOUND;
|
|
}
|
|
/* Position before the desired row */
|
|
else if (irow > 0)
|
|
stmt->rowset_start = irow - 1;
|
|
/* Position with respect to the end of the result set */
|
|
else
|
|
stmt->rowset_start = num_tuples + irow;
|
|
|
|
break;
|
|
|
|
case SQL_FETCH_RELATIVE:
|
|
|
|
/*
|
|
* Refresh the current rowset -- not currently implemented,
|
|
* but lie anyway
|
|
*/
|
|
if (irow == 0)
|
|
break;
|
|
|
|
stmt->rowset_start += irow;
|
|
|
|
|
|
break;
|
|
|
|
case SQL_FETCH_BOOKMARK:
|
|
|
|
stmt->rowset_start = irow - 1;
|
|
break;
|
|
|
|
default:
|
|
SC_log_error(func, "Unsupported SQLExtendedFetch Direction", stmt);
|
|
return SQL_ERROR;
|
|
|
|
}
|
|
|
|
|
|
/***********************************/
|
|
/* CHECK FOR PROPER CURSOR STATE */
|
|
/***********************************/
|
|
|
|
/*
|
|
* Handle Declare Fetch style specially because the end is not really
|
|
* the end...
|
|
*/
|
|
if (globals.use_declarefetch && !stmt->manual_result)
|
|
{
|
|
if (QR_end_tuples(res))
|
|
return SQL_NO_DATA_FOUND;
|
|
}
|
|
else
|
|
{
|
|
/* If *new* rowset is after the result_set, return no data found */
|
|
if (stmt->rowset_start >= num_tuples)
|
|
{
|
|
stmt->rowset_start = num_tuples;
|
|
return SQL_NO_DATA_FOUND;
|
|
}
|
|
}
|
|
|
|
/* If *new* rowset is prior to result_set, return no data found */
|
|
if (stmt->rowset_start < 0)
|
|
{
|
|
if (stmt->rowset_start + stmt->options.rowset_size <= 0)
|
|
{
|
|
stmt->rowset_start = -1;
|
|
return SQL_NO_DATA_FOUND;
|
|
}
|
|
else
|
|
{ /* overlap with beginning of result set,
|
|
* so get first rowset */
|
|
stmt->rowset_start = 0;
|
|
}
|
|
}
|
|
|
|
/* currTuple is always 1 row prior to the rowset */
|
|
stmt->currTuple = stmt->rowset_start - 1;
|
|
|
|
/* increment the base row in the tuple cache */
|
|
QR_set_rowset_size(res, stmt->options.rowset_size);
|
|
QR_inc_base(res, stmt->last_fetch_count);
|
|
|
|
/* Physical Row advancement occurs for each row fetched below */
|
|
|
|
mylog("SQLExtendedFetch: new currTuple = %d\n", stmt->currTuple);
|
|
|
|
truncated = error = FALSE;
|
|
for (i = 0; i < stmt->options.rowset_size; i++)
|
|
{
|
|
|
|
stmt->bind_row = i; /* set the binding location */
|
|
result = SC_fetch(stmt);
|
|
|
|
/* Determine Function status */
|
|
if (result == SQL_NO_DATA_FOUND)
|
|
break;
|
|
else if (result == SQL_SUCCESS_WITH_INFO)
|
|
truncated = TRUE;
|
|
else if (result == SQL_ERROR)
|
|
error = TRUE;
|
|
|
|
/* Determine Row Status */
|
|
if (rgfRowStatus)
|
|
{
|
|
if (result == SQL_ERROR)
|
|
*(rgfRowStatus + i) = SQL_ROW_ERROR;
|
|
else
|
|
*(rgfRowStatus + i) = SQL_ROW_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* Save the fetch count for SQLSetPos */
|
|
stmt->last_fetch_count = i;
|
|
|
|
/* Reset next binding row */
|
|
stmt->bind_row = 0;
|
|
|
|
/* Move the cursor position to the first row in the result set. */
|
|
stmt->currTuple = stmt->rowset_start;
|
|
|
|
/* For declare/fetch, need to reset cursor to beginning of rowset */
|
|
if (globals.use_declarefetch && !stmt->manual_result)
|
|
QR_set_position(res, 0);
|
|
|
|
/* Set the number of rows retrieved */
|
|
if (pcrow)
|
|
*pcrow = i;
|
|
|
|
if (i == 0)
|
|
return SQL_NO_DATA_FOUND; /* Only DeclareFetch should wind
|
|
* up here */
|
|
else if (error)
|
|
return SQL_ERROR;
|
|
else if (truncated)
|
|
return SQL_SUCCESS_WITH_INFO;
|
|
else
|
|
return SQL_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
/* This determines whether there are more results sets available for */
|
|
/* the "hstmt". */
|
|
|
|
/* CC: return SQL_NO_DATA_FOUND since we do not support multiple result sets */
|
|
RETCODE SQL_API
|
|
SQLMoreResults(
|
|
HSTMT hstmt)
|
|
{
|
|
return SQL_NO_DATA_FOUND;
|
|
}
|
|
|
|
/* This positions the cursor within a rowset, that was positioned using SQLExtendedFetch. */
|
|
/* This will be useful (so far) only when using SQLGetData after SQLExtendedFetch. */
|
|
RETCODE SQL_API
|
|
SQLSetPos(
|
|
HSTMT hstmt,
|
|
UWORD irow,
|
|
UWORD fOption,
|
|
UWORD fLock)
|
|
{
|
|
static char *func = "SQLSetPos";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *res;
|
|
int num_cols,
|
|
i;
|
|
BindInfoClass *bindings = stmt->bindings;
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
if (fOption != SQL_POSITION && fOption != SQL_REFRESH)
|
|
{
|
|
stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR;
|
|
stmt->errormsg = "Only SQL_POSITION/REFRESH is supported for SQLSetPos";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (!(res = stmt->result))
|
|
{
|
|
stmt->errormsg = "Null statement result in SQLSetPos.";
|
|
stmt->errornumber = STMT_SEQUENCE_ERROR;
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
num_cols = QR_NumResultCols(res);
|
|
|
|
if (irow == 0)
|
|
{
|
|
stmt->errornumber = STMT_ROW_OUT_OF_RANGE;
|
|
stmt->errormsg = "Driver does not support Bulk operations.";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (irow > stmt->last_fetch_count)
|
|
{
|
|
stmt->errornumber = STMT_ROW_OUT_OF_RANGE;
|
|
stmt->errormsg = "Row value out of range";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
irow--;
|
|
|
|
/* Reset for SQLGetData */
|
|
for (i = 0; i < num_cols; i++)
|
|
bindings[i].data_left = -1;
|
|
|
|
QR_set_position(res, irow);
|
|
|
|
stmt->currTuple = stmt->rowset_start + irow;
|
|
|
|
return SQL_SUCCESS;
|
|
|
|
}
|
|
|
|
/* Sets options that control the behavior of cursors. */
|
|
|
|
RETCODE SQL_API
|
|
SQLSetScrollOptions(
|
|
HSTMT hstmt,
|
|
UWORD fConcurrency,
|
|
SDWORD crowKeyset,
|
|
UWORD crowRowset)
|
|
{
|
|
static char *func = "SQLSetScrollOptions";
|
|
|
|
SC_log_error(func, "Function not implemented", (StatementClass *) hstmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
|
|
/* Set the cursor name on a statement handle */
|
|
|
|
RETCODE SQL_API
|
|
SQLSetCursorName(
|
|
HSTMT hstmt,
|
|
UCHAR FAR *szCursor,
|
|
SWORD cbCursor)
|
|
{
|
|
static char *func = "SQLSetCursorName";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
int len;
|
|
|
|
mylog("SQLSetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d\n", hstmt, szCursor, cbCursor);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
len = (cbCursor == SQL_NTS) ? strlen(szCursor) : cbCursor;
|
|
|
|
if (len <= 0 || len > sizeof(stmt->cursor_name) - 1)
|
|
{
|
|
stmt->errornumber = STMT_INVALID_CURSOR_NAME;
|
|
stmt->errormsg = "Invalid Cursor Name";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
strncpy_null(stmt->cursor_name, szCursor, len + 1);
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
/* Return the cursor name for a statement handle */
|
|
|
|
RETCODE SQL_API
|
|
SQLGetCursorName(
|
|
HSTMT hstmt,
|
|
UCHAR FAR *szCursor,
|
|
SWORD cbCursorMax,
|
|
SWORD FAR *pcbCursor)
|
|
{
|
|
static char *func = "SQLGetCursorName";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
int len = 0;
|
|
RETCODE result;
|
|
|
|
mylog("SQLGetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d, pcbCursor=%u\n", hstmt, szCursor, cbCursorMax, pcbCursor);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, "", NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
if (stmt->cursor_name[0] == '\0')
|
|
{
|
|
stmt->errornumber = STMT_NO_CURSOR_NAME;
|
|
stmt->errormsg = "No Cursor name available";
|
|
SC_log_error(func, "", stmt);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
result = SQL_SUCCESS;
|
|
len = strlen(stmt->cursor_name);
|
|
|
|
if (szCursor)
|
|
{
|
|
strncpy_null(szCursor, stmt->cursor_name, cbCursorMax);
|
|
|
|
if (len >= cbCursorMax)
|
|
{
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
stmt->errornumber = STMT_TRUNCATED;
|
|
stmt->errormsg = "The buffer was too small for the result.";
|
|
}
|
|
}
|
|
|
|
if (pcbCursor)
|
|
*pcbCursor = len;
|
|
|
|
return result;
|
|
}
|