5133 lines
133 KiB
C
5133 lines
133 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, SQLSetPos, SQLSetScrollOptions(NI),
|
|
* SQLSetCursorName, SQLGetCursorName
|
|
*
|
|
* Comments: See "readme.txt" for copyright and license information.
|
|
*-------
|
|
*/
|
|
|
|
#include "psqlodbc.h"
|
|
|
|
#include <string.h>
|
|
#include "misc.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>
|
|
#include <limits.h>
|
|
|
|
#include "pgapifunc.h"
|
|
|
|
/* Helper macro */
|
|
#define getEffectiveOid(conn, fi) pg_true_type((conn), (fi)->columntype, FI_type(fi))
|
|
#define NULL_IF_NULL(a) ((a) ? ((const char *)(a)) : "(null)")
|
|
|
|
|
|
RETCODE SQL_API
|
|
PGAPI_RowCount(HSTMT hstmt,
|
|
SQLLEN * pcrow)
|
|
{
|
|
CSTR func = "PGAPI_RowCount";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *res;
|
|
|
|
MYLOG(0, "entering...\n");
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
if (stmt->proc_return > 0)
|
|
{
|
|
*pcrow = 0;
|
|
MYLOG(DETAIL_LOG_LEVEL, "returning RowCount=" FORMAT_LEN "\n", *pcrow);
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
res = SC_get_Curres(stmt);
|
|
if (res)
|
|
{
|
|
if (stmt->status != STMT_FINISHED)
|
|
{
|
|
SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Can't get row count while statement is still executing.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
if (res->recent_processed_row_count >= 0)
|
|
{
|
|
*pcrow = res->recent_processed_row_count;
|
|
MYLOG(0, "**** THE ROWS: *pcrow = " FORMAT_LEN "\n", *pcrow);
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
else if (QR_NumResultCols(res) > 0)
|
|
{
|
|
*pcrow = QR_get_cursor(res) ? -1 : QR_get_num_total_tuples(res) - res->dl_count;
|
|
MYLOG(0, "RowCount=" FORMAT_LEN "\n", *pcrow);
|
|
return SQL_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
static BOOL
|
|
SC_describe_ok(StatementClass *stmt, BOOL build_fi, int col_idx, const char *func)
|
|
{
|
|
Int2 num_fields;
|
|
QResultClass *result;
|
|
BOOL exec_ok = TRUE;
|
|
|
|
num_fields = SC_describe(stmt);
|
|
result = SC_get_Curres(stmt);
|
|
|
|
MYLOG(0, "entering result = %p, status = %d, numcols = %d\n", result, stmt->status, result != NULL ? QR_NumResultCols(result) : -1);
|
|
/****if ((!result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) ****/
|
|
if (!QR_command_maybe_successful(result) || num_fields < 0)
|
|
{
|
|
/* no query has been executed on this statement */
|
|
SC_set_error(stmt, STMT_EXEC_ERROR, "No query has been executed with that handle", func);
|
|
exec_ok = FALSE;
|
|
}
|
|
else if (col_idx >= 0 && col_idx < num_fields)
|
|
{
|
|
OID reloid = QR_get_relid(result, col_idx);
|
|
IRDFields *irdflds = SC_get_IRDF(stmt);
|
|
FIELD_INFO *fi;
|
|
TABLE_INFO *ti = NULL;
|
|
BOOL found = 0;
|
|
MYLOG(DETAIL_LOG_LEVEL, "build_fi=%d reloid=%u\n", build_fi, reloid);
|
|
if (build_fi && 0 != QR_get_attid(result, col_idx))
|
|
{
|
|
found = getCOLIfromTI(func, NULL, stmt, reloid, &ti);
|
|
if(!found && stmt->hdbc && stmt->hdbc->status == CONN_DOWN)
|
|
exec_ok = FALSE;
|
|
}
|
|
MYLOG(DETAIL_LOG_LEVEL, "nfields=%d\n", irdflds->nfields);
|
|
if (irdflds->fi && col_idx < (int) irdflds->nfields)
|
|
{
|
|
fi = irdflds->fi[col_idx];
|
|
if (fi)
|
|
{
|
|
if (ti)
|
|
{
|
|
if (NULL == fi->ti)
|
|
fi->ti = ti;
|
|
if (!FI_is_applicable(fi)
|
|
&& 0 != (ti->flags & TI_COLATTRIBUTE))
|
|
fi->flag |= FIELD_COL_ATTRIBUTE;
|
|
}
|
|
fi->basetype = QR_get_field_type(result, col_idx);
|
|
if (0 == fi->columntype)
|
|
fi->columntype = fi->basetype;
|
|
}
|
|
}
|
|
}
|
|
return exec_ok;
|
|
}
|
|
|
|
/*
|
|
* This returns the number of columns associated with the database
|
|
* attached to "hstmt".
|
|
*/
|
|
RETCODE SQL_API
|
|
PGAPI_NumResultCols(HSTMT hstmt,
|
|
SQLSMALLINT * pccol)
|
|
{
|
|
CSTR func = "PGAPI_NumResultCols";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *result;
|
|
char parse_ok;
|
|
RETCODE ret = SQL_SUCCESS;
|
|
|
|
MYLOG(0, "entering...\n");
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
SC_clear_error(stmt);
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
|
|
if (stmt->proc_return > 0)
|
|
{
|
|
*pccol = 0;
|
|
goto cleanup;
|
|
}
|
|
parse_ok = FALSE;
|
|
if (!stmt->catalog_result && SC_is_parse_forced(stmt) && SC_can_parse_statement(stmt))
|
|
{
|
|
if (SC_parsed_status(stmt) == STMT_PARSE_NONE)
|
|
{
|
|
MYLOG(0, "calling parse_statement on stmt=%p\n", stmt);
|
|
parse_statement(stmt, FALSE);
|
|
}
|
|
|
|
if (SC_parsed_status(stmt) != STMT_PARSE_FATAL)
|
|
{
|
|
parse_ok = TRUE;
|
|
*pccol = SC_get_IRDF(stmt)->nfields;
|
|
MYLOG(0, "PARSE: *pccol = %d\n", *pccol);
|
|
}
|
|
}
|
|
|
|
if (!parse_ok)
|
|
{
|
|
if (!SC_describe_ok(stmt, FALSE, -1, func))
|
|
{
|
|
ret = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
result = SC_get_Curres(stmt);
|
|
*pccol = QR_NumPublicResultCols(result);
|
|
}
|
|
|
|
cleanup:
|
|
#undef return
|
|
return ret;
|
|
}
|
|
|
|
#define USE_FI(fi, unknown) (fi && UNKNOWNS_AS_LONGEST != unknown)
|
|
|
|
/*
|
|
* Return information about the database column the user wants
|
|
* information about.
|
|
*/
|
|
RETCODE SQL_API
|
|
PGAPI_DescribeCol(HSTMT hstmt,
|
|
SQLUSMALLINT icol,
|
|
SQLCHAR * szColName,
|
|
SQLSMALLINT cbColNameMax,
|
|
SQLSMALLINT * pcbColName,
|
|
SQLSMALLINT * pfSqlType,
|
|
SQLULEN * pcbColDef,
|
|
SQLSMALLINT * pibScale,
|
|
SQLSMALLINT * pfNullable)
|
|
{
|
|
CSTR func = "PGAPI_DescribeCol";
|
|
|
|
/* gets all the information about a specific column */
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
ConnectionClass *conn;
|
|
IRDFields *irdflds;
|
|
QResultClass *res = NULL;
|
|
char *col_name = NULL;
|
|
OID fieldtype = 0;
|
|
SQLLEN column_size = 0;
|
|
int unknown_sizes;
|
|
SQLINTEGER decimal_digits = 0;
|
|
ConnInfo *ci;
|
|
FIELD_INFO *fi;
|
|
char buf[255];
|
|
int len = 0;
|
|
RETCODE result = SQL_SUCCESS;
|
|
|
|
MYLOG(0, "entering.%d..\n", icol);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
conn = SC_get_conn(stmt);
|
|
ci = &(conn->connInfo);
|
|
unknown_sizes = ci->drivers.unknown_sizes;
|
|
|
|
SC_clear_error(stmt);
|
|
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
irdflds = SC_get_IRDF(stmt);
|
|
if (0 == icol) /* bookmark column */
|
|
{
|
|
SQLSMALLINT fType = stmt->options.use_bookmarks == SQL_UB_VARIABLE ? SQL_BINARY : SQL_INTEGER;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "answering bookmark info\n");
|
|
if (szColName && cbColNameMax > 0)
|
|
*szColName = '\0';
|
|
if (pcbColName)
|
|
*pcbColName = 0;
|
|
if (pfSqlType)
|
|
*pfSqlType = fType;
|
|
if (pcbColDef)
|
|
*pcbColDef = 10;
|
|
if (pibScale)
|
|
*pibScale = 0;
|
|
if (pfNullable)
|
|
*pfNullable = SQL_NO_NULLS;
|
|
result = SQL_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Dont check for bookmark column. This is the responsibility of the
|
|
* driver manager.
|
|
*/
|
|
|
|
icol--; /* use zero based column numbers */
|
|
|
|
fi = NULL;
|
|
if (icol < irdflds->nfields && irdflds->fi)
|
|
fi = irdflds->fi[icol];
|
|
if (!FI_is_applicable(fi) && !stmt->catalog_result && SC_is_parse_forced(stmt) && SC_can_parse_statement(stmt))
|
|
{
|
|
if (SC_parsed_status(stmt) == STMT_PARSE_NONE)
|
|
{
|
|
MYLOG(0, "calling parse_statement on stmt=%p\n", stmt);
|
|
parse_statement(stmt, FALSE);
|
|
}
|
|
|
|
MYLOG(0, "PARSE: icol=%d, stmt=%p, stmt->nfld=%d, stmt->fi=%p\n", icol, stmt, irdflds->nfields, irdflds->fi);
|
|
|
|
if (SC_parsed_status(stmt) != STMT_PARSE_FATAL && irdflds->fi)
|
|
{
|
|
if (icol < irdflds->nfields)
|
|
fi = irdflds->fi[icol];
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_COLUMN_NUMBER_ERROR, "Invalid column number in DescribeCol.", func);
|
|
result = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
MYLOG(0, "getting info for icol=%d\n", icol);
|
|
}
|
|
}
|
|
|
|
if (!FI_is_applicable(fi))
|
|
{
|
|
/*
|
|
* 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.
|
|
*/
|
|
BOOL build_fi = (NULL != pfNullable || NULL != pfSqlType);
|
|
fi = NULL;
|
|
if (!SC_describe_ok(stmt, build_fi, icol, func))
|
|
{
|
|
result = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
res = SC_get_Curres(stmt);
|
|
if (icol >= QR_NumPublicResultCols(res))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_COLUMN_NUMBER_ERROR, "Invalid column number in DescribeCol.", func);
|
|
SPRINTF_FIXED(buf, "Col#=%d, #Cols=%d,%d keys=%d", icol, QR_NumResultCols(res), QR_NumPublicResultCols(res), res->num_key_fields);
|
|
SC_log_error(func, buf, stmt);
|
|
result = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
if (icol < irdflds->nfields && irdflds->fi)
|
|
fi = irdflds->fi[icol];
|
|
}
|
|
res = SC_get_Curres(stmt);
|
|
#ifdef SUPPRESS_LONGEST_ON_CURSORS
|
|
if (UNKNOWNS_AS_LONGEST == unknown_sizes)
|
|
{
|
|
if (QR_once_reached_eof(res))
|
|
unknown_sizes = UNKNOWNS_AS_LONGEST;
|
|
else
|
|
unknown_sizes = UNKNOWNS_AS_MAX;
|
|
}
|
|
#endif /* SUPPRESS_LONGEST_ON_CURSORS */
|
|
/* handle constants */
|
|
if (res &&
|
|
-2 == QR_get_fieldsize(res, icol))
|
|
unknown_sizes = UNKNOWNS_AS_LONGEST;
|
|
|
|
if (FI_is_applicable(fi))
|
|
{
|
|
fieldtype = getEffectiveOid(conn, fi);
|
|
if (NAME_IS_VALID(fi->column_alias))
|
|
col_name = GET_NAME(fi->column_alias);
|
|
else
|
|
col_name = GET_NAME(fi->column_name);
|
|
if (USE_FI(fi, unknown_sizes))
|
|
{
|
|
column_size = fi->column_size;
|
|
decimal_digits = fi->decimal_digits;
|
|
}
|
|
else
|
|
{
|
|
column_size = pgtype_column_size(stmt, fieldtype, icol, unknown_sizes);
|
|
decimal_digits = pgtype_decimal_digits(stmt, fieldtype, icol);
|
|
}
|
|
|
|
MYLOG(0, "PARSE: fieldtype=%u, col_name='%s', column_size=" FORMAT_LEN "\n", fieldtype, NULL_IF_NULL(col_name), column_size);
|
|
}
|
|
else
|
|
{
|
|
col_name = QR_get_fieldname(res, icol);
|
|
fieldtype = QR_get_field_type(res, icol);
|
|
|
|
column_size = pgtype_column_size(stmt, fieldtype, icol, unknown_sizes);
|
|
decimal_digits = pgtype_decimal_digits(stmt, fieldtype, icol);
|
|
}
|
|
|
|
MYLOG(0, "col %d fieldname = '%s'\n", icol, NULL_IF_NULL(col_name));
|
|
MYLOG(0, "col %d fieldtype = %d\n", icol, fieldtype);
|
|
MYLOG(0, "col %d column_size = " FORMAT_LEN "\n", icol, column_size);
|
|
|
|
result = SQL_SUCCESS;
|
|
|
|
/*
|
|
* COLUMN NAME
|
|
*/
|
|
len = col_name ? (int) strlen(col_name) : 0;
|
|
|
|
if (pcbColName)
|
|
*pcbColName = len;
|
|
|
|
if (szColName && cbColNameMax > 0)
|
|
{
|
|
if (NULL != col_name)
|
|
strncpy_null((char *) szColName, col_name, cbColNameMax);
|
|
else
|
|
szColName[0] = '\0';
|
|
|
|
if (len >= cbColNameMax)
|
|
{
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
SC_set_error(stmt, STMT_TRUNCATED, "The buffer was too small for the colName.", func);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CONCISE(SQL) TYPE
|
|
*/
|
|
if (pfSqlType)
|
|
{
|
|
*pfSqlType = pgtype_to_concise_type(stmt, fieldtype, icol, unknown_sizes);
|
|
|
|
MYLOG(0, "col %d *pfSqlType = %d\n", icol, *pfSqlType);
|
|
}
|
|
|
|
/*
|
|
* COLUMN SIZE(PRECISION in 2.x)
|
|
*/
|
|
if (pcbColDef)
|
|
{
|
|
if (column_size < 0)
|
|
column_size = 0; /* "I dont know" */
|
|
|
|
*pcbColDef = column_size;
|
|
|
|
MYLOG(0, "Col: col %d *pcbColDef = " FORMAT_ULEN "\n", icol, *pcbColDef);
|
|
}
|
|
|
|
/*
|
|
* DECIMAL DIGITS(SCALE in 2.x)
|
|
*/
|
|
if (pibScale)
|
|
{
|
|
if (decimal_digits < 0)
|
|
decimal_digits = 0;
|
|
|
|
*pibScale = (SQLSMALLINT) decimal_digits;
|
|
MYLOG(0, "col %d *pibScale = %d\n", icol, *pibScale);
|
|
}
|
|
|
|
/*
|
|
* NULLABILITY
|
|
*/
|
|
if (pfNullable)
|
|
{
|
|
if (SC_has_outer_join(stmt))
|
|
*pfNullable = TRUE;
|
|
else
|
|
*pfNullable = fi ? fi->nullable : pgtype_nullable(conn, fieldtype);
|
|
|
|
MYLOG(0, "col %d *pfNullable = %d\n", icol, *pfNullable);
|
|
}
|
|
|
|
cleanup:
|
|
#undef return
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/* Returns result column descriptor information for a result set. */
|
|
RETCODE SQL_API
|
|
PGAPI_ColAttributes(HSTMT hstmt,
|
|
SQLUSMALLINT icol,
|
|
SQLUSMALLINT fDescType,
|
|
PTR rgbDesc,
|
|
SQLSMALLINT cbDescMax,
|
|
SQLSMALLINT * pcbDesc,
|
|
SQLLEN * pfDesc)
|
|
{
|
|
CSTR func = "PGAPI_ColAttributes";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
IRDFields *irdflds;
|
|
OID field_type = 0;
|
|
Int2 col_idx;
|
|
ConnectionClass *conn;
|
|
ConnInfo *ci;
|
|
int column_size, unknown_sizes;
|
|
int cols = 0;
|
|
RETCODE result;
|
|
const char *p = NULL;
|
|
SQLLEN value = 0;
|
|
const FIELD_INFO *fi = NULL;
|
|
const TABLE_INFO *ti = NULL;
|
|
QResultClass *res;
|
|
BOOL stmt_updatable;
|
|
|
|
MYLOG(0, "entering..col=%d %d len=%d.\n", icol, fDescType,
|
|
cbDescMax);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
stmt_updatable = SC_is_updatable(stmt)
|
|
/* The following doesn't seem appropriate for client side cursors
|
|
&& stmt->options.scroll_concurrency != SQL_CONCUR_READ_ONLY
|
|
*/
|
|
;
|
|
|
|
if (pcbDesc)
|
|
*pcbDesc = 0;
|
|
irdflds = SC_get_IRDF(stmt);
|
|
conn = SC_get_conn(stmt);
|
|
ci = &(conn->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.
|
|
*/
|
|
|
|
res = SC_get_Curres(stmt);
|
|
if (0 == icol && SQL_DESC_COUNT != fDescType) /* bookmark column */
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "answering bookmark info\n");
|
|
switch (fDescType)
|
|
{
|
|
case SQL_DESC_OCTET_LENGTH:
|
|
if (pfDesc)
|
|
*pfDesc = 4;
|
|
break;
|
|
case SQL_DESC_TYPE:
|
|
if (pfDesc)
|
|
*pfDesc = stmt->options.use_bookmarks == SQL_UB_VARIABLE ? SQL_BINARY : SQL_INTEGER;
|
|
break;
|
|
}
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
col_idx = icol - 1;
|
|
|
|
unknown_sizes = ci->drivers.unknown_sizes;
|
|
|
|
/* not appropriate for SQLColAttributes() */
|
|
if (stmt->catalog_result)
|
|
unknown_sizes = UNKNOWNS_AS_LONGEST;
|
|
else if (unknown_sizes == UNKNOWNS_AS_DONTKNOW)
|
|
unknown_sizes = UNKNOWNS_AS_MAX;
|
|
|
|
if (!stmt->catalog_result && SC_is_parse_forced(stmt) && SC_can_parse_statement(stmt))
|
|
{
|
|
if (SC_parsed_status(stmt) == STMT_PARSE_NONE)
|
|
{
|
|
MYLOG(0, "calling parse_statement\n");
|
|
parse_statement(stmt, FALSE);
|
|
}
|
|
|
|
cols = irdflds->nfields;
|
|
|
|
/*
|
|
* Column Count is a special case. The Column number is ignored
|
|
* in this case.
|
|
*/
|
|
if (fDescType == SQL_DESC_COUNT)
|
|
{
|
|
if (pfDesc)
|
|
*pfDesc = cols;
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
if (SC_parsed_status(stmt) != STMT_PARSE_FATAL && irdflds->fi)
|
|
{
|
|
if (col_idx >= cols)
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_COLUMN_NUMBER_ERROR, "Invalid column number in ColAttributes.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (col_idx < irdflds->nfields && irdflds->fi)
|
|
fi = irdflds->fi[col_idx];
|
|
if (FI_is_applicable(fi))
|
|
field_type = getEffectiveOid(conn, fi);
|
|
else
|
|
{
|
|
BOOL build_fi = FALSE;
|
|
|
|
fi = NULL;
|
|
switch (fDescType)
|
|
{
|
|
case SQL_COLUMN_OWNER_NAME:
|
|
case SQL_COLUMN_TABLE_NAME:
|
|
case SQL_COLUMN_TYPE:
|
|
case SQL_COLUMN_TYPE_NAME:
|
|
case SQL_COLUMN_AUTO_INCREMENT:
|
|
case SQL_DESC_NULLABLE:
|
|
case SQL_DESC_BASE_TABLE_NAME:
|
|
case SQL_DESC_BASE_COLUMN_NAME:
|
|
case SQL_COLUMN_UPDATABLE:
|
|
case 1212: /* SQL_CA_SS_COLUMN_KEY ? */
|
|
build_fi = TRUE;
|
|
break;
|
|
}
|
|
if (!SC_describe_ok(stmt, build_fi, col_idx, func))
|
|
return SQL_ERROR;
|
|
|
|
res = SC_get_Curres(stmt);
|
|
cols = QR_NumPublicResultCols(res);
|
|
|
|
/*
|
|
* Column Count is a special case. The Column number is ignored
|
|
* in this case.
|
|
*/
|
|
if (fDescType == SQL_DESC_COUNT)
|
|
{
|
|
if (pfDesc)
|
|
*pfDesc = cols;
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
if (col_idx >= cols)
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_COLUMN_NUMBER_ERROR, "Invalid column number in ColAttributes.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
field_type = QR_get_field_type(res, col_idx);
|
|
if (col_idx < irdflds->nfields && irdflds->fi)
|
|
fi = irdflds->fi[col_idx];
|
|
}
|
|
if (FI_is_applicable(fi))
|
|
{
|
|
ti = fi->ti;
|
|
field_type = getEffectiveOid(conn, fi);
|
|
}
|
|
|
|
MYLOG(0, "col %d field_type=%d fi,ti=%p,%p\n", col_idx, field_type, fi, ti);
|
|
|
|
#ifdef SUPPRESS_LONGEST_ON_CURSORS
|
|
if (UNKNOWNS_AS_LONGEST == unknown_sizes)
|
|
{
|
|
if (QR_once_reached_eof(res))
|
|
unknown_sizes = UNKNOWNS_AS_LONGEST;
|
|
else
|
|
unknown_sizes = UNKNOWNS_AS_MAX;
|
|
}
|
|
#endif /* SUPPRESS_LONGEST_ON_CURSORS */
|
|
/* handle constants */
|
|
if (res &&
|
|
-2 == QR_get_fieldsize(res, col_idx))
|
|
unknown_sizes = UNKNOWNS_AS_LONGEST;
|
|
|
|
column_size = (USE_FI(fi, unknown_sizes) && fi->column_size > 0) ? fi->column_size : pgtype_column_size(stmt, field_type, col_idx, unknown_sizes);
|
|
switch (fDescType)
|
|
{
|
|
case SQL_COLUMN_AUTO_INCREMENT: /* == SQL_DESC_AUTO_UNIQUE_VALUE */
|
|
if (fi && fi->auto_increment)
|
|
value = TRUE;
|
|
else
|
|
value = pgtype_auto_increment(conn, field_type);
|
|
if (value == -1) /* non-numeric becomes FALSE (ODBC Doc) */
|
|
value = FALSE;
|
|
MYLOG(0, "AUTO_INCREMENT=" FORMAT_LEN "\n", value);
|
|
|
|
break;
|
|
|
|
case SQL_COLUMN_CASE_SENSITIVE: /* == SQL_DESC_CASE_SENSITIVE */
|
|
value = pgtype_case_sensitive(conn, field_type);
|
|
break;
|
|
|
|
/*
|
|
* This special case is handled above.
|
|
*
|
|
* case SQL_COLUMN_COUNT:
|
|
*/
|
|
case SQL_COLUMN_DISPLAY_SIZE: /* == SQL_DESC_DISPLAY_SIZE */
|
|
value = (USE_FI(fi, unknown_sizes) && 0 != fi->display_size) ? fi->display_size : pgtype_display_size(stmt, field_type, col_idx, unknown_sizes);
|
|
|
|
MYLOG(0, "col %d, display_size= " FORMAT_LEN "\n", col_idx, value);
|
|
|
|
break;
|
|
|
|
case SQL_COLUMN_LABEL: /* == SQL_DESC_LABEL */
|
|
if (fi && (NAME_IS_VALID(fi->column_alias)))
|
|
{
|
|
p = GET_NAME(fi->column_alias);
|
|
|
|
MYLOG(0, "COLUMN_LABEL = '%s'\n", p);
|
|
break;
|
|
}
|
|
/* otherwise same as column name -- FALL THROUGH!!! */
|
|
|
|
case SQL_DESC_NAME:
|
|
MYLOG(DETAIL_LOG_LEVEL, "fi=%p (alias, name)=", fi);
|
|
if (fi)
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "(%s,%s)\n", PRINT_NAME(fi->column_alias), PRINT_NAME(fi->column_name));
|
|
else
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "NULL\n");
|
|
p = fi ? (NAME_IS_NULL(fi->column_alias) ? SAFE_NAME(fi->column_name) : GET_NAME(fi->column_alias)) : QR_get_fieldname(res, col_idx);
|
|
|
|
MYLOG(0, "COLUMN_NAME = '%s'\n", p);
|
|
break;
|
|
|
|
case SQL_COLUMN_LENGTH:
|
|
value = (USE_FI(fi, unknown_sizes) && fi->length > 0) ? fi->length : pgtype_buffer_length(stmt, field_type, col_idx, unknown_sizes);
|
|
if (0 > value)
|
|
/* if (-1 == value) I'm not sure which is right */
|
|
value = 0;
|
|
|
|
MYLOG(0, "col %d, column_length = " FORMAT_LEN "\n", col_idx, value);
|
|
break;
|
|
|
|
case SQL_COLUMN_MONEY: /* == SQL_DESC_FIXED_PREC_SCALE */
|
|
value = pgtype_money(conn, field_type);
|
|
MYLOG(DETAIL_LOG_LEVEL, "COLUMN_MONEY=" FORMAT_LEN "\n", value);
|
|
break;
|
|
|
|
case SQL_DESC_NULLABLE:
|
|
if (SC_has_outer_join(stmt))
|
|
value = TRUE;
|
|
else
|
|
value = fi ? fi->nullable : pgtype_nullable(conn, field_type);
|
|
MYLOG(DETAIL_LOG_LEVEL, "COLUMN_NULLABLE=" FORMAT_LEN "\n", value);
|
|
break;
|
|
|
|
case SQL_COLUMN_OWNER_NAME: /* == SQL_DESC_SCHEMA_NAME */
|
|
p = ti ? SAFE_NAME(ti->schema_name) : NULL_STRING;
|
|
MYLOG(0, "SCHEMA_NAME = '%s'\n", p);
|
|
break;
|
|
|
|
case SQL_COLUMN_PRECISION: /* in 2.x */
|
|
value = column_size;
|
|
if (value < 0)
|
|
value = 0;
|
|
|
|
MYLOG(0, "col %d, column_size = " FORMAT_LEN "\n", col_idx, value);
|
|
break;
|
|
|
|
case SQL_COLUMN_QUALIFIER_NAME: /* == SQL_DESC_CATALOG_NAME */
|
|
p = ti ? CurrCatString(conn) : NULL_STRING; /* empty string means *not supported* */
|
|
break;
|
|
|
|
case SQL_COLUMN_SCALE: /* in 2.x */
|
|
value = pgtype_decimal_digits(stmt, field_type, col_idx);
|
|
MYLOG(DETAIL_LOG_LEVEL, "COLUMN_SCALE=" FORMAT_LEN "\n", value);
|
|
if (value < 0)
|
|
value = 0;
|
|
break;
|
|
|
|
case SQL_COLUMN_SEARCHABLE: /* == SQL_DESC_SEARCHABLE */
|
|
value = pgtype_searchable(conn, field_type);
|
|
break;
|
|
|
|
case SQL_COLUMN_TABLE_NAME: /* == SQL_DESC_TABLE_NAME */
|
|
p = ti ? SAFE_NAME(ti->table_name) : NULL_STRING;
|
|
|
|
MYLOG(0, "TABLE_NAME = '%s'\n", p);
|
|
break;
|
|
|
|
case SQL_COLUMN_TYPE: /* == SQL_DESC_CONCISE_TYPE */
|
|
value = pgtype_to_concise_type(stmt, field_type, col_idx, unknown_sizes);
|
|
MYLOG(0, "COLUMN_TYPE=" FORMAT_LEN "\n", value);
|
|
break;
|
|
|
|
case SQL_COLUMN_TYPE_NAME: /* == SQL_DESC_TYPE_NAME */
|
|
p = pgtype_to_name(stmt, field_type, col_idx, fi && fi->auto_increment);
|
|
break;
|
|
|
|
case SQL_COLUMN_UNSIGNED: /* == SQL_DESC_UNSINGED */
|
|
value = pgtype_unsigned(conn, field_type);
|
|
if (value == -1) /* non-numeric becomes TRUE (ODBC Doc) */
|
|
value = SQL_TRUE;
|
|
|
|
break;
|
|
|
|
case SQL_COLUMN_UPDATABLE: /* == SQL_DESC_UPDATABLE */
|
|
|
|
/*
|
|
* Neither Access or Borland care about this.
|
|
*
|
|
* if (field_type == PG_TYPE_OID) pfDesc = SQL_ATTR_READONLY;
|
|
* else
|
|
*/
|
|
if (!stmt_updatable)
|
|
value = SQL_ATTR_READONLY;
|
|
else
|
|
value = fi ? (fi->updatable ? SQL_ATTR_WRITE : SQL_ATTR_READONLY) : (QR_get_attid(res, col_idx) > 0 ? SQL_ATTR_WRITE : SQL_ATTR_READONLY);
|
|
if (SQL_ATTR_READONLY != value)
|
|
{
|
|
const char *name = fi ? SAFE_NAME(fi->column_name) : QR_get_fieldname(res, col_idx);
|
|
if (stricmp(name, OID_NAME) == 0 ||
|
|
stricmp(name, "ctid") == 0 ||
|
|
stricmp(name, XMIN_NAME) == 0)
|
|
value = SQL_ATTR_READONLY;
|
|
else if (conn->ms_jet && fi && fi->auto_increment)
|
|
value = SQL_ATTR_READONLY;
|
|
}
|
|
|
|
MYLOG(0, "%s: UPDATEABLE = " FORMAT_LEN "\n", func, value);
|
|
break;
|
|
case SQL_DESC_BASE_COLUMN_NAME:
|
|
|
|
p = fi ? SAFE_NAME(fi->column_name) : QR_get_fieldname(res, col_idx);
|
|
|
|
MYLOG(0, "BASE_COLUMN_NAME = '%s'\n", p);
|
|
break;
|
|
case SQL_DESC_BASE_TABLE_NAME: /* the same as TABLE_NAME ok ? */
|
|
p = ti ? SAFE_NAME(ti->table_name) : NULL_STRING;
|
|
|
|
MYLOG(0, "BASE_TABLE_NAME = '%s'\n", p);
|
|
break;
|
|
case SQL_DESC_LENGTH: /* different from SQL_COLUMN_LENGTH */
|
|
value = (fi && column_size > 0) ? column_size : pgtype_desclength(stmt, field_type, col_idx, unknown_sizes);
|
|
if (-1 == value)
|
|
value = 0;
|
|
|
|
MYLOG(0, "col %d, desc_length = " FORMAT_LEN "\n", col_idx, value);
|
|
break;
|
|
case SQL_DESC_OCTET_LENGTH:
|
|
value = (USE_FI(fi, unknown_sizes) && fi->length > 0) ? fi->length : pgtype_attr_transfer_octet_length(conn, field_type, column_size, unknown_sizes);
|
|
if (-1 == value)
|
|
value = 0;
|
|
MYLOG(0, "col %d, octet_length = " FORMAT_LEN "\n", col_idx, value);
|
|
break;
|
|
case SQL_DESC_PRECISION: /* different from SQL_COLUMN_PRECISION */
|
|
if (value = FI_precision(fi), value <= 0)
|
|
value = pgtype_precision(stmt, field_type, col_idx, unknown_sizes);
|
|
if (value < 0)
|
|
value = 0;
|
|
|
|
MYLOG(0, "col %d, desc_precision = " FORMAT_LEN "\n", col_idx, value);
|
|
break;
|
|
case SQL_DESC_SCALE: /* different from SQL_COLUMN_SCALE */
|
|
value = pgtype_scale(stmt, field_type, col_idx);
|
|
if (value < 0)
|
|
value = 0;
|
|
break;
|
|
case SQL_DESC_LOCAL_TYPE_NAME:
|
|
p = pgtype_to_name(stmt, field_type, col_idx, fi && fi->auto_increment);
|
|
break;
|
|
case SQL_DESC_TYPE:
|
|
value = pgtype_to_sqldesctype(stmt, field_type, col_idx, unknown_sizes);
|
|
break;
|
|
case SQL_DESC_NUM_PREC_RADIX:
|
|
value = pgtype_radix(conn, field_type);
|
|
break;
|
|
case SQL_DESC_LITERAL_PREFIX:
|
|
p = pgtype_literal_prefix(conn, field_type);
|
|
break;
|
|
case SQL_DESC_LITERAL_SUFFIX:
|
|
p = pgtype_literal_suffix(conn, field_type);
|
|
break;
|
|
case SQL_DESC_UNNAMED:
|
|
value = (fi && NAME_IS_NULL(fi->column_name) && NAME_IS_NULL(fi->column_alias)) ? SQL_UNNAMED : SQL_NAMED;
|
|
break;
|
|
case 1211: /* SQL_CA_SS_COLUMN_HIDDEN ? */
|
|
value = 0;
|
|
break;
|
|
case 1212: /* SQL_CA_SS_COLUMN_KEY ? */
|
|
if (fi)
|
|
{
|
|
if (fi->columnkey < 0)
|
|
{
|
|
SC_set_SS_columnkey(stmt);
|
|
}
|
|
value = fi->columnkey;
|
|
MYLOG(0, "SS_COLUMN_KEY=" FORMAT_LEN "\n", value);
|
|
break;
|
|
}
|
|
SC_set_error(stmt, STMT_OPTION_NOT_FOR_THE_DRIVER, "this request may be for MS SQL Server", func);
|
|
return SQL_ERROR;
|
|
default:
|
|
SC_set_error(stmt, STMT_INVALID_OPTION_IDENTIFIER, "ColAttribute for this type not implemented yet", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
result = SQL_SUCCESS;
|
|
|
|
if (p)
|
|
{ /* char/binary data */
|
|
size_t len = strlen(p);
|
|
|
|
if (rgbDesc)
|
|
{
|
|
strncpy_null((char *) rgbDesc, p, (size_t) cbDescMax);
|
|
|
|
if (len >= cbDescMax)
|
|
{
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
SC_set_error(stmt, STMT_TRUNCATED, "The buffer was too small for the rgbDesc.", func);
|
|
}
|
|
}
|
|
|
|
if (pcbDesc)
|
|
*pcbDesc = (SQLSMALLINT) len;
|
|
}
|
|
else
|
|
{
|
|
/* numeric data */
|
|
if (pfDesc)
|
|
*pfDesc = value;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Returns result data for a single column in the current row. */
|
|
RETCODE SQL_API
|
|
PGAPI_GetData(HSTMT hstmt,
|
|
SQLUSMALLINT icol,
|
|
SQLSMALLINT fCType,
|
|
PTR rgbValue,
|
|
SQLLEN cbValueMax,
|
|
SQLLEN * pcbValue)
|
|
{
|
|
CSTR func = "PGAPI_GetData";
|
|
QResultClass *res;
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
UInt2 num_cols;
|
|
SQLLEN num_rows;
|
|
OID field_type;
|
|
int atttypmod;
|
|
void *value = NULL;
|
|
RETCODE result = SQL_SUCCESS;
|
|
char get_bookmark = FALSE;
|
|
SQLSMALLINT target_type;
|
|
int precision = -1;
|
|
#ifdef WITH_UNIXODBC
|
|
SQLCHAR dum_rgb[2] = "\0\0";
|
|
#endif /* WITH_UNIXODBC */
|
|
|
|
MYLOG(0, "entering stmt=%p icol=%d\n", stmt, icol);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
res = SC_get_Curres(stmt);
|
|
|
|
if (STMT_EXECUTING == stmt->status)
|
|
{
|
|
SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Can't get data while statement is still executing.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status != STMT_FINISHED)
|
|
{
|
|
SC_set_error(stmt, STMT_STATUS_ERROR, "GetData can only be called after the successful execution on a SQL statement", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
#ifdef WITH_UNIXODBC
|
|
if (NULL == rgbValue) /* unixODBC allows rgbValue is NULL? */
|
|
{
|
|
cbValueMax = 0;
|
|
rgbValue = dum_rgb; /* to avoid a crash */
|
|
}
|
|
#endif /* WITH_UNIXODBC */
|
|
if (SQL_ARD_TYPE == fCType)
|
|
{
|
|
ARDFields *opts;
|
|
BindInfoClass *binfo = NULL;
|
|
|
|
opts = SC_get_ARDF(stmt);
|
|
if (0 == icol)
|
|
binfo = opts->bookmark;
|
|
else if (icol <= opts->allocated && opts->bindings)
|
|
binfo = &opts->bindings[icol - 1];
|
|
if (binfo)
|
|
{
|
|
target_type = binfo->returntype;
|
|
MYLOG(0, "SQL_ARD_TYPE=%d\n", target_type);
|
|
precision = binfo->precision;
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_STATUS_ERROR, "GetData can't determine the type via ARD", func);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
else
|
|
target_type = fCType;
|
|
if (icol == 0)
|
|
{
|
|
if (stmt->options.use_bookmarks == SQL_UB_OFF)
|
|
{
|
|
SC_set_error(stmt, STMT_COLNUM_ERROR, "Attempt to retrieve bookmark with bookmark usage disabled", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/* Make sure it is the bookmark data type */
|
|
switch (target_type)
|
|
{
|
|
case SQL_C_BOOKMARK:
|
|
case SQL_C_VARBOOKMARK:
|
|
break;
|
|
default:
|
|
MYLOG(DETAIL_LOG_LEVEL, "GetData Column 0 is type %d not of type SQL_C_BOOKMARK\n", target_type);
|
|
SC_set_error(stmt, STMT_PROGRAM_TYPE_OUT_OF_RANGE, "Column 0 is not of type SQL_C_BOOKMARK", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
get_bookmark = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* use zero-based column numbers */
|
|
icol--;
|
|
|
|
/* make sure the column number is valid */
|
|
num_cols = QR_NumPublicResultCols(res);
|
|
if (icol >= num_cols)
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_COLUMN_NUMBER_ERROR, "Invalid column number.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
if (!SC_is_fetchcursor(stmt))
|
|
{
|
|
/* make sure we're positioned on a valid row */
|
|
num_rows = QR_get_num_total_tuples(res);
|
|
if ((stmt->currTuple < 0) ||
|
|
(stmt->currTuple >= num_rows))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Not positioned on a valid row for GetData.", func);
|
|
result = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
MYLOG(0, " num_rows = " FORMAT_LEN "\n", num_rows);
|
|
|
|
if (!get_bookmark)
|
|
{
|
|
SQLLEN curt = GIdx2CacheIdx(stmt->currTuple, stmt, res);
|
|
value = QR_get_value_backend_row(res, curt, icol);
|
|
MYLOG(DETAIL_LOG_LEVEL, "currT=" FORMAT_LEN " base=" FORMAT_LEN " rowset=" FORMAT_LEN "\n", stmt->currTuple, QR_get_rowstart_in_cache(res), SC_get_rowset_start(stmt));
|
|
MYLOG(0, " value = '%s'\n", NULL_IF_NULL(value));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* it's a SOCKET result (backend data) */
|
|
if (stmt->currTuple == -1 || !res || !res->tupleField)
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Not positioned on a valid row for GetData.", func);
|
|
result = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!get_bookmark)
|
|
{
|
|
/** value = QR_get_value_backend(res, icol); maybe thiw doesn't work */
|
|
SQLLEN curt = GIdx2CacheIdx(stmt->currTuple, stmt, res);
|
|
value = QR_get_value_backend_row(res, curt, icol);
|
|
}
|
|
MYLOG(0, " socket: value = '%s'\n", NULL_IF_NULL(value));
|
|
}
|
|
|
|
if (get_bookmark)
|
|
{
|
|
BOOL contents_get = FALSE;
|
|
|
|
if (rgbValue)
|
|
{
|
|
if (SQL_C_BOOKMARK == target_type || sizeof(UInt4) <= cbValueMax)
|
|
{
|
|
Int4 bookmark = SC_make_int4_bookmark(stmt->currTuple);
|
|
contents_get = TRUE;
|
|
memcpy(rgbValue, &bookmark, sizeof(bookmark));
|
|
}
|
|
}
|
|
if (pcbValue)
|
|
*pcbValue = sizeof(Int4);
|
|
|
|
if (contents_get)
|
|
result = SQL_SUCCESS;
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_TRUNCATED, "The buffer was too small for the GetData.", func);
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
field_type = QR_get_field_type(res, icol);
|
|
atttypmod = QR_get_atttypmod(res, icol);
|
|
|
|
MYLOG(0, "**** icol = %d, target_type = %d, field_type = %d, value = '%s'\n", icol, target_type, field_type, NULL_IF_NULL(value));
|
|
|
|
SC_set_current_col(stmt, icol);
|
|
|
|
result = copy_and_convert_field(stmt, field_type, atttypmod, value,
|
|
target_type, precision, rgbValue, cbValueMax, pcbValue, pcbValue);
|
|
|
|
switch (result)
|
|
{
|
|
case COPY_OK:
|
|
result = SQL_SUCCESS;
|
|
break;
|
|
|
|
case COPY_UNSUPPORTED_TYPE:
|
|
SC_set_error(stmt, STMT_RESTRICTED_DATA_TYPE_ERROR, "Received an unsupported type from Postgres.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
case COPY_UNSUPPORTED_CONVERSION:
|
|
SC_set_error(stmt, STMT_RESTRICTED_DATA_TYPE_ERROR, "Couldn't handle the necessary data type conversion.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
case COPY_RESULT_TRUNCATED:
|
|
SC_set_error(stmt, STMT_TRUNCATED, "The buffer was too small for the GetData.", func);
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
break;
|
|
|
|
case COPY_INVALID_STRING_CONVERSION: /* invalid string */
|
|
SC_set_error(stmt, STMT_STRING_CONVERSION_ERROR, "invalid string conversion occured.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
case COPY_GENERAL_ERROR: /* error msg already filled in */
|
|
result = SQL_ERROR;
|
|
break;
|
|
|
|
case COPY_NO_DATA_FOUND:
|
|
result = SQL_NO_DATA_FOUND;
|
|
break;
|
|
|
|
default:
|
|
SC_set_error(stmt, STMT_INTERNAL_ERROR, "Unrecognized return value from copy_and_convert_field.", func);
|
|
result = SQL_ERROR;
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
#undef return
|
|
MYLOG(DETAIL_LOG_LEVEL, "leaving %d\n", result);
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns data for bound columns in the current row ("hstmt->iCursor"),
|
|
* advances the cursor.
|
|
*/
|
|
RETCODE SQL_API
|
|
PGAPI_Fetch(HSTMT hstmt)
|
|
{
|
|
CSTR func = "PGAPI_Fetch";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
ARDFields *opts;
|
|
QResultClass *res;
|
|
BindInfoClass *bookmark;
|
|
RETCODE retval = SQL_SUCCESS;
|
|
|
|
MYLOG(0, "entering stmt = %p, stmt->result= %p\n", stmt, stmt ? SC_get_Curres(stmt) : NULL);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
SC_clear_error(stmt);
|
|
|
|
if (!(res = SC_get_Curres(stmt)))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in PGAPI_Fetch.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/* Not allowed to bind a bookmark column when using SQLFetch. */
|
|
opts = SC_get_ARDF(stmt);
|
|
if ((bookmark = opts->bookmark) && bookmark->buffer)
|
|
{
|
|
SC_set_error(stmt, STMT_COLNUM_ERROR, "Not allowed to bind a bookmark column when using PGAPI_Fetch", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status == STMT_EXECUTING)
|
|
{
|
|
SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Can't fetch while statement is still executing.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status != STMT_FINISHED)
|
|
{
|
|
SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Fetch can only be called after the successful execution on a SQL statement", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (opts->bindings == NULL)
|
|
{
|
|
if (!SC_may_fetch_rows(stmt))
|
|
return SQL_NO_DATA_FOUND;
|
|
/* just to avoid a crash if the user insists on calling this */
|
|
/* function even if SQL_ExecDirect has reported an Error */
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Bindings were not allocated properly.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
if (stmt->rowset_start < 0)
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
QR_set_reqsize(res, 1);
|
|
SC_inc_rowset_start(stmt, stmt->last_fetch_count_include_ommitted);
|
|
|
|
retval = SC_fetch(stmt);
|
|
#undef return
|
|
return retval;
|
|
}
|
|
|
|
static RETCODE SQL_API
|
|
SC_pos_reload_needed(StatementClass *stmt, SQLULEN req_size, UDWORD flag);
|
|
|
|
SQLLEN
|
|
getNthValid(const QResultClass *res, SQLLEN sta, UWORD orientation,
|
|
SQLULEN nth, SQLLEN *nearest)
|
|
{
|
|
SQLLEN i, num_tuples = QR_get_num_total_tuples(res), nearp;
|
|
SQLULEN count;
|
|
KeySet *keyset;
|
|
|
|
if (!QR_once_reached_eof(res))
|
|
num_tuples = INT_MAX;
|
|
/* Note that the parameter nth is 1-based */
|
|
MYLOG(DETAIL_LOG_LEVEL, "get " FORMAT_ULEN "th Valid data from " FORMAT_LEN " to %s [dlt=%d]", nth, sta, orientation == SQL_FETCH_PRIOR ? "backward" : "forward", res->dl_count);
|
|
if (0 == res->dl_count)
|
|
{
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "\n");
|
|
if (SQL_FETCH_PRIOR == orientation)
|
|
{
|
|
if (sta + 1 >= (SQLLEN) nth)
|
|
{
|
|
*nearest = sta + 1 - nth;
|
|
return nth;
|
|
}
|
|
*nearest = -1;
|
|
return -(SQLLEN)(sta + 1);
|
|
}
|
|
else
|
|
{
|
|
nearp = sta - 1 + nth;
|
|
if (nearp < num_tuples)
|
|
{
|
|
*nearest = nearp;
|
|
return nth;
|
|
}
|
|
*nearest = num_tuples;
|
|
return -(SQLLEN)(num_tuples - sta);
|
|
}
|
|
}
|
|
count = 0;
|
|
if (QR_get_cursor(res))
|
|
{
|
|
SQLLEN *deleted = res->deleted;
|
|
SQLLEN delsta;
|
|
|
|
if (SQL_FETCH_PRIOR == orientation)
|
|
{
|
|
*nearest = sta + 1 - nth;
|
|
delsta = (-1);
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "deleted ");
|
|
for (i = res->dl_count - 1; i >=0 && *nearest <= deleted[i]; i--)
|
|
{
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "[" FORMAT_LEN "]=" FORMAT_LEN " ", i, deleted[i]);
|
|
if (sta >= deleted[i])
|
|
{
|
|
(*nearest)--;
|
|
if (i > delsta)
|
|
delsta = i;
|
|
}
|
|
}
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "nearest=" FORMAT_LEN "\n", *nearest);
|
|
if (*nearest < 0)
|
|
{
|
|
*nearest = -1;
|
|
count = sta - delsta;
|
|
}
|
|
else
|
|
return nth;
|
|
}
|
|
else
|
|
{
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "\n");
|
|
*nearest = sta - 1 + nth;
|
|
delsta = res->dl_count;
|
|
if (!QR_once_reached_eof(res))
|
|
num_tuples = INT_MAX;
|
|
for (i = 0; i < res->dl_count && *nearest >= deleted[i]; i++)
|
|
{
|
|
if (sta <= deleted[i])
|
|
{
|
|
(*nearest)++;
|
|
if (i < delsta)
|
|
delsta = i;
|
|
}
|
|
}
|
|
if (*nearest >= num_tuples)
|
|
{
|
|
*nearest = num_tuples;
|
|
count = *nearest - sta + delsta - res->dl_count;
|
|
}
|
|
else
|
|
return nth;
|
|
}
|
|
}
|
|
else if (SQL_FETCH_PRIOR == orientation)
|
|
{
|
|
for (i = sta, keyset = res->keyset + sta;
|
|
i >= 0; i--, keyset--)
|
|
{
|
|
if (0 == (keyset->status & (CURS_SELF_DELETING | CURS_SELF_DELETED | CURS_OTHER_DELETED)))
|
|
{
|
|
*nearest = i;
|
|
MYPRINTF(DETAIL_LOG_LEVEL, " nearest=" FORMAT_LEN "\n", *nearest);
|
|
if (++count == nth)
|
|
return count;
|
|
}
|
|
}
|
|
*nearest = -1;
|
|
}
|
|
else
|
|
{
|
|
for (i = sta, keyset = res->keyset + sta;
|
|
i < num_tuples; i++, keyset++)
|
|
{
|
|
if (0 == (keyset->status & (CURS_SELF_DELETING | CURS_SELF_DELETED | CURS_OTHER_DELETED)))
|
|
{
|
|
*nearest = i;
|
|
MYPRINTF(DETAIL_LOG_LEVEL, " nearest=" FORMAT_LEN "\n", *nearest);
|
|
if (++count == nth)
|
|
return count;
|
|
}
|
|
}
|
|
*nearest = num_tuples;
|
|
}
|
|
MYPRINTF(DETAIL_LOG_LEVEL, " nearest not found\n");
|
|
return -(SQLLEN)count;
|
|
}
|
|
|
|
static void
|
|
move_cursor_position_if_needed(StatementClass *self, QResultClass *res)
|
|
{
|
|
SQLLEN move_offset;
|
|
|
|
/*
|
|
* The move direction must be initialized to is_not_moving or
|
|
* is_moving_from_the_last in advance.
|
|
*/
|
|
if (!QR_get_cursor(res))
|
|
{
|
|
QR_stop_movement(res); /* for safety */
|
|
res->move_offset = 0;
|
|
return;
|
|
}
|
|
MYLOG(DETAIL_LOG_LEVEL, "BASE=" FORMAT_LEN " numb=" FORMAT_LEN " curr=" FORMAT_LEN " cursT=" FORMAT_LEN "\n", QR_get_rowstart_in_cache(res), res->num_cached_rows, self->currTuple, res->cursTuple);
|
|
|
|
/* normal case */
|
|
res->move_offset = 0;
|
|
move_offset = self->currTuple - res->cursTuple;
|
|
if (QR_get_rowstart_in_cache(res) >= 0 &&
|
|
QR_get_rowstart_in_cache(res) <= res->num_cached_rows)
|
|
{
|
|
QR_set_next_in_cache(res, (QR_get_rowstart_in_cache(res) < 0) ? 0 : QR_get_rowstart_in_cache(res));
|
|
return;
|
|
}
|
|
if (0 == move_offset)
|
|
return;
|
|
if (move_offset > 0)
|
|
{
|
|
QR_set_move_forward(res);
|
|
res->move_offset = move_offset;
|
|
}
|
|
else
|
|
{
|
|
QR_set_move_backward(res);
|
|
res->move_offset = -move_offset;
|
|
}
|
|
}
|
|
/*
|
|
* return NO_DATA_FOUND macros
|
|
* save_rowset_start or num_tuples must be defined
|
|
*/
|
|
#define EXTFETCH_RETURN_BOF(stmt, res) \
|
|
{ \
|
|
MYLOG(DETAIL_LOG_LEVEL, "RETURN_BOF\n"); \
|
|
SC_set_rowset_start(stmt, -1, TRUE); \
|
|
stmt->currTuple = -1; \
|
|
return SQL_NO_DATA_FOUND; \
|
|
}
|
|
#define EXTFETCH_RETURN_EOF(stmt, res) \
|
|
{ \
|
|
MYLOG(DETAIL_LOG_LEVEL, "RETURN_EOF\n"); \
|
|
SC_set_rowset_start(stmt, num_tuples, TRUE); \
|
|
stmt->currTuple = -1; \
|
|
return SQL_NO_DATA_FOUND; \
|
|
}
|
|
|
|
/* This fetchs a block of data (rowset). */
|
|
RETCODE SQL_API
|
|
PGAPI_ExtendedFetch(HSTMT hstmt,
|
|
SQLUSMALLINT fFetchType,
|
|
SQLLEN irow,
|
|
SQLULEN * pcrow,
|
|
SQLUSMALLINT * rgfRowStatus,
|
|
SQLLEN bookmark_offset,
|
|
SQLLEN rowsetSize)
|
|
{
|
|
CSTR func = "PGAPI_ExtendedFetch";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
ARDFields *opts;
|
|
QResultClass *res;
|
|
BindInfoClass *bookmark;
|
|
SQLLEN num_tuples, i, fc_io;
|
|
SQLLEN save_rowset_size, progress_size;
|
|
SQLLEN rowset_start, rowset_end = (-1);
|
|
RETCODE result = SQL_SUCCESS;
|
|
char truncated, error, should_set_rowset_start = FALSE;
|
|
SQLLEN currp;
|
|
UWORD pstatus;
|
|
BOOL currp_is_valid, reached_eof, useCursor;
|
|
SQLLEN reqsize = rowsetSize;
|
|
|
|
MYLOG(0, "entering stmt=%p rowsetSize=" FORMAT_LEN "\n", stmt, rowsetSize);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
/* if (SC_is_fetchcursor(stmt) && !stmt->manual_result) */
|
|
if (SQL_CURSOR_FORWARD_ONLY == stmt->options.cursor_type)
|
|
{
|
|
if (fFetchType != SQL_FETCH_NEXT)
|
|
{
|
|
SC_set_error(stmt, STMT_FETCH_OUT_OF_RANGE, "The fetch type for PGAPI_ExtendedFetch isn't allowed with ForwardOnly cursor.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
|
|
SC_clear_error(stmt);
|
|
|
|
if (!(res = SC_get_Curres(stmt)))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in PGAPI_ExtendedFetch.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
opts = SC_get_ARDF(stmt);
|
|
/*
|
|
* If a bookmark colunmn is bound but bookmark usage is off, then
|
|
* error
|
|
*/
|
|
if ((bookmark = opts->bookmark) && bookmark->buffer && stmt->options.use_bookmarks == SQL_UB_OFF)
|
|
{
|
|
SC_set_error(stmt, STMT_COLNUM_ERROR, "Attempt to retrieve bookmark with bookmark usage disabled", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status == STMT_EXECUTING)
|
|
{
|
|
SC_set_error(stmt, STMT_SEQUENCE_ERROR, "Can't fetch while statement is still executing.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (stmt->status != STMT_FINISHED)
|
|
{
|
|
SC_set_error(stmt, STMT_STATUS_ERROR, "ExtendedFetch can only be called after the successful execution on a SQL statement", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (opts->bindings == NULL)
|
|
{
|
|
if (!SC_may_fetch_rows(stmt))
|
|
return SQL_NO_DATA_FOUND;
|
|
/* just to avoid a crash if the user insists on calling this */
|
|
/* function even if SQL_ExecDirect has reported an Error */
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Bindings were not allocated properly.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/* Initialize to no rows fetched */
|
|
if (rgfRowStatus)
|
|
for (i = 0; i < rowsetSize; i++)
|
|
*(rgfRowStatus + i) = SQL_ROW_NOROW;
|
|
|
|
if (pcrow)
|
|
*pcrow = 0;
|
|
|
|
useCursor = (SC_is_fetchcursor(stmt) && NULL != QR_get_cursor(res));
|
|
num_tuples = QR_get_num_total_tuples(res);
|
|
reached_eof = QR_once_reached_eof(res) && QR_get_cursor(res);
|
|
if (useCursor && !reached_eof)
|
|
num_tuples = INT_MAX;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "num_tuples=" FORMAT_LEN "\n", num_tuples);
|
|
/* Save and discard the saved rowset size */
|
|
save_rowset_size = stmt->save_rowset_size;
|
|
stmt->save_rowset_size = -1;
|
|
rowset_start = SC_get_rowset_start(stmt);
|
|
|
|
QR_stop_movement(res);
|
|
res->move_offset = 0;
|
|
switch (fFetchType)
|
|
{
|
|
int min_nth;
|
|
SQLLEN bidx;
|
|
|
|
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.
|
|
*/
|
|
|
|
progress_size = (save_rowset_size > 0 ? save_rowset_size : rowsetSize);
|
|
if (rowset_start < 0)
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
else if (res->keyset)
|
|
{
|
|
if (stmt->last_fetch_count <= progress_size)
|
|
{
|
|
SC_inc_rowset_start(stmt, stmt->last_fetch_count_include_ommitted);
|
|
progress_size -= stmt->last_fetch_count;
|
|
}
|
|
if (progress_size > 0)
|
|
{
|
|
if (getNthValid(res, SC_get_rowset_start(stmt),
|
|
SQL_FETCH_NEXT, progress_size + 1,
|
|
&rowset_start) <= 0)
|
|
{
|
|
EXTFETCH_RETURN_EOF(stmt, res)
|
|
}
|
|
else
|
|
should_set_rowset_start =TRUE;
|
|
}
|
|
}
|
|
else
|
|
SC_inc_rowset_start(stmt, progress_size);
|
|
MYLOG(0, "SQL_FETCH_NEXT: num_tuples=" FORMAT_LEN ", currtuple=" FORMAT_LEN ", rowst=" FORMAT_LEN "\n", num_tuples, stmt->currTuple, rowset_start);
|
|
break;
|
|
|
|
case SQL_FETCH_PRIOR:
|
|
MYLOG(0, "SQL_FETCH_PRIOR: num_tuples=" FORMAT_LEN ", currtuple=" FORMAT_LEN "\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 (rowset_start <= 0)
|
|
{
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
}
|
|
if (rowset_start >= num_tuples &&
|
|
rowsetSize > num_tuples)
|
|
{
|
|
SC_set_error(stmt, STMT_POS_BEFORE_RECORDSET, "fetch prior from eof and before the beginning", func);
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
}
|
|
else if (QR_haskeyset(res))
|
|
{
|
|
rowset_end = rowset_start - 1;
|
|
if (min_nth = getNthValid(res, rowset_end, SQL_FETCH_PRIOR, rowsetSize, &rowset_start), min_nth <= 0)
|
|
{
|
|
if (min_nth == 0)
|
|
{
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_POS_BEFORE_RECORDSET, "fetch prior and before the beginning", func);
|
|
rowset_end = (-1);
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
should_set_rowset_start = TRUE;
|
|
reqsize = rowset_end - rowset_start + 1;
|
|
}
|
|
}
|
|
else if (rowset_start < rowsetSize)
|
|
{
|
|
SC_set_error(stmt, STMT_POS_BEFORE_RECORDSET, "fetch prior from eof and before the beginning", func);
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
}
|
|
else
|
|
SC_inc_rowset_start(stmt, -rowsetSize);
|
|
break;
|
|
|
|
case SQL_FETCH_FIRST:
|
|
MYLOG(0, "SQL_FETCH_FIRST: num_tuples=" FORMAT_LEN ", currtuple=" FORMAT_LEN "\n", num_tuples, stmt->currTuple);
|
|
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
break;
|
|
|
|
case SQL_FETCH_LAST:
|
|
MYLOG(0, "SQL_FETCH_LAST: num_tuples=" FORMAT_LEN ", currtuple=" FORMAT_LEN "\n", num_tuples, stmt->currTuple);
|
|
|
|
if (!reached_eof)
|
|
{
|
|
QR_move_cursor_to_last(res, stmt);
|
|
num_tuples = QR_get_num_total_tuples(res);
|
|
}
|
|
rowset_end = num_tuples - 1;
|
|
if (min_nth = getNthValid(res, rowset_end, SQL_FETCH_PRIOR, rowsetSize, &rowset_start), min_nth <= 0)
|
|
{
|
|
if (min_nth == 0)
|
|
{
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_POS_BEFORE_RECORDSET, "fetch last and before the beginning", func);
|
|
rowset_end = (-1);
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
should_set_rowset_start = TRUE;
|
|
reqsize = rowset_end - rowset_start + 1;
|
|
}
|
|
break;
|
|
|
|
case SQL_FETCH_ABSOLUTE:
|
|
MYLOG(0, "SQL_FETCH_ABSOLUTE: num_tuples=" FORMAT_LEN ", currtuple=" FORMAT_LEN ", irow=" FORMAT_LEN "\n", num_tuples, stmt->currTuple, irow);
|
|
|
|
/* Position before result set, but dont fetch anything */
|
|
if (irow == 0)
|
|
{
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
}
|
|
/* Position before the desired row */
|
|
else if (irow > 0)
|
|
{
|
|
if (getNthValid(res, 0, SQL_FETCH_NEXT, irow, &rowset_start) <= 0)
|
|
{
|
|
EXTFETCH_RETURN_EOF(stmt, res)
|
|
}
|
|
else
|
|
should_set_rowset_start = TRUE;
|
|
}
|
|
/* Position with respect to the end of the result set */
|
|
else
|
|
{
|
|
if (!reached_eof)
|
|
{
|
|
QR_move_cursor_to_last(res, stmt);
|
|
num_tuples = QR_get_num_total_tuples(res);
|
|
}
|
|
if (min_nth = getNthValid(res, num_tuples - 1, SQL_FETCH_PRIOR, -irow, &rowset_start), min_nth <= 0)
|
|
{
|
|
if (min_nth == 0)
|
|
{
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_POS_BEFORE_RECORDSET, "fetch absolute and before the beginning", func);
|
|
rowset_end = (-1);
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
}
|
|
}
|
|
else
|
|
should_set_rowset_start = TRUE;
|
|
}
|
|
break;
|
|
|
|
case SQL_FETCH_RELATIVE:
|
|
|
|
/*
|
|
* Refresh the current rowset -- not currently implemented,
|
|
* but lie anyway
|
|
*/
|
|
if (irow == 0)
|
|
break;
|
|
|
|
if (irow > 0)
|
|
{
|
|
if (getNthValid(res, SC_get_rowset_start(stmt) + 1, SQL_FETCH_NEXT, irow, &rowset_start) <= 0)
|
|
{
|
|
EXTFETCH_RETURN_EOF(stmt, res)
|
|
}
|
|
else
|
|
should_set_rowset_start = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (min_nth = getNthValid(res, SC_get_rowset_start(stmt) - 1, SQL_FETCH_PRIOR, -irow, &rowset_start), min_nth <= 0)
|
|
{
|
|
if (min_nth == 0)
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_POS_BEFORE_RECORDSET, "fetch relative and before the beginning", func);
|
|
rowset_end = (-1);
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
}
|
|
}
|
|
else
|
|
should_set_rowset_start = TRUE;
|
|
}
|
|
break;
|
|
|
|
case SQL_FETCH_BOOKMARK:
|
|
bidx = SC_resolve_int4_bookmark(irow);
|
|
|
|
if (bidx < 0)
|
|
{
|
|
if (!reached_eof)
|
|
{
|
|
QR_move_cursor_to_last(res, stmt);
|
|
num_tuples = QR_get_num_total_tuples(res);
|
|
}
|
|
bidx = num_tuples - 1 - res->ad_count - bidx;
|
|
}
|
|
|
|
rowset_start = bidx;
|
|
if (bookmark_offset >= 0)
|
|
{
|
|
if (getNthValid(res, bidx, SQL_FETCH_NEXT, bookmark_offset + 1, &rowset_start) <= 0)
|
|
{
|
|
EXTFETCH_RETURN_EOF(stmt, res)
|
|
}
|
|
else
|
|
should_set_rowset_start = TRUE;
|
|
}
|
|
else if (getNthValid(res, bidx, SQL_FETCH_PRIOR, 1 - bookmark_offset, &rowset_start) <= 0)
|
|
{
|
|
stmt->currTuple = -1;
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
}
|
|
else
|
|
should_set_rowset_start = TRUE;
|
|
break;
|
|
|
|
default:
|
|
SC_set_error(stmt, STMT_FETCH_OUT_OF_RANGE, "Unsupported PGAPI_ExtendedFetch Direction", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* CHECK FOR PROPER CURSOR STATE
|
|
*/
|
|
|
|
/*
|
|
* Handle Declare Fetch style specially because the end is not really
|
|
* the end...
|
|
*/
|
|
if (!should_set_rowset_start)
|
|
rowset_start = SC_get_rowset_start(stmt);
|
|
if (useCursor)
|
|
{
|
|
if (reached_eof &&
|
|
rowset_start >= num_tuples)
|
|
{
|
|
EXTFETCH_RETURN_EOF(stmt, res)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* If *new* rowset is after the result_set, return no data found */
|
|
if (rowset_start >= num_tuples)
|
|
{
|
|
EXTFETCH_RETURN_EOF(stmt, res)
|
|
}
|
|
}
|
|
/* If *new* rowset is prior to result_set, return no data found */
|
|
if (rowset_start < 0)
|
|
{
|
|
if (rowset_start + rowsetSize <= 0)
|
|
{
|
|
EXTFETCH_RETURN_BOF(stmt, res)
|
|
}
|
|
else
|
|
{ /* overlap with beginning of result set,
|
|
* so get first rowset */
|
|
SC_set_rowset_start(stmt, 0, TRUE);
|
|
}
|
|
should_set_rowset_start = FALSE;
|
|
}
|
|
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
/* set the rowset_start if needed */
|
|
if (should_set_rowset_start)
|
|
SC_set_rowset_start(stmt, rowset_start, TRUE);
|
|
if (rowset_end < 0 && QR_haskeyset(res))
|
|
{
|
|
getNthValid(res, rowset_start,
|
|
SQL_FETCH_NEXT, rowsetSize, &rowset_end);
|
|
reqsize = rowset_end - rowset_start + 1;
|
|
}
|
|
QR_set_reqsize(res, (Int4) reqsize);
|
|
/* currTuple is always 1 row prior to the rowset start */
|
|
stmt->currTuple = RowIdx2GIdx(-1, stmt);
|
|
|
|
if (SC_is_fetchcursor(stmt) ||
|
|
SQL_CURSOR_KEYSET_DRIVEN == stmt->options.cursor_type)
|
|
{
|
|
move_cursor_position_if_needed(stmt, res);
|
|
}
|
|
else
|
|
QR_set_rowstart_in_cache(res, SC_get_rowset_start(stmt));
|
|
|
|
if (res->keyset && !QR_get_cursor(res))
|
|
{
|
|
UDWORD flag = 0;
|
|
|
|
if (SQL_CURSOR_KEYSET_DRIVEN == stmt->options.cursor_type)
|
|
{
|
|
if (fFetchType != SQL_FETCH_NEXT ||
|
|
QR_get_rowstart_in_cache(res) + reqsize > QR_get_num_cached_tuples(res))
|
|
{
|
|
flag = 1;
|
|
}
|
|
}
|
|
if (SQL_RD_ON == stmt->options.retrieve_data ||
|
|
flag != 0)
|
|
{
|
|
SC_pos_reload_needed(stmt, reqsize, flag);
|
|
}
|
|
}
|
|
/* Physical Row advancement occurs for each row fetched below */
|
|
|
|
MYLOG(0, "new currTuple = " FORMAT_LEN "\n", stmt->currTuple);
|
|
|
|
truncated = error = FALSE;
|
|
|
|
currp = -1;
|
|
stmt->bind_row = 0; /* set the binding location */
|
|
result = SC_fetch(stmt);
|
|
if (SQL_ERROR == result)
|
|
goto cleanup;
|
|
if (SQL_NO_DATA_FOUND != result && res->keyset)
|
|
{
|
|
currp = GIdx2KResIdx(SC_get_rowset_start(stmt), stmt, res);
|
|
MYLOG(DETAIL_LOG_LEVEL, "currp=" FORMAT_LEN "\n", currp);
|
|
if (currp < 0)
|
|
{
|
|
result = SQL_ERROR;
|
|
MYLOG(0, "rowset_start=" FORMAT_LEN " but currp=" FORMAT_LEN "\n", SC_get_rowset_start(stmt), currp);
|
|
SC_set_error(stmt, STMT_INTERNAL_ERROR, "rowset_start not in the keyset", func);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
for (i = 0, fc_io = 0; SQL_NO_DATA_FOUND != result && SQL_ERROR != result; currp++)
|
|
{
|
|
fc_io++;
|
|
currp_is_valid = FALSE;
|
|
if (res->keyset)
|
|
{
|
|
if (currp < res->num_cached_keys)
|
|
{
|
|
currp_is_valid = TRUE;
|
|
res->keyset[currp].status &= ~CURS_IN_ROWSET; /* Off the flag first */
|
|
}
|
|
else
|
|
{
|
|
MYLOG(0, "Umm current row is out of keyset\n");
|
|
break;
|
|
}
|
|
}
|
|
MYLOG(DETAIL_LOG_LEVEL, "ExtFetch result=%d\n", result);
|
|
if (currp_is_valid && SQL_SUCCESS_WITH_INFO == result && 0 == stmt->last_fetch_count)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "just skipping deleted row " FORMAT_LEN "\n", currp);
|
|
if (rowsetSize - i + fc_io > reqsize)
|
|
QR_set_reqsize(res, (Int4) (rowsetSize - i + fc_io));
|
|
result = SC_fetch(stmt);
|
|
if (SQL_ERROR == result)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
/* Determine Function status */
|
|
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 if (currp_is_valid)
|
|
{
|
|
pstatus = (res->keyset[currp].status & KEYSET_INFO_PUBLIC);
|
|
if (pstatus != 0 && pstatus != SQL_ROW_ADDED)
|
|
{
|
|
rgfRowStatus[i] = pstatus;
|
|
}
|
|
else
|
|
rgfRowStatus[i] = SQL_ROW_SUCCESS;
|
|
/* refresh the status */
|
|
/* if (SQL_ROW_DELETED != pstatus) */
|
|
res->keyset[currp].status &= (~KEYSET_INFO_PUBLIC);
|
|
}
|
|
else
|
|
*(rgfRowStatus + i) = SQL_ROW_SUCCESS;
|
|
}
|
|
if (SQL_ERROR != result && currp_is_valid)
|
|
res->keyset[currp].status |= CURS_IN_ROWSET; /* This is the unique place where the CURS_IN_ROWSET bit is turned on */
|
|
i++;
|
|
if (i >= rowsetSize)
|
|
break;
|
|
stmt->bind_row = (SQLSETPOSIROW) i; /* set the binding location */
|
|
result = SC_fetch(stmt);
|
|
}
|
|
if (SQL_ERROR == result)
|
|
goto cleanup;
|
|
|
|
/* Save the fetch count for SQLSetPos */
|
|
stmt->last_fetch_count = i;
|
|
stmt->save_rowset_size = rowsetSize;
|
|
|
|
stmt->last_fetch_count_include_ommitted = fc_io;
|
|
|
|
/* Reset next binding row */
|
|
stmt->bind_row = 0;
|
|
|
|
/* Move the cursor position to the first row in the result set. */
|
|
stmt->currTuple = RowIdx2GIdx(0, stmt);
|
|
|
|
/* For declare/fetch, need to reset cursor to beginning of rowset */
|
|
if (useCursor)
|
|
QR_set_position(res, 0);
|
|
|
|
/* Set the number of rows retrieved */
|
|
if (pcrow)
|
|
*pcrow = i;
|
|
MYLOG(DETAIL_LOG_LEVEL, "pcrow=" FORMAT_LEN "\n", i);
|
|
|
|
if (i == 0)
|
|
/* Only DeclareFetch should wind up here */
|
|
result = SQL_NO_DATA_FOUND;
|
|
else if (error)
|
|
result = SQL_ERROR;
|
|
else if (truncated)
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
else if (SC_get_errornumber(stmt) == STMT_POS_BEFORE_RECORDSET)
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
else
|
|
result = SQL_SUCCESS;
|
|
|
|
cleanup:
|
|
#undef return
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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
|
|
PGAPI_MoreResults(HSTMT hstmt)
|
|
{
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
QResultClass *res;
|
|
RETCODE ret = SQL_SUCCESS;
|
|
|
|
MYLOG(0, "entering...\n");
|
|
res = SC_get_Curres(stmt);
|
|
if (res)
|
|
{
|
|
res = res->next;
|
|
SC_set_Curres(stmt, res);
|
|
}
|
|
if (res)
|
|
{
|
|
SQLSMALLINT num_p;
|
|
|
|
if (stmt->multi_statement < 0)
|
|
PGAPI_NumParams(stmt, &num_p);
|
|
if (stmt->multi_statement > 0)
|
|
{
|
|
const char *cmdstr;
|
|
|
|
SC_initialize_cols_info(stmt, FALSE, TRUE);
|
|
stmt->statement_type = STMT_TYPE_UNKNOWN;
|
|
if (cmdstr = QR_get_command(res), NULL != cmdstr)
|
|
stmt->statement_type = statement_type(cmdstr);
|
|
stmt->join_info = 0;
|
|
SC_clear_parse_method(stmt);
|
|
}
|
|
stmt->diag_row_count = res->recent_processed_row_count;
|
|
SC_set_rowset_start(stmt, -1, FALSE);
|
|
stmt->currTuple = -1;
|
|
}
|
|
else
|
|
{
|
|
PGAPI_FreeStmt(hstmt, SQL_CLOSE);
|
|
ret = SQL_NO_DATA_FOUND;
|
|
}
|
|
MYLOG(0, "leaving %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Stuff for updatable cursors.
|
|
*/
|
|
static Int2 getNumResultCols(const QResultClass *res)
|
|
{
|
|
Int2 res_cols = QR_NumPublicResultCols(res);
|
|
return res_cols;
|
|
}
|
|
static OID getOid(const QResultClass *res, SQLLEN index)
|
|
{
|
|
return res->keyset[index].oid;
|
|
}
|
|
static void getTid(const QResultClass *res, SQLLEN index, UInt4 *blocknum, UInt2 *offset)
|
|
{
|
|
*blocknum = res->keyset[index].blocknum;
|
|
*offset = res->keyset[index].offset;
|
|
}
|
|
static void KeySetSet(const TupleField *tuple, int num_fields, int num_key_fields, KeySet *keyset, BOOL statusInit)
|
|
{
|
|
if (statusInit)
|
|
keyset->status = 0;
|
|
sscanf(tuple[num_fields - num_key_fields].value, "(%u,%hu)",
|
|
&keyset->blocknum, &keyset->offset);
|
|
if (num_key_fields > 1)
|
|
{
|
|
const char *oval = tuple[num_fields - 1].value;
|
|
|
|
if ('-' == oval[0])
|
|
sscanf(oval, "%d", &keyset->oid);
|
|
else
|
|
sscanf(oval, "%u", &keyset->oid);
|
|
}
|
|
else
|
|
keyset->oid = 0;
|
|
}
|
|
|
|
static void AddRollback(StatementClass *stmt, QResultClass *res, SQLLEN index, const KeySet *keyset, Int4 dmlcode)
|
|
{
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
Rollback *rollback;
|
|
|
|
if (!CC_is_in_trans(conn))
|
|
return;
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering " FORMAT_LEN "(%u,%u) %s\n", index, keyset->blocknum, keyset->offset, dmlcode == SQL_ADD ? "ADD" : (dmlcode == SQL_UPDATE ? "UPDATE" : (dmlcode == SQL_DELETE ? "DELETE" : "REFRESH")));
|
|
if (!res->rollback)
|
|
{
|
|
res->rb_count = 0;
|
|
res->rb_alloc = 10;
|
|
rollback = res->rollback = malloc(sizeof(Rollback) * res->rb_alloc);
|
|
if (!rollback)
|
|
{
|
|
res->rb_alloc = res->rb_count = 0;
|
|
MYLOG(DETAIL_LOG_LEVEL, "out of memory\n");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (res->rb_count >= res->rb_alloc)
|
|
{
|
|
res->rb_alloc *= 2;
|
|
if (rollback = realloc(res->rollback, sizeof(Rollback) * res->rb_alloc), !rollback)
|
|
{
|
|
res->rb_alloc = res->rb_count = 0;
|
|
return;
|
|
}
|
|
res->rollback = rollback;
|
|
}
|
|
rollback = res->rollback + res->rb_count;
|
|
}
|
|
rollback->index = index;
|
|
rollback->option = dmlcode;
|
|
rollback->offset = 0;
|
|
rollback->blocknum = 0;
|
|
rollback->oid = 0;
|
|
if (keyset)
|
|
{
|
|
rollback->blocknum = keyset->blocknum;
|
|
rollback->offset = keyset->offset;
|
|
rollback->oid = keyset->oid;
|
|
}
|
|
|
|
conn->result_uncommitted = 1;
|
|
res->rb_count++;
|
|
}
|
|
|
|
SQLLEN ClearCachedRows(TupleField *tuple, int num_fields, SQLLEN num_rows)
|
|
{
|
|
SQLLEN i;
|
|
|
|
for (i = 0; i < num_fields * num_rows; i++, tuple++)
|
|
{
|
|
if (tuple->value)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "freeing tuple[" FORMAT_LEN "][" FORMAT_LEN "].value=%p\n", i / num_fields, i % num_fields, tuple->value);
|
|
free(tuple->value);
|
|
tuple->value = NULL;
|
|
}
|
|
tuple->len = -1;
|
|
}
|
|
return i;
|
|
}
|
|
SQLLEN ReplaceCachedRows(TupleField *otuple, const TupleField *ituple, int num_fields, SQLLEN num_rows)
|
|
{
|
|
SQLLEN i;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering %p num_fields=%d num_rows=" FORMAT_LEN "\n", otuple, num_fields, num_rows);
|
|
for (i = 0; i < num_fields * num_rows; i++, ituple++, otuple++)
|
|
{
|
|
if (otuple->value)
|
|
{
|
|
free(otuple->value);
|
|
otuple->value = NULL;
|
|
}
|
|
if (ituple->value)
|
|
{
|
|
otuple->value = strdup(ituple->value);
|
|
MYLOG(DETAIL_LOG_LEVEL, "[" FORMAT_LEN "," FORMAT_LEN "] %s copied\n", i / num_fields, i % num_fields, (const char *) otuple->value);
|
|
}
|
|
if (otuple->value)
|
|
otuple->len = ituple->len;
|
|
else
|
|
otuple->len = -1;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static
|
|
int MoveCachedRows(TupleField *otuple, TupleField *ituple, Int2 num_fields, SQLLEN num_rows)
|
|
{
|
|
int i;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering %p num_fields=%d num_rows=" FORMAT_LEN "\n", otuple, num_fields, num_rows);
|
|
for (i = 0; i < num_fields * num_rows; i++, ituple++, otuple++)
|
|
{
|
|
if (otuple->value)
|
|
{
|
|
free(otuple->value);
|
|
otuple->value = NULL;
|
|
}
|
|
if (ituple->value)
|
|
{
|
|
otuple->value = ituple->value;
|
|
ituple->value = NULL;
|
|
MYLOG(DETAIL_LOG_LEVEL, "[%d,%d] %s copied\n", i / num_fields, i % num_fields, (const char *) otuple->value);
|
|
}
|
|
otuple->len = ituple->len;
|
|
ituple->len = -1;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static const char *
|
|
ti_quote(StatementClass *stmt, OID tableoid, char *buf, int buf_size)
|
|
{
|
|
TABLE_INFO *ti = stmt->ti[0];
|
|
pgNAME rNAME;
|
|
|
|
if (0 == tableoid || !TI_has_subclass(ti))
|
|
return quote_table(ti->schema_name, ti->table_name, buf, buf_size);
|
|
else if (NAME_IS_VALID(rNAME = TI_From_IH(ti, tableoid)))
|
|
return SAFE_NAME(rNAME);
|
|
else
|
|
{
|
|
char query[200];
|
|
QResultClass *res;
|
|
char *ret = "";
|
|
|
|
SPRINTF_FIXED(query, "select relname, nspname from pg_class c, pg_namespace n where c.oid=%u and c.relnamespace=n.oid", tableoid);
|
|
res = CC_send_query(SC_get_conn(stmt), query, NULL, READ_ONLY_QUERY, stmt);
|
|
if (QR_command_maybe_successful(res) &&
|
|
QR_get_num_cached_tuples(res) == 1)
|
|
{
|
|
pgNAME schema_name, table_name;
|
|
|
|
SET_NAME_DIRECTLY(schema_name, QR_get_value_backend_text(res, 0, 1));
|
|
SET_NAME_DIRECTLY(table_name, QR_get_value_backend_text(res, 0, 0));
|
|
ret = quote_table(schema_name, table_name, buf, buf_size);
|
|
TI_Ins_IH(ti, tableoid, ret);
|
|
}
|
|
QR_Destructor(res);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static BOOL tupleExists(StatementClass *stmt, const KeySet *keyset)
|
|
{
|
|
PQExpBufferData selstr = {0};
|
|
const TABLE_INFO *ti = stmt->ti[0];
|
|
QResultClass *res;
|
|
RETCODE ret = FALSE;
|
|
const char *bestqual = GET_NAME(ti->bestqual);
|
|
char table_fqn[256];
|
|
|
|
initPQExpBuffer(&selstr);
|
|
printfPQExpBuffer(&selstr,
|
|
"select 1 from %s where ctid = '(%u,%u)'",
|
|
ti_quote(stmt, keyset->oid, table_fqn, sizeof(table_fqn)),
|
|
keyset->blocknum, keyset->offset);
|
|
if (NULL != bestqual && 0 != keyset->oid && !TI_has_subclass(ti))
|
|
{
|
|
appendPQExpBuffer(&selstr, " and ");
|
|
appendPQExpBuffer(&selstr, bestqual, keyset->oid);
|
|
}
|
|
if (PQExpBufferDataBroken(selstr))
|
|
{
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory in tupleExists()", __FUNCTION__);
|
|
goto cleanup;
|
|
}
|
|
res = CC_send_query(SC_get_conn(stmt), selstr.data, NULL, READ_ONLY_QUERY, NULL);
|
|
if (QR_command_maybe_successful(res) && 1 == res->num_cached_rows)
|
|
ret = TRUE;
|
|
QR_Destructor(res);
|
|
cleanup:
|
|
if (!PQExpBufferDataBroken(selstr))
|
|
termPQExpBuffer(&selstr);
|
|
return ret;
|
|
}
|
|
|
|
static BOOL enlargeAdded(QResultClass *res, UInt4 number, const StatementClass *stmt)
|
|
{
|
|
UInt4 alloc;
|
|
int num_fields = res->num_fields;
|
|
|
|
alloc = res->ad_alloc;
|
|
if (0 == alloc)
|
|
alloc = number > 10 ? number : 10;
|
|
else
|
|
while (alloc < number)
|
|
{
|
|
alloc *= 2;
|
|
}
|
|
|
|
if (alloc <= res->ad_alloc)
|
|
return TRUE;
|
|
QR_REALLOC_return_with_error(res->added_keyset, KeySet, sizeof(KeySet) * alloc, res, "enlargeAdded failed", FALSE);
|
|
if (SQL_CURSOR_KEYSET_DRIVEN != stmt->options.cursor_type)
|
|
QR_REALLOC_return_with_error(res->added_tuples, TupleField, sizeof(TupleField) * num_fields * alloc, res, "enlargeAdded failed 2", FALSE);
|
|
res->ad_alloc = alloc;
|
|
return TRUE;
|
|
}
|
|
static void AddAdded(StatementClass *stmt, QResultClass *res, SQLLEN index, const TupleField *tuple_added)
|
|
{
|
|
KeySet *added_keyset, *keyset, keys;
|
|
TupleField *added_tuples = NULL, *tuple;
|
|
UInt4 ad_count;
|
|
Int2 num_fields;
|
|
|
|
if (!res) return;
|
|
num_fields = res->num_fields;
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering index=" FORMAT_LEN ", tuple=%p, num_fields=%d\n", index, tuple_added, num_fields);
|
|
ad_count = res->ad_count;
|
|
res->ad_count++;
|
|
if (QR_get_cursor(res))
|
|
index = -(SQLLEN)res->ad_count;
|
|
if (!tuple_added)
|
|
return;
|
|
KeySetSet(tuple_added, num_fields + res->num_key_fields, res->num_key_fields, &keys, TRUE);
|
|
keys.status = SQL_ROW_ADDED;
|
|
if (CC_is_in_trans(SC_get_conn(stmt)))
|
|
keys.status |= CURS_SELF_ADDING;
|
|
else
|
|
keys.status |= CURS_SELF_ADDED;
|
|
AddRollback(stmt, res, index, &keys, SQL_ADD);
|
|
|
|
if (!QR_get_cursor(res))
|
|
return;
|
|
if (ad_count > 0 && 0 == res->ad_alloc)
|
|
return;
|
|
if (!enlargeAdded(res, ad_count + 1, stmt))
|
|
return;
|
|
added_keyset = res->added_keyset;
|
|
added_tuples = res->added_tuples;
|
|
|
|
keyset = added_keyset + ad_count;
|
|
*keyset = keys;
|
|
if (added_tuples)
|
|
{
|
|
tuple = added_tuples + num_fields * ad_count;
|
|
memset(tuple, 0, sizeof(TupleField) * num_fields);
|
|
ReplaceCachedRows(tuple, tuple_added, num_fields, 1);
|
|
}
|
|
}
|
|
|
|
static void RemoveAdded(QResultClass *, SQLLEN);
|
|
static void RemoveUpdated(QResultClass *, SQLLEN);
|
|
static void RemoveUpdatedAfterTheKey(QResultClass *, SQLLEN, const KeySet*);
|
|
static void RemoveDeleted(QResultClass *, SQLLEN);
|
|
|
|
static void RemoveAdded(QResultClass *res, SQLLEN index)
|
|
{
|
|
SQLLEN rmidx, mv_count;
|
|
Int2 num_fields = res->num_fields;
|
|
KeySet *added_keyset;
|
|
TupleField *added_tuples;
|
|
|
|
MYLOG(0, "entering index=" FORMAT_LEN "\n", index);
|
|
if (index < 0)
|
|
rmidx = -index - 1;
|
|
else
|
|
rmidx = index - res->num_total_read;
|
|
if (rmidx >= res->ad_count)
|
|
return;
|
|
added_keyset = res->added_keyset + rmidx;
|
|
added_tuples = res->added_tuples + num_fields * rmidx;
|
|
ClearCachedRows(added_tuples, num_fields, 1);
|
|
mv_count = res->ad_count - rmidx - 1;
|
|
if (mv_count > 0)
|
|
{
|
|
memmove(added_keyset, added_keyset + 1, mv_count * sizeof(KeySet));
|
|
memmove(added_tuples, added_tuples + num_fields, mv_count * num_fields * sizeof(TupleField));
|
|
}
|
|
RemoveDeleted(res, index);
|
|
RemoveUpdated(res, index);
|
|
res->ad_count--;
|
|
MYLOG(0, "removed=1 count=%d\n", res->ad_count);
|
|
}
|
|
|
|
static void
|
|
CommitAdded(QResultClass *res)
|
|
{
|
|
KeySet *added_keyset;
|
|
int i;
|
|
UWORD status;
|
|
|
|
MYLOG(0, "entering res=%p\n", res);
|
|
if (!res || !res->added_keyset) return;
|
|
added_keyset = res->added_keyset;
|
|
for (i = res->ad_count - 1; i >= 0; i--)
|
|
{
|
|
status = added_keyset[i].status;
|
|
if (0 != (status & CURS_SELF_ADDING))
|
|
{
|
|
status |= CURS_SELF_ADDED;
|
|
status &= ~CURS_SELF_ADDING;
|
|
}
|
|
if (0 != (status & CURS_SELF_UPDATING))
|
|
{
|
|
status |= CURS_SELF_UPDATED;
|
|
status &= ~CURS_SELF_UPDATING;
|
|
}
|
|
if (0 != (status & CURS_SELF_DELETING))
|
|
{
|
|
status |= CURS_SELF_DELETED;
|
|
status &= ~CURS_SELF_DELETING;
|
|
}
|
|
if (status != added_keyset[i].status)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "!!Commit Added=" FORMAT_ULEN "(%d)\n", QR_get_num_total_read(res) + i, i);
|
|
added_keyset[i].status = status;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
AddDeleted(QResultClass *res, SQLULEN index, const KeySet *keyset)
|
|
{
|
|
int i;
|
|
Int2 dl_count, new_alloc;
|
|
SQLLEN *deleted;
|
|
KeySet *deleted_keyset;
|
|
UWORD status;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering " FORMAT_ULEN "\n", index);
|
|
dl_count = res->dl_count;
|
|
res->dl_count++;
|
|
if (!QR_get_cursor(res))
|
|
return TRUE;
|
|
if (!res->deleted)
|
|
{
|
|
dl_count = 0;
|
|
new_alloc = 10;
|
|
QR_MALLOC_return_with_error(res->deleted, SQLLEN, sizeof(SQLLEN) * new_alloc, res, "Deleted index malloc error", FALSE);
|
|
QR_MALLOC_return_with_error(res->deleted_keyset, KeySet, sizeof(KeySet) * new_alloc, res, "Deleted keyset malloc error", FALSE);
|
|
deleted = res->deleted;
|
|
deleted_keyset = res->deleted_keyset;
|
|
res->dl_alloc = new_alloc;
|
|
}
|
|
else
|
|
{
|
|
if (dl_count >= res->dl_alloc)
|
|
{
|
|
new_alloc = res->dl_alloc * 2;
|
|
res->dl_alloc = 0;
|
|
QR_REALLOC_return_with_error(res->deleted, SQLLEN, sizeof(SQLLEN) * new_alloc, res, "Deleted index realloc error", FALSE);
|
|
deleted = res->deleted;
|
|
QR_REALLOC_return_with_error(res->deleted_keyset, KeySet, sizeof(KeySet) * new_alloc, res, "Deleted KeySet realloc error", FALSE);
|
|
deleted_keyset = res->deleted_keyset;
|
|
res->dl_alloc = new_alloc;
|
|
}
|
|
/* sort deleted indexes in ascending order */
|
|
for (i = 0, deleted = res->deleted, deleted_keyset = res->deleted_keyset; i < dl_count; i++, deleted++, deleted_keyset++)
|
|
{
|
|
if (index < *deleted)
|
|
break;
|
|
}
|
|
memmove(deleted + 1, deleted, sizeof(SQLLEN) * (dl_count - i));
|
|
memmove(deleted_keyset + 1, deleted_keyset, sizeof(KeySet) * (dl_count - i));
|
|
}
|
|
*deleted = index;
|
|
*deleted_keyset = *keyset;
|
|
status = keyset->status;
|
|
status &= (~KEYSET_INFO_PUBLIC);
|
|
status |= SQL_ROW_DELETED;
|
|
if (CC_is_in_trans(QR_get_conn(res)))
|
|
{
|
|
status |= CURS_SELF_DELETING;
|
|
QR_get_conn(res)->result_uncommitted = 1;
|
|
}
|
|
else
|
|
{
|
|
status &= ~(CURS_SELF_ADDING | CURS_SELF_UPDATING | CURS_SELF_DELETING);
|
|
status |= CURS_SELF_DELETED;
|
|
}
|
|
deleted_keyset->status = status;
|
|
res->dl_count = dl_count + 1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
RemoveDeleted(QResultClass *res, SQLLEN index)
|
|
{
|
|
int i, mv_count, rm_count = 0;
|
|
SQLLEN pidx, midx;
|
|
SQLLEN *deleted, num_read = QR_get_num_total_read(res);
|
|
KeySet *deleted_keyset;
|
|
|
|
MYLOG(0, "entering index=" FORMAT_LEN "\n", index);
|
|
if (index < 0)
|
|
{
|
|
midx = index;
|
|
pidx = num_read - index - 1;
|
|
}
|
|
else
|
|
{
|
|
pidx = index;
|
|
if (index >= num_read)
|
|
midx = num_read - index - 1;
|
|
else
|
|
midx = index;
|
|
}
|
|
for (i = 0; i < res->dl_count; i++)
|
|
{
|
|
if (pidx == res->deleted[i] ||
|
|
midx == res->deleted[i])
|
|
{
|
|
mv_count = res->dl_count - i - 1;
|
|
if (mv_count > 0)
|
|
{
|
|
deleted = res->deleted + i;
|
|
deleted_keyset = res->deleted_keyset + i;
|
|
memmove(deleted, deleted + 1, mv_count * sizeof(SQLLEN));
|
|
memmove(deleted_keyset, deleted_keyset + 1, mv_count * sizeof(KeySet));
|
|
}
|
|
res->dl_count--;
|
|
rm_count++;
|
|
}
|
|
}
|
|
MYLOG(0, "removed count=%d,%d\n", rm_count, res->dl_count);
|
|
}
|
|
|
|
static void
|
|
CommitDeleted(QResultClass *res)
|
|
{
|
|
int i;
|
|
SQLLEN *deleted;
|
|
KeySet *deleted_keyset;
|
|
UWORD status;
|
|
|
|
if (!res->deleted)
|
|
return;
|
|
|
|
for (i = 0, deleted = res->deleted, deleted_keyset = res->deleted_keyset; i < res->dl_count; i++, deleted++, deleted_keyset++)
|
|
{
|
|
status = deleted_keyset->status;
|
|
if (0 != (status & CURS_SELF_ADDING))
|
|
{
|
|
status |= CURS_SELF_ADDED;
|
|
status &= ~CURS_SELF_ADDING;
|
|
}
|
|
if (0 != (status & CURS_SELF_UPDATING))
|
|
{
|
|
status |= CURS_SELF_UPDATED;
|
|
status &= ~CURS_SELF_UPDATING;
|
|
}
|
|
if (0 != (status & CURS_SELF_DELETING))
|
|
{
|
|
status |= CURS_SELF_DELETED;
|
|
status &= ~CURS_SELF_DELETING;
|
|
}
|
|
if (status != deleted_keyset->status)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "Deleted=" FORMAT_LEN "(%d)\n", *deleted, i);
|
|
deleted_keyset->status = status;
|
|
}
|
|
}
|
|
}
|
|
|
|
static BOOL
|
|
enlargeUpdated(QResultClass *res, Int4 number, const StatementClass *stmt)
|
|
{
|
|
Int2 alloc;
|
|
|
|
alloc = res->up_alloc;
|
|
if (0 == alloc)
|
|
alloc = number > 10 ? number : 10;
|
|
else
|
|
while (alloc < number)
|
|
{
|
|
alloc *= 2;
|
|
}
|
|
if (alloc <= res->up_alloc)
|
|
return TRUE;
|
|
|
|
QR_REALLOC_return_with_error(res->updated, SQLLEN, sizeof(SQLLEN) * alloc, res, "enlargeUpdated failed", FALSE);
|
|
QR_REALLOC_return_with_error(res->updated_keyset, KeySet, sizeof(KeySet) * alloc, res, "enlargeUpdated failed 2", FALSE);
|
|
if (SQL_CURSOR_KEYSET_DRIVEN != stmt->options.cursor_type)
|
|
QR_REALLOC_return_with_error(res->updated_tuples, TupleField, sizeof(TupleField) * res->num_fields * alloc, res, "enlargeUpdated failed 3", FALSE);
|
|
res->up_alloc = alloc;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
AddUpdated(StatementClass *stmt, SQLLEN index, const KeySet *keyset, const TupleField *tuple_updated)
|
|
{
|
|
QResultClass *res;
|
|
SQLLEN *updated;
|
|
KeySet *updated_keyset;
|
|
TupleField *updated_tuples = NULL, *tuple;
|
|
/* SQLLEN res_ridx; */
|
|
UInt2 up_count;
|
|
BOOL is_in_trans;
|
|
SQLLEN upd_idx, upd_add_idx;
|
|
Int2 num_fields;
|
|
int i;
|
|
UWORD status;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering index=" FORMAT_LEN "\n", index);
|
|
if (!stmt) return;
|
|
if (res = SC_get_Curres(stmt), !res) return;
|
|
if (!keyset) return;
|
|
if (!QR_get_cursor(res)) return;
|
|
up_count = res->up_count;
|
|
if (up_count > 0 && 0 == res->up_alloc) return;
|
|
num_fields = res->num_fields;
|
|
|
|
if (!tuple_updated)
|
|
return;
|
|
upd_idx = -1;
|
|
upd_add_idx = -1;
|
|
updated = res->updated;
|
|
is_in_trans = CC_is_in_trans(SC_get_conn(stmt));
|
|
updated_keyset = res->updated_keyset;
|
|
status = keyset->status;
|
|
status &= (~KEYSET_INFO_PUBLIC);
|
|
status |= SQL_ROW_UPDATED;
|
|
if (is_in_trans)
|
|
status |= CURS_SELF_UPDATING;
|
|
else
|
|
{
|
|
for (i = up_count - 1; i >= 0; i--)
|
|
{
|
|
if (updated[i] == index)
|
|
break;
|
|
}
|
|
if (i >= 0)
|
|
upd_idx = i;
|
|
else
|
|
{
|
|
SQLLEN num_totals = QR_get_num_total_tuples(res);
|
|
if (index >= num_totals)
|
|
upd_add_idx = num_totals - index;
|
|
}
|
|
status |= CURS_SELF_UPDATED;
|
|
status &= ~(CURS_SELF_ADDING | CURS_SELF_UPDATING | CURS_SELF_DELETING);
|
|
}
|
|
|
|
tuple = NULL;
|
|
/* update the corresponding add(updat)ed info */
|
|
if (upd_add_idx >= 0)
|
|
{
|
|
res->added_keyset[upd_add_idx].status = status;
|
|
if (res->added_tuples)
|
|
{
|
|
tuple = res->added_tuples + num_fields * upd_add_idx;
|
|
ClearCachedRows(tuple, num_fields, 1);
|
|
}
|
|
}
|
|
else if (upd_idx >= 0)
|
|
{
|
|
res->updated_keyset[upd_idx].status = status;
|
|
if (res->updated_tuples)
|
|
{
|
|
tuple = res->added_tuples + num_fields * upd_add_idx;
|
|
ClearCachedRows(tuple, num_fields, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!enlargeUpdated(res, res->up_count + 1, stmt))
|
|
return;
|
|
updated = res->updated;
|
|
updated_keyset = res->updated_keyset;
|
|
updated_tuples = res->updated_tuples;
|
|
upd_idx = up_count;
|
|
updated[up_count] = index;
|
|
updated_keyset[up_count] = *keyset;
|
|
updated_keyset[up_count].status = status;
|
|
if (updated_tuples)
|
|
{
|
|
tuple = updated_tuples + num_fields * up_count;
|
|
memset(tuple, 0, sizeof(TupleField) * num_fields);
|
|
}
|
|
res->up_count++;
|
|
}
|
|
|
|
if (tuple)
|
|
ReplaceCachedRows(tuple, tuple_updated, num_fields, 1);
|
|
if (is_in_trans)
|
|
SC_get_conn(stmt)->result_uncommitted = 1;
|
|
MYLOG(0, "up_count=%d\n", res->up_count);
|
|
}
|
|
|
|
static void
|
|
RemoveUpdated(QResultClass *res, SQLLEN index)
|
|
{
|
|
MYLOG(0, "entering index=" FORMAT_LEN "\n", index);
|
|
RemoveUpdatedAfterTheKey(res, index, NULL);
|
|
}
|
|
|
|
static void
|
|
RemoveUpdatedAfterTheKey(QResultClass *res, SQLLEN index, const KeySet *keyset)
|
|
{
|
|
SQLLEN *updated, num_read = QR_get_num_total_read(res);
|
|
KeySet *updated_keyset;
|
|
TupleField *updated_tuples = NULL;
|
|
SQLLEN pidx, midx, mv_count;
|
|
int i, num_fields = res->num_fields, rm_count = 0;
|
|
|
|
MYLOG(0, "entering " FORMAT_LEN ",(%u,%u)\n", index, keyset ? keyset->blocknum : 0, keyset ? keyset->offset : 0);
|
|
if (index < 0)
|
|
{
|
|
midx = index;
|
|
pidx = num_read - index - 1;
|
|
}
|
|
else
|
|
{
|
|
pidx = index;
|
|
if (index >= num_read)
|
|
midx = num_read - index - 1;
|
|
else
|
|
midx = index;
|
|
}
|
|
for (i = 0; i < res->up_count; i++)
|
|
{
|
|
updated = res->updated + i;
|
|
if (pidx == *updated ||
|
|
midx == *updated)
|
|
{
|
|
updated_keyset = res->updated_keyset + i;
|
|
if (keyset &&
|
|
updated_keyset->blocknum == keyset->blocknum &&
|
|
updated_keyset->offset == keyset->offset)
|
|
break;
|
|
updated_tuples = NULL;
|
|
if (res->updated_tuples)
|
|
{
|
|
updated_tuples = res->updated_tuples + i * num_fields;
|
|
ClearCachedRows(updated_tuples, num_fields, 1);
|
|
}
|
|
mv_count = res->up_count - i -1;
|
|
if (mv_count > 0)
|
|
{
|
|
memmove(updated, updated + 1, sizeof(SQLLEN) * mv_count);
|
|
memmove(updated_keyset, updated_keyset + 1, sizeof(KeySet) * mv_count);
|
|
if (updated_tuples)
|
|
memmove(updated_tuples, updated_tuples + num_fields, sizeof(TupleField) * num_fields * mv_count);
|
|
}
|
|
res->up_count--;
|
|
rm_count++;
|
|
}
|
|
}
|
|
MYLOG(0, "removed count=%d,%d\n", rm_count, res->up_count);
|
|
}
|
|
|
|
static void
|
|
CommitUpdated(QResultClass *res)
|
|
{
|
|
KeySet *updated_keyset;
|
|
int i;
|
|
UWORD status;
|
|
|
|
MYLOG(0, "entering res=%p\n", res);
|
|
if (!res) return;
|
|
if (!QR_get_cursor(res))
|
|
return;
|
|
if (res->up_count <= 0)
|
|
return;
|
|
if (updated_keyset = res->updated_keyset, !updated_keyset)
|
|
return;
|
|
for (i = res->up_count - 1; i >= 0; i--)
|
|
{
|
|
status = updated_keyset[i].status;
|
|
if (0 != (status & CURS_SELF_UPDATING))
|
|
{
|
|
status &= ~CURS_SELF_UPDATING;
|
|
status |= CURS_SELF_UPDATED;
|
|
}
|
|
if (0 != (status & CURS_SELF_ADDING))
|
|
{
|
|
status &= ~CURS_SELF_ADDING;
|
|
status |= CURS_SELF_ADDED;
|
|
}
|
|
if (0 != (status & CURS_SELF_DELETING))
|
|
{
|
|
status &= ~CURS_SELF_DELETING;
|
|
status |= CURS_SELF_DELETED;
|
|
}
|
|
if (status != updated_keyset[i].status)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "!!Commit Updated=" FORMAT_LEN "(%d)\n", res->updated[i], i);
|
|
updated_keyset[i].status = status;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
DiscardRollback(StatementClass *stmt, QResultClass *res)
|
|
{
|
|
int i;
|
|
SQLLEN index, kres_ridx;
|
|
UWORD status;
|
|
Rollback *rollback;
|
|
KeySet *keyset;
|
|
BOOL kres_is_valid;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering\n");
|
|
if (QR_get_cursor(res))
|
|
{
|
|
CommitAdded(res);
|
|
CommitUpdated(res);
|
|
CommitDeleted(res);
|
|
return;
|
|
}
|
|
|
|
if (0 == res->rb_count || NULL == res->rollback)
|
|
return;
|
|
rollback = res->rollback;
|
|
keyset = res->keyset;
|
|
for (i = 0; i < res->rb_count; i++)
|
|
{
|
|
index = rollback[i].index;
|
|
status = 0;
|
|
kres_is_valid = FALSE;
|
|
if (index >= 0)
|
|
{
|
|
kres_ridx = GIdx2KResIdx(index, stmt, res);
|
|
if (kres_ridx >= 0 && kres_ridx < res->num_cached_keys)
|
|
{
|
|
kres_is_valid = TRUE;
|
|
status = keyset[kres_ridx].status;
|
|
}
|
|
}
|
|
if (kres_is_valid)
|
|
{
|
|
keyset[kres_ridx].status &= ~(CURS_SELF_DELETING | CURS_SELF_UPDATING | CURS_SELF_ADDING);
|
|
keyset[kres_ridx].status |= ((status & (CURS_SELF_DELETING | CURS_SELF_UPDATING | CURS_SELF_ADDING)) << 3);
|
|
}
|
|
}
|
|
free(rollback);
|
|
res->rollback = NULL;
|
|
res->rb_count = res->rb_alloc = 0;
|
|
}
|
|
|
|
static QResultClass *positioned_load(StatementClass *stmt, UInt4 flag, const UInt4 *oidint, const char *tid);
|
|
|
|
static void
|
|
UndoRollback(StatementClass *stmt, QResultClass *res, BOOL partial)
|
|
{
|
|
Int4 i, rollbp;
|
|
SQLLEN index, ridx, kres_ridx;
|
|
UWORD status;
|
|
Rollback *rollback;
|
|
KeySet *keyset, keys, *wkey = NULL;
|
|
BOOL curs = (NULL != QR_get_cursor(res)), texist, kres_is_valid;
|
|
|
|
if (0 == res->rb_count || NULL == res->rollback)
|
|
return;
|
|
rollback = res->rollback;
|
|
keyset = res->keyset;
|
|
|
|
rollbp = 0;
|
|
if (partial)
|
|
{
|
|
SQLLEN pidx, midx;
|
|
int rollbps;
|
|
int doubtp;
|
|
int j;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, " ");
|
|
for (i = 0, doubtp = 0; i < res->rb_count; i++)
|
|
{
|
|
keys.status = 0;
|
|
keys.blocknum = rollback[i].blocknum;
|
|
keys.offset = rollback[i].offset;
|
|
keys.oid = rollback[i].oid;
|
|
texist = tupleExists(stmt, &keys);
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "texist[%d]=%d", i, texist);
|
|
if (SQL_ADD == rollback[i].option)
|
|
{
|
|
if (texist)
|
|
doubtp = i + 1;
|
|
}
|
|
else if (SQL_REFRESH == rollback[i].option)
|
|
{
|
|
if (texist || doubtp == i)
|
|
doubtp = i + 1;
|
|
}
|
|
else
|
|
{
|
|
if (texist)
|
|
break;
|
|
if (doubtp == i)
|
|
doubtp = i + 1;
|
|
}
|
|
MYPRINTF(DETAIL_LOG_LEVEL, " (doubtp=%d)", doubtp);
|
|
}
|
|
rollbp = i;
|
|
MYPRINTF(DETAIL_LOG_LEVEL, " doubtp=%d,rollbp=%d\n", doubtp, rollbp);
|
|
do
|
|
{
|
|
rollbps = rollbp;
|
|
for (i = doubtp; i < rollbp; i++)
|
|
{
|
|
index = rollback[i].index;
|
|
if (SQL_ADD == rollback[i].option)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "index[%d]=" FORMAT_LEN "\n", i, index);
|
|
if (index < 0)
|
|
{
|
|
midx = index;
|
|
pidx = res->num_total_read - index - 1;
|
|
}
|
|
else
|
|
{
|
|
pidx = index;
|
|
midx = res->num_total_read - index - 1;
|
|
}
|
|
MYLOG(DETAIL_LOG_LEVEL, "pidx=" FORMAT_LEN ",midx=" FORMAT_LEN "\n", pidx, midx);
|
|
for (j = rollbp - 1; j > i; j--)
|
|
{
|
|
if (rollback[j].index == midx ||
|
|
rollback[j].index == pidx)
|
|
{
|
|
if (SQL_DELETE == rollback[j].option)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "delete[%d].index=" FORMAT_LEN "\n", j, rollback[j].index);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (j <= i)
|
|
{
|
|
rollbp = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} while (rollbp < rollbps);
|
|
}
|
|
MYLOG(DETAIL_LOG_LEVEL, "rollbp=%d\n", rollbp);
|
|
|
|
for (i = res->rb_count - 1; i >= rollbp; i--)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "do %d(%d)\n", i, rollback[i].option);
|
|
index = rollback[i].index;
|
|
if (curs)
|
|
{
|
|
if (SQL_ADD == rollback[i].option)
|
|
RemoveAdded(res, index);
|
|
RemoveDeleted(res, index);
|
|
keys.status = 0;
|
|
keys.blocknum = rollback[i].blocknum;
|
|
keys.offset = rollback[i].offset;
|
|
keys.oid = rollback[i].oid;
|
|
/* RemoveUpdatedAfterTheKey(res, index, &keys); is no longer needed? */
|
|
RemoveUpdated(res, index);
|
|
}
|
|
status = 0;
|
|
kres_is_valid = FALSE;
|
|
if (index >= 0)
|
|
{
|
|
kres_ridx = GIdx2KResIdx(index, stmt, res);
|
|
if (kres_ridx >= 0 && kres_ridx < res->num_cached_keys)
|
|
{
|
|
kres_is_valid = TRUE;
|
|
wkey = keyset + kres_ridx;
|
|
status = wkey->status;
|
|
}
|
|
}
|
|
MYLOG(DETAIL_LOG_LEVEL, " index=" FORMAT_LEN " status=%hx", index, status);
|
|
if (kres_is_valid)
|
|
{
|
|
QResultClass *qres;
|
|
Int2 num_fields = res->num_fields;
|
|
|
|
ridx = GIdx2CacheIdx(index, stmt, res);
|
|
if (SQL_ADD == rollback[i].option)
|
|
{
|
|
if (ridx >=0 && ridx < res->num_cached_rows)
|
|
{
|
|
TupleField *tuple = res->backend_tuples + res->num_fields * ridx;
|
|
ClearCachedRows(tuple, res->num_fields, 1);
|
|
res->num_cached_rows--;
|
|
}
|
|
res->num_cached_keys--;
|
|
if (!curs)
|
|
res->ad_count--;
|
|
}
|
|
else if (SQL_REFRESH == rollback[i].option)
|
|
continue;
|
|
else
|
|
{
|
|
MYPRINTF(DETAIL_LOG_LEVEL, " (%u, %u)", wkey->blocknum, wkey->offset);
|
|
wkey->blocknum = rollback[i].blocknum;
|
|
wkey->offset = rollback[i].offset;
|
|
wkey->oid = rollback[i].oid;
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "->(%u, %u)", wkey->blocknum, wkey->offset);
|
|
wkey->status &= ~KEYSET_INFO_PUBLIC;
|
|
if (SQL_DELETE == rollback[i].option)
|
|
wkey->status &= ~CURS_SELF_DELETING;
|
|
else if (SQL_UPDATE == rollback[i].option)
|
|
wkey->status &= ~CURS_SELF_UPDATING;
|
|
wkey->status |= CURS_NEEDS_REREAD;
|
|
if (ridx >=0 && ridx < res->num_cached_rows)
|
|
{
|
|
char tidval[32];
|
|
Oid oid = wkey->oid;
|
|
|
|
SPRINTF_FIXED(tidval,
|
|
"(%u,%u)", wkey->blocknum, wkey->offset);
|
|
qres = positioned_load(stmt, 0, &oid, tidval);
|
|
if (QR_command_maybe_successful(qres) &&
|
|
QR_get_num_cached_tuples(qres) == 1)
|
|
{
|
|
MoveCachedRows(res->backend_tuples + num_fields * ridx, qres->backend_tuples, num_fields, 1);
|
|
wkey->status &= ~CURS_NEEDS_REREAD;
|
|
}
|
|
QR_Destructor(qres);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "\n");
|
|
res->rb_count = rollbp;
|
|
if (0 == rollbp)
|
|
{
|
|
free(rollback);
|
|
res->rollback = NULL;
|
|
res->rb_alloc = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
ProcessRollback(ConnectionClass *conn, BOOL undo, BOOL partial)
|
|
{
|
|
int i;
|
|
StatementClass *stmt;
|
|
QResultClass *res;
|
|
|
|
for (i = 0; i < conn->num_stmts; i++)
|
|
{
|
|
if (stmt = conn->stmts[i], !stmt)
|
|
continue;
|
|
for (res = SC_get_Result(stmt); res; res = res->next)
|
|
{
|
|
if (undo)
|
|
UndoRollback(stmt, res, partial);
|
|
else
|
|
DiscardRollback(stmt, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#define LATEST_TUPLE_LOAD 1L
|
|
#define USE_INSERTED_TID (1L << 1)
|
|
static QResultClass *
|
|
positioned_load(StatementClass *stmt, UInt4 flag, const UInt4 *oidint, const char *tidval)
|
|
{
|
|
CSTR func = "positioned_load";
|
|
QResultClass *qres = NULL;
|
|
PQExpBufferData selstr = {0};
|
|
BOOL latest = ((flag & LATEST_TUPLE_LOAD) != 0);
|
|
TABLE_INFO *ti = stmt->ti[0];
|
|
const char *bestqual = GET_NAME(ti->bestqual);
|
|
const ssize_t from_pos = stmt->load_from_pos;
|
|
const char *load_stmt = stmt->load_statement;
|
|
|
|
MYLOG(DETAIL_LOG_LEVEL, "entering bestitem=%s bestqual=%s\n", SAFE_NAME(ti->bestitem), SAFE_NAME(ti->bestqual));
|
|
initPQExpBuffer(&selstr);
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
if (TI_has_subclass(ti))
|
|
{
|
|
OID tableoid = *oidint;
|
|
char table_fqn[256];
|
|
const char *quoted_table;
|
|
|
|
quoted_table = ti_quote(stmt, tableoid, table_fqn, sizeof(table_fqn));
|
|
if (tidval)
|
|
{
|
|
if (latest)
|
|
{
|
|
printfPQExpBuffer(&selstr,
|
|
"%.*sfrom %s where ctid = (select currtid2('%s', '%s'))",
|
|
(int) from_pos, load_stmt,
|
|
quoted_table,
|
|
quoted_table,
|
|
tidval);
|
|
}
|
|
else
|
|
printfPQExpBuffer(&selstr, "%.*sfrom %s where ctid = '%s'", (int) from_pos, load_stmt, quoted_table, tidval);
|
|
}
|
|
else if ((flag & USE_INSERTED_TID) != 0)
|
|
printfPQExpBuffer(&selstr, "%.*sfrom %s where ctid = (select currtid(0, '(0,0)'))", (int) from_pos, load_stmt, quoted_table);
|
|
/*
|
|
else if (bestitem && oidint)
|
|
{
|
|
}
|
|
*/
|
|
else
|
|
{
|
|
SC_set_error(stmt,STMT_INTERNAL_ERROR, "can't find added and updating row because of the lack of oid", func);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CSTR andqual = " and ";
|
|
BOOL andExist = TRUE;
|
|
|
|
if (tidval)
|
|
{
|
|
if (latest)
|
|
{
|
|
char table_fqn[256];
|
|
|
|
printfPQExpBuffer(&selstr,
|
|
"%s where ctid = (select currtid2('%s', '%s'))",
|
|
load_stmt,
|
|
ti_quote(stmt, 0, table_fqn, sizeof(table_fqn)),
|
|
tidval);
|
|
}
|
|
else
|
|
printfPQExpBuffer(&selstr, "%s where ctid = '%s'", load_stmt, tidval);
|
|
}
|
|
else if ((flag & USE_INSERTED_TID) != 0)
|
|
printfPQExpBuffer(&selstr, "%s where ctid = (select currtid(0, '(0,0)'))", load_stmt);
|
|
else if (bestqual)
|
|
{
|
|
andExist = FALSE;
|
|
printfPQExpBuffer(&selstr, "%s where ", load_stmt);
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt,STMT_INTERNAL_ERROR, "can't find added and updating row because of the lack of oid", func);
|
|
goto cleanup;
|
|
}
|
|
if (bestqual && oidint)
|
|
{
|
|
if (andExist)
|
|
appendPQExpBufferStr(&selstr, andqual);
|
|
appendPQExpBuffer(&selstr, bestqual, *oidint);
|
|
}
|
|
}
|
|
|
|
if (PQExpBufferDataBroken(selstr))
|
|
{
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Could not allocate memory positioned_load()", func);
|
|
goto cleanup;
|
|
}
|
|
MYLOG(0, "selstr=%s\n", selstr.data);
|
|
qres = CC_send_query(SC_get_conn(stmt), selstr.data, NULL, READ_ONLY_QUERY, stmt);
|
|
cleanup:
|
|
#undef return
|
|
if (!PQExpBufferDataBroken(selstr))
|
|
termPQExpBuffer(&selstr);
|
|
return qres;
|
|
}
|
|
|
|
BOOL QR_get_last_bookmark(const QResultClass *res, Int4 index, KeySet *keyset)
|
|
{
|
|
int i;
|
|
|
|
if (res->dl_count > 0 && res->deleted)
|
|
{
|
|
for (i = 0; i < res->dl_count; i++)
|
|
{
|
|
if (res->deleted[i] == index)
|
|
{
|
|
*keyset = res->deleted_keyset[i];
|
|
return TRUE;
|
|
}
|
|
if (res->deleted[i] > index)
|
|
break;
|
|
}
|
|
}
|
|
if (res->up_count > 0 && res->updated)
|
|
{
|
|
for (i = res->up_count - 1; i >= 0; i--)
|
|
{
|
|
if (res->updated[i] == index)
|
|
{
|
|
*keyset = res->updated_keyset[i];
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static RETCODE
|
|
SC_pos_reload_with_key(StatementClass *stmt, SQLULEN global_ridx, UInt2 *count, Int4 logKind, const KeySet *keyset)
|
|
{
|
|
CSTR func = "SC_pos_reload_with_key";
|
|
int res_cols;
|
|
UInt2 rcnt;
|
|
SQLLEN kres_ridx;
|
|
OID oidint;
|
|
QResultClass *res, *qres;
|
|
IRDFields *irdflds = SC_get_IRDF(stmt);
|
|
RETCODE ret = SQL_ERROR;
|
|
char tidval[32];
|
|
BOOL use_ctid = TRUE;
|
|
BOOL idx_exist = TRUE;
|
|
|
|
MYLOG(0, "entering fi=%p ti=%p\n", irdflds->fi, stmt->ti);
|
|
rcnt = 0;
|
|
if (count)
|
|
*count = 0;
|
|
if (!(res = SC_get_Curres(stmt)))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in SC_pos_reload.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
kres_ridx = GIdx2KResIdx(global_ridx, stmt, res);
|
|
if (kres_ridx < 0 || kres_ridx >= res->num_cached_keys)
|
|
{
|
|
if (NULL == keyset || keyset->offset == 0)
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_OUT_OF_RANGE, "the target keys are out of the rowset", func);
|
|
return SQL_ERROR;
|
|
}
|
|
idx_exist = FALSE;
|
|
}
|
|
else if (0 != (res->keyset[kres_ridx].status & CURS_SELF_ADDING))
|
|
{
|
|
if (NULL == keyset || keyset->offset == 0)
|
|
{
|
|
use_ctid = FALSE;
|
|
MYLOG(0, "The tuple is currently being added and can't use ctid\n");
|
|
}
|
|
}
|
|
|
|
if (SC_update_not_ready(stmt))
|
|
parse_statement(stmt, TRUE); /* not preferable */
|
|
if (!SC_is_updatable(stmt))
|
|
{
|
|
stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
SC_set_error(stmt, STMT_INVALID_OPTION_IDENTIFIER, "the statement is read-only", func);
|
|
return SQL_ERROR;
|
|
}
|
|
/* old oid & tid */
|
|
if (idx_exist)
|
|
{
|
|
UInt4 blocknum;
|
|
UInt2 offset;
|
|
|
|
if (!(oidint = getOid(res, kres_ridx)))
|
|
{
|
|
if (!strcmp(SAFE_NAME(stmt->ti[0]->bestitem), OID_NAME))
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the row was already deleted ?", func);
|
|
return SQL_SUCCESS_WITH_INFO;
|
|
}
|
|
}
|
|
getTid(res, kres_ridx, &blocknum, &offset);
|
|
SPRINTF_FIXED(tidval, "(%u, %u)", blocknum, offset);
|
|
}
|
|
res_cols = getNumResultCols(res);
|
|
if (keyset) /* after or update */
|
|
{
|
|
char tid[32];
|
|
|
|
oidint = keyset->oid;
|
|
SPRINTF_FIXED(tid, "(%u,%hu)", keyset->blocknum, keyset->offset);
|
|
qres = positioned_load(stmt, 0, &oidint, tid);
|
|
}
|
|
else
|
|
{
|
|
if (use_ctid)
|
|
qres = positioned_load(stmt, LATEST_TUPLE_LOAD, &oidint, tidval);
|
|
else
|
|
qres = positioned_load(stmt, 0, &oidint, NULL);
|
|
keyset = res->keyset + kres_ridx;
|
|
}
|
|
if (!QR_command_maybe_successful(qres))
|
|
{
|
|
ret = SQL_ERROR;
|
|
if (qres)
|
|
SC_replace_error_with_res(stmt, STMT_ERROR_TAKEN_FROM_BACKEND, "positioned_load failed", qres, TRUE);
|
|
}
|
|
else if (rcnt = (UInt2) QR_get_num_cached_tuples(qres), rcnt == 1)
|
|
{
|
|
SQLLEN res_ridx;
|
|
|
|
switch (logKind)
|
|
{
|
|
case 0:
|
|
case SQL_FETCH_BY_BOOKMARK:
|
|
break;
|
|
case SQL_UPDATE:
|
|
AddUpdated(stmt, global_ridx, keyset, qres->tupleField);
|
|
break;
|
|
default:
|
|
AddRollback(stmt, res, global_ridx, keyset, logKind);
|
|
}
|
|
res_ridx = GIdx2CacheIdx(global_ridx, stmt, res);
|
|
if (res_ridx >= 0 && res_ridx < QR_get_num_cached_tuples(res))
|
|
{
|
|
TupleField *tuple_old, *tuple_new;
|
|
int effective_fields = res_cols;
|
|
|
|
tuple_old = res->backend_tuples + res->num_fields * res_ridx;
|
|
|
|
QR_set_position(qres, 0);
|
|
tuple_new = qres->tupleField;
|
|
if (SQL_CURSOR_KEYSET_DRIVEN == stmt->options.cursor_type &&
|
|
strcmp(tuple_new[qres->num_fields - res->num_key_fields].value, tidval))
|
|
res->keyset[kres_ridx].status |= SQL_ROW_UPDATED;
|
|
KeySetSet(tuple_new, qres->num_fields, res->num_key_fields, res->keyset + kres_ridx, FALSE);
|
|
MoveCachedRows(tuple_old, tuple_new, effective_fields, 1);
|
|
}
|
|
ret = SQL_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the content was deleted after last fetch", func);
|
|
ret = SQL_SUCCESS_WITH_INFO;
|
|
AddRollback(stmt, res, global_ridx, keyset, logKind);
|
|
if (idx_exist)
|
|
{
|
|
if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN)
|
|
{
|
|
res->keyset[kres_ridx].status |= SQL_ROW_DELETED;
|
|
}
|
|
}
|
|
}
|
|
QR_Destructor(qres);
|
|
if (count)
|
|
*count = rcnt;
|
|
return ret;
|
|
}
|
|
|
|
|
|
RETCODE
|
|
SC_pos_reload(StatementClass *stmt, SQLULEN global_ridx, UInt2 *count, Int4 logKind)
|
|
{
|
|
return SC_pos_reload_with_key(stmt, global_ridx, count, logKind, NULL);
|
|
}
|
|
|
|
static const int pre_fetch_count = 32;
|
|
static SQLLEN LoadFromKeyset(StatementClass *stmt, QResultClass * res, int rows_per_fetch, SQLLEN limitrow)
|
|
{
|
|
CSTR func = "LoadFromKeyset";
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
SQLLEN i;
|
|
int j, rowc, rcnt = 0;
|
|
OID oid;
|
|
UInt4 blocknum;
|
|
SQLLEN kres_ridx;
|
|
UInt2 offset;
|
|
PQExpBufferData qval = {0};
|
|
int keys_per_fetch = 10;
|
|
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
for (i = SC_get_rowset_start(stmt), kres_ridx = GIdx2KResIdx(i, stmt, res), rowc = 0;; i++, kres_ridx++)
|
|
{
|
|
if (i >= limitrow)
|
|
{
|
|
if (!rowc)
|
|
break;
|
|
if (res->reload_count > 0)
|
|
{
|
|
for (j = rowc; j < keys_per_fetch; j++)
|
|
{
|
|
appendPQExpBufferStr(&qval, j ? ",NULL" : "NULL");
|
|
}
|
|
}
|
|
rowc = -1; /* end of loop */
|
|
}
|
|
if (rowc < 0 || rowc >= keys_per_fetch)
|
|
{
|
|
QResultClass *qres;
|
|
|
|
appendPQExpBufferStr(&qval, ")");
|
|
if (PQExpBufferDataBroken(qval))
|
|
{
|
|
rcnt = -1;
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory in LoadFromKeyset()", func);
|
|
goto cleanup;
|
|
}
|
|
qres = CC_send_query(conn, qval.data, NULL, CREATE_KEYSET | READ_ONLY_QUERY, stmt);
|
|
if (QR_command_maybe_successful(qres))
|
|
{
|
|
SQLLEN j, k, l;
|
|
Int2 m;
|
|
TupleField *tuple, *tuplew;
|
|
UInt4 bln;
|
|
UInt2 off;
|
|
|
|
for (j = 0; j < QR_get_num_total_read(qres); j++)
|
|
{
|
|
oid = getOid(qres, j);
|
|
getTid(qres, j, &blocknum, &offset);
|
|
for (k = SC_get_rowset_start(stmt); k < limitrow; k++)
|
|
{
|
|
getTid(res, k, &bln, &off);
|
|
if (oid == getOid(res, k) &&
|
|
bln == blocknum &&
|
|
off == offset)
|
|
{
|
|
l = GIdx2CacheIdx(k, stmt, res);
|
|
tuple = res->backend_tuples + res->num_fields * l;
|
|
tuplew = qres->backend_tuples + qres->num_fields * j;
|
|
for (m = 0; m < res->num_fields; m++, tuple++, tuplew++)
|
|
{
|
|
if (tuple->len > 0 && tuple->value)
|
|
free(tuple->value);
|
|
tuple->value = tuplew->value;
|
|
tuple->len = tuplew->len;
|
|
tuplew->value = NULL;
|
|
tuplew->len = -1;
|
|
}
|
|
res->keyset[k].status &= ~CURS_NEEDS_REREAD;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_EXEC_ERROR, "Data Load Error", func);
|
|
rcnt = -1;
|
|
QR_Destructor(qres);
|
|
break;
|
|
}
|
|
QR_Destructor(qres);
|
|
if (rowc < 0)
|
|
break;
|
|
rowc = 0;
|
|
}
|
|
if (!rowc)
|
|
{
|
|
if (PQExpBufferDataBroken(qval))
|
|
{
|
|
initPQExpBuffer(&qval);
|
|
|
|
if (res->reload_count > 0)
|
|
keys_per_fetch = res->reload_count;
|
|
else
|
|
{
|
|
char planname[32];
|
|
int j;
|
|
QResultClass *qres;
|
|
|
|
if (rows_per_fetch >= pre_fetch_count * 2)
|
|
keys_per_fetch = pre_fetch_count;
|
|
else
|
|
keys_per_fetch = rows_per_fetch;
|
|
if (!keys_per_fetch)
|
|
keys_per_fetch = 2;
|
|
SPRINTF_FIXED(planname, "_KEYSET_%p", res);
|
|
printfPQExpBuffer(&qval, "PREPARE \"%s\"", planname);
|
|
for (j = 0; j < keys_per_fetch; j++)
|
|
{
|
|
appendPQExpBufferStr(&qval, j ? ",tid" : "(tid");
|
|
}
|
|
appendPQExpBuffer(&qval, ") as %s where ctid in ", stmt->load_statement);
|
|
for (j = 0; j < keys_per_fetch; j++)
|
|
{
|
|
if (j == 0)
|
|
appendPQExpBufferStr(&qval, "($1");
|
|
else
|
|
appendPQExpBuffer(&qval, ",$%d", j + 1);
|
|
}
|
|
appendPQExpBufferStr(&qval, ")");
|
|
if (PQExpBufferDataBroken(qval))
|
|
{
|
|
rcnt = -1;
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory in LoadFromKeyset()", func);
|
|
goto cleanup;
|
|
}
|
|
qres = CC_send_query(conn, qval.data, NULL, READ_ONLY_QUERY, stmt);
|
|
if (QR_command_maybe_successful(qres))
|
|
{
|
|
res->reload_count = keys_per_fetch;
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_EXEC_ERROR, "Prepare for Data Load Error", func);
|
|
rcnt = -1;
|
|
QR_Destructor(qres);
|
|
break;
|
|
}
|
|
QR_Destructor(qres);
|
|
}
|
|
}
|
|
if (res->reload_count > 0)
|
|
{
|
|
printfPQExpBuffer(&qval, "EXECUTE \"_KEYSET_%p\"(", res);
|
|
}
|
|
else
|
|
{
|
|
printfPQExpBuffer(&qval, "%s where ctid in (", stmt->load_statement);
|
|
}
|
|
}
|
|
if (0 != (res->keyset[kres_ridx].status & CURS_NEEDS_REREAD))
|
|
{
|
|
getTid(res, kres_ridx, &blocknum, &offset);
|
|
if (rowc)
|
|
appendPQExpBuffer(&qval, ",'(%u,%u)'", blocknum, offset);
|
|
else
|
|
appendPQExpBuffer(&qval, "'(%u,%u)'", blocknum, offset);
|
|
rowc++;
|
|
rcnt++;
|
|
}
|
|
}
|
|
cleanup:
|
|
#undef return
|
|
if (!PQExpBufferDataBroken(qval))
|
|
termPQExpBuffer(&qval);
|
|
return rcnt;
|
|
}
|
|
|
|
static SQLLEN LoadFromKeyset_inh(StatementClass *stmt, QResultClass * res, int rows_per_fetch, SQLLEN limitrow)
|
|
{
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
SQLLEN i;
|
|
int j, rowc, rcnt = 0;
|
|
OID oid, new_oid;
|
|
UInt4 blocknum;
|
|
SQLLEN kres_ridx;
|
|
UInt2 offset;
|
|
PQExpBufferData qval = {0};
|
|
int keys_per_fetch = 10;
|
|
const char *load_stmt = stmt->load_statement;
|
|
const ssize_t from_pos = stmt->load_from_pos;
|
|
|
|
MYLOG(0, "entering in rows_per_fetch=%d limitrow=" FORMAT_LEN "\n", rows_per_fetch, limitrow);
|
|
new_oid = 0;
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
for (i = SC_get_rowset_start(stmt), kres_ridx = GIdx2KResIdx(i, stmt, res), rowc = 0, oid = 0;; i++, kres_ridx++)
|
|
{
|
|
if (i >= limitrow)
|
|
{
|
|
if (!rowc)
|
|
break;
|
|
rowc = -1; /* end of loop */
|
|
}
|
|
else if (0 == (res->keyset[kres_ridx].status & CURS_NEEDS_REREAD))
|
|
continue;
|
|
else
|
|
new_oid = getOid(res, kres_ridx);
|
|
if (rowc < 0 ||
|
|
rowc >= keys_per_fetch ||
|
|
(oid != 0 &&
|
|
new_oid != oid))
|
|
{
|
|
QResultClass *qres;
|
|
|
|
appendPQExpBufferStr(&qval, ")");
|
|
if (PQExpBufferDataBroken(qval))
|
|
{
|
|
rcnt = -1;
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory in LoadFromKeyset_inh()", __FUNCTION__);
|
|
goto cleanup;
|
|
}
|
|
qres = CC_send_query(conn, qval.data, NULL, CREATE_KEYSET | READ_ONLY_QUERY, stmt);
|
|
if (QR_command_maybe_successful(qres))
|
|
{
|
|
SQLLEN k, l;
|
|
Int2 m;
|
|
TupleField *tuple, *tuplew;
|
|
UInt4 bln;
|
|
UInt2 off;
|
|
OID tbloid;
|
|
|
|
for (j = 0; j < QR_get_num_total_read(qres); j++)
|
|
{
|
|
tbloid = getOid(qres, j);
|
|
getTid(qres, j, &blocknum, &offset);
|
|
for (k = SC_get_rowset_start(stmt); k < limitrow; k++)
|
|
{
|
|
getTid(res, k, &bln, &off);
|
|
if (tbloid == getOid(res, k) &&
|
|
bln == blocknum &&
|
|
off == offset)
|
|
{
|
|
l = GIdx2CacheIdx(k, stmt, res);
|
|
tuple = res->backend_tuples + res->num_fields * l;
|
|
tuplew = qres->backend_tuples + qres->num_fields * j;
|
|
for (m = 0; m < res->num_fields; m++, tuple++, tuplew++)
|
|
{
|
|
if (tuple->len > 0 && tuple->value)
|
|
free(tuple->value);
|
|
tuple->value = tuplew->value;
|
|
tuple->len = tuplew->len;
|
|
tuplew->value = NULL;
|
|
tuplew->len = -1;
|
|
}
|
|
res->keyset[k].status &= ~CURS_NEEDS_REREAD;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_EXEC_ERROR, "Data Load Error", __FUNCTION__);
|
|
rcnt = -1;
|
|
QR_Destructor(qres);
|
|
break;
|
|
}
|
|
QR_Destructor(qres);
|
|
if (rowc < 0)
|
|
break;
|
|
rowc = 0;
|
|
}
|
|
if (!rowc)
|
|
{
|
|
char table_fqn[256];
|
|
|
|
if (PQExpBufferDataBroken(qval))
|
|
{
|
|
if (rows_per_fetch >= pre_fetch_count * 2)
|
|
keys_per_fetch = pre_fetch_count;
|
|
else
|
|
keys_per_fetch = rows_per_fetch;
|
|
if (!keys_per_fetch)
|
|
keys_per_fetch = 2;
|
|
initPQExpBuffer(&qval);
|
|
}
|
|
printfPQExpBuffer(&qval, "%.*sfrom %s where ctid in (", (int) from_pos, load_stmt, ti_quote(stmt, new_oid, table_fqn, sizeof(table_fqn)));
|
|
}
|
|
if (new_oid != oid)
|
|
oid = new_oid;
|
|
getTid(res, kres_ridx, &blocknum, &offset);
|
|
if (rowc)
|
|
appendPQExpBuffer(&qval, ",'(%u,%u)'", blocknum, offset);
|
|
else
|
|
appendPQExpBuffer(&qval, "'(%u,%u)'", blocknum, offset);
|
|
rowc++;
|
|
rcnt++;
|
|
}
|
|
cleanup:
|
|
#undef return
|
|
if (!PQExpBufferDataBroken(qval))
|
|
termPQExpBuffer(&qval);
|
|
return rcnt;
|
|
}
|
|
|
|
static RETCODE SQL_API
|
|
SC_pos_reload_needed(StatementClass *stmt, SQLULEN req_size, UDWORD flag)
|
|
{
|
|
CSTR func = "SC_pos_reload_needed";
|
|
Int4 req_rows_size;
|
|
SQLLEN i, limitrow;
|
|
UInt2 qcount;
|
|
QResultClass *res;
|
|
RETCODE ret = SQL_ERROR;
|
|
SQLLEN kres_ridx, rowc;
|
|
Int4 rows_per_fetch;
|
|
BOOL create_from_scratch = (0 != flag);
|
|
|
|
MYLOG(0, "entering\n");
|
|
if (!(res = SC_get_Curres(stmt)))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in SC_pos_reload_needed.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
if (SC_update_not_ready(stmt))
|
|
parse_statement(stmt, TRUE); /* not preferable */
|
|
if (!SC_is_updatable(stmt))
|
|
{
|
|
stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
SC_set_error(stmt, STMT_INVALID_OPTION_IDENTIFIER, "the statement is read-only", func);
|
|
return SQL_ERROR;
|
|
}
|
|
rows_per_fetch = 0;
|
|
req_rows_size = QR_get_reqsize(res);
|
|
if (req_size > req_rows_size)
|
|
req_rows_size = (UInt4) req_size;
|
|
if (create_from_scratch)
|
|
{
|
|
rows_per_fetch = ((pre_fetch_count - 1) / req_rows_size + 1) * req_rows_size;
|
|
limitrow = RowIdx2GIdx(rows_per_fetch, stmt);
|
|
}
|
|
else
|
|
{
|
|
limitrow = RowIdx2GIdx(req_rows_size, stmt);
|
|
}
|
|
if (limitrow > res->num_cached_keys)
|
|
limitrow = res->num_cached_keys;
|
|
if (create_from_scratch ||
|
|
!res->dataFilled)
|
|
{
|
|
ClearCachedRows(res->backend_tuples, res->num_fields, res->num_cached_rows);
|
|
res->dataFilled = FALSE;
|
|
}
|
|
if (!res->dataFilled)
|
|
{
|
|
SQLLEN brows = GIdx2RowIdx(limitrow, stmt);
|
|
if (brows > res->count_backend_allocated)
|
|
{
|
|
QR_REALLOC_return_with_error(res->backend_tuples, TupleField, sizeof(TupleField) * res->num_fields * brows, res, "pos_reload_needed failed", SQL_ERROR);
|
|
res->count_backend_allocated = brows;
|
|
}
|
|
if (brows > 0)
|
|
memset(res->backend_tuples, 0, sizeof(TupleField) * res->num_fields * brows);
|
|
QR_set_num_cached_rows(res, brows);
|
|
QR_set_rowstart_in_cache(res, 0);
|
|
if (SQL_RD_ON != stmt->options.retrieve_data)
|
|
return SQL_SUCCESS;
|
|
for (i = SC_get_rowset_start(stmt), kres_ridx = GIdx2KResIdx(i, stmt,res); i < limitrow; i++, kres_ridx++)
|
|
{
|
|
if (0 == (res->keyset[kres_ridx].status & (CURS_SELF_DELETING | CURS_SELF_DELETED | CURS_OTHER_DELETED)))
|
|
res->keyset[kres_ridx].status |= CURS_NEEDS_REREAD;
|
|
}
|
|
}
|
|
if (TI_has_subclass(stmt->ti[0]))
|
|
{
|
|
if (rowc = LoadFromKeyset_inh(stmt, res, rows_per_fetch, limitrow), rowc < 0)
|
|
{
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
else if (rowc = LoadFromKeyset(stmt, res, rows_per_fetch, limitrow), rowc < 0)
|
|
{
|
|
return SQL_ERROR;
|
|
}
|
|
for (i = SC_get_rowset_start(stmt), kres_ridx = GIdx2KResIdx(i, stmt, res); i < limitrow; i++)
|
|
{
|
|
if (0 != (res->keyset[kres_ridx].status & CURS_NEEDS_REREAD))
|
|
{
|
|
ret = SC_pos_reload(stmt, i, &qcount, 0);
|
|
if (SQL_ERROR == ret)
|
|
{
|
|
break;
|
|
}
|
|
if (SQL_ROW_DELETED == (res->keyset[kres_ridx].status & KEYSET_INFO_PUBLIC))
|
|
{
|
|
res->keyset[kres_ridx].status |= CURS_OTHER_DELETED;
|
|
}
|
|
res->keyset[kres_ridx].status &= ~CURS_NEEDS_REREAD;
|
|
}
|
|
}
|
|
res->dataFilled = TRUE;
|
|
return ret;
|
|
}
|
|
|
|
static RETCODE SQL_API
|
|
SC_pos_newload(StatementClass *stmt, const UInt4 *oidint, BOOL tidRef,
|
|
const char *tidval)
|
|
{
|
|
CSTR func = "SC_pos_newload";
|
|
int i;
|
|
QResultClass *res, *qres;
|
|
RETCODE ret = SQL_ERROR;
|
|
|
|
MYLOG(0, "entering ti=%p\n", stmt->ti);
|
|
if (!(res = SC_get_Curres(stmt)))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in SC_pos_newload.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
if (SC_update_not_ready(stmt))
|
|
parse_statement(stmt, TRUE); /* not preferable */
|
|
if (!SC_is_updatable(stmt))
|
|
{
|
|
stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
SC_set_error(stmt, STMT_INVALID_OPTION_IDENTIFIER, "the statement is read-only", func);
|
|
return SQL_ERROR;
|
|
}
|
|
qres = positioned_load(stmt, (tidRef && NULL == tidval) ? USE_INSERTED_TID : 0, oidint, tidRef ? tidval : NULL);
|
|
if (!qres || !QR_command_maybe_successful(qres))
|
|
{
|
|
SC_set_error(stmt, STMT_ERROR_TAKEN_FROM_BACKEND, "positioned_load in pos_newload failed", func);
|
|
}
|
|
else
|
|
{
|
|
SQLLEN count = QR_get_num_cached_tuples(qres);
|
|
|
|
QR_set_position(qres, 0);
|
|
if (count == 1)
|
|
{
|
|
int effective_fields = res->num_fields;
|
|
ssize_t tuple_size;
|
|
SQLLEN num_total_rows, num_cached_rows, kres_ridx;
|
|
BOOL appendKey = FALSE, appendData = FALSE;
|
|
TupleField *tuple_old, *tuple_new;
|
|
|
|
tuple_new = qres->tupleField;
|
|
num_total_rows = QR_get_num_total_tuples(res);
|
|
|
|
AddAdded(stmt, res, num_total_rows, tuple_new);
|
|
num_cached_rows = QR_get_num_cached_tuples(res);
|
|
kres_ridx = GIdx2KResIdx(num_total_rows, stmt, res);
|
|
if (QR_haskeyset(res))
|
|
{ if (!QR_get_cursor(res))
|
|
{
|
|
appendKey = TRUE;
|
|
if (num_total_rows == CacheIdx2GIdx(num_cached_rows, stmt, res))
|
|
appendData = TRUE;
|
|
else
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "total " FORMAT_LEN " <> backend " FORMAT_LEN " - base " FORMAT_LEN " + start " FORMAT_LEN " cursor_type=%d\n",
|
|
num_total_rows, num_cached_rows,
|
|
QR_get_rowstart_in_cache(res), SC_get_rowset_start(stmt), stmt->options.cursor_type);
|
|
}
|
|
}
|
|
else if (kres_ridx >= 0 && kres_ridx < res->cache_size)
|
|
{
|
|
appendKey = TRUE;
|
|
appendData = TRUE;
|
|
}
|
|
}
|
|
if (appendKey)
|
|
{
|
|
if (res->num_cached_keys >= res->count_keyset_allocated)
|
|
{
|
|
if (!res->count_keyset_allocated)
|
|
tuple_size = TUPLE_MALLOC_INC;
|
|
else
|
|
tuple_size = res->count_keyset_allocated * 2;
|
|
QR_REALLOC_return_with_error(res->keyset, KeySet, sizeof(KeySet) * tuple_size, res, "pos_newload failed", SQL_ERROR);
|
|
res->count_keyset_allocated = tuple_size;
|
|
}
|
|
KeySetSet(tuple_new, qres->num_fields, res->num_key_fields, res->keyset + kres_ridx, TRUE);
|
|
res->num_cached_keys++;
|
|
}
|
|
if (appendData)
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "total " FORMAT_LEN " == backend " FORMAT_LEN " - base " FORMAT_LEN " + start " FORMAT_LEN " cursor_type=%d\n",
|
|
num_total_rows, num_cached_rows,
|
|
QR_get_rowstart_in_cache(res), SC_get_rowset_start(stmt), stmt->options.cursor_type);
|
|
if (num_cached_rows >= res->count_backend_allocated)
|
|
{
|
|
if (!res->count_backend_allocated)
|
|
tuple_size = TUPLE_MALLOC_INC;
|
|
else
|
|
tuple_size = res->count_backend_allocated * 2;
|
|
QR_REALLOC_return_with_error(res->backend_tuples, TupleField, res->num_fields * sizeof(TupleField) * tuple_size, res, "SC_pos_newload failed", SQL_ERROR);
|
|
res->count_backend_allocated = tuple_size;
|
|
}
|
|
tuple_old = res->backend_tuples + res->num_fields * num_cached_rows;
|
|
for (i = 0; i < effective_fields; i++)
|
|
{
|
|
tuple_old[i].len = tuple_new[i].len;
|
|
tuple_new[i].len = -1;
|
|
tuple_old[i].value = tuple_new[i].value;
|
|
tuple_new[i].value = NULL;
|
|
}
|
|
res->num_cached_rows++;
|
|
}
|
|
ret = SQL_SUCCESS;
|
|
}
|
|
else if (0 == count)
|
|
ret = SQL_NO_DATA_FOUND;
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the driver cound't identify inserted rows", func);
|
|
ret = SQL_ERROR;
|
|
}
|
|
/* stmt->currTuple = SC_get_rowset_start(stmt) + ridx; */
|
|
}
|
|
QR_Destructor(qres);
|
|
return ret;
|
|
}
|
|
|
|
static RETCODE SQL_API
|
|
irow_update(RETCODE ret, StatementClass *stmt, StatementClass *ustmt, SQLULEN global_ridx, const KeySet *old_keyset)
|
|
{
|
|
CSTR func = "irow_update";
|
|
|
|
if (ret != SQL_ERROR)
|
|
{
|
|
int updcnt;
|
|
QResultClass *tres = SC_get_Curres(ustmt);
|
|
const char *cmdstr = QR_get_command(tres);
|
|
|
|
if (cmdstr &&
|
|
sscanf(cmdstr, "UPDATE %d", &updcnt) == 1)
|
|
{
|
|
if (updcnt == 1)
|
|
{
|
|
KeySet keys;
|
|
|
|
if (NULL != tres->backend_tuples &&
|
|
1 == QR_get_num_cached_tuples(tres))
|
|
{
|
|
KeySetSet(tres->backend_tuples, QR_NumResultCols(tres), QR_NumResultCols(tres), &keys, TRUE);
|
|
if (SQL_SUCCEEDED(ret = SC_pos_reload_with_key(stmt, global_ridx, (UInt2 *) 0, SQL_UPDATE, &keys)))
|
|
AddRollback(stmt, SC_get_Curres(stmt), global_ridx, old_keyset, SQL_UPDATE);
|
|
}
|
|
else
|
|
ret = SQL_ERROR;
|
|
}
|
|
else if (updcnt == 0)
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the content was changed before updation", func);
|
|
ret = SQL_ERROR;
|
|
if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN)
|
|
SC_pos_reload(stmt, global_ridx, (UInt2 *) 0, 0);
|
|
}
|
|
else
|
|
ret = SQL_ERROR;
|
|
}
|
|
else
|
|
ret = SQL_ERROR;
|
|
if (ret == SQL_ERROR && SC_get_errornumber(stmt) == 0)
|
|
{
|
|
SC_set_error(stmt, STMT_ERROR_TAKEN_FROM_BACKEND, "SetPos update return error", func);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* SQL_NEED_DATA callback for SC_pos_update */
|
|
typedef struct
|
|
{
|
|
BOOL updyes;
|
|
QResultClass *res;
|
|
StatementClass *stmt, *qstmt;
|
|
IRDFields *irdflds;
|
|
SQLSETPOSIROW irow;
|
|
SQLULEN global_ridx;
|
|
KeySet old_keyset;
|
|
} pup_cdata;
|
|
static RETCODE
|
|
pos_update_callback(RETCODE retcode, void *para)
|
|
{
|
|
RETCODE ret = retcode;
|
|
pup_cdata *s = (pup_cdata *) para;
|
|
SQLLEN kres_ridx;
|
|
BOOL idx_exist = TRUE;
|
|
|
|
if (s->updyes)
|
|
{
|
|
MYLOG(0, "entering\n");
|
|
ret = irow_update(ret, s->stmt, s->qstmt, s->global_ridx, &s->old_keyset);
|
|
MYLOG(DETAIL_LOG_LEVEL, "irow_update ret=%d,%d\n", ret, SC_get_errornumber(s->qstmt));
|
|
if (ret != SQL_SUCCESS)
|
|
SC_error_copy(s->stmt, s->qstmt, TRUE);
|
|
PGAPI_FreeStmt(s->qstmt, SQL_DROP);
|
|
s->qstmt = NULL;
|
|
}
|
|
s->updyes = FALSE;
|
|
kres_ridx = GIdx2KResIdx(s->global_ridx, s->stmt, s->res);
|
|
if (kres_ridx < 0 || kres_ridx >= s->res->num_cached_keys)
|
|
{
|
|
idx_exist = FALSE;
|
|
}
|
|
if (SQL_SUCCESS == ret && s->res->keyset && idx_exist)
|
|
{
|
|
ConnectionClass *conn = SC_get_conn(s->stmt);
|
|
|
|
if (CC_is_in_trans(conn))
|
|
{
|
|
s->res->keyset[kres_ridx].status |= (SQL_ROW_UPDATED | CURS_SELF_UPDATING);
|
|
}
|
|
else
|
|
s->res->keyset[kres_ridx].status |= (SQL_ROW_UPDATED | CURS_SELF_UPDATED);
|
|
}
|
|
if (s->irdflds->rowStatusArray)
|
|
{
|
|
switch (ret)
|
|
{
|
|
case SQL_SUCCESS:
|
|
s->irdflds->rowStatusArray[s->irow] = SQL_ROW_UPDATED;
|
|
break;
|
|
default:
|
|
s->irdflds->rowStatusArray[s->irow] = ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
RETCODE
|
|
SC_pos_update(StatementClass *stmt,
|
|
SQLSETPOSIROW irow, SQLULEN global_ridx, const KeySet *keyset)
|
|
{
|
|
CSTR func = "SC_pos_update";
|
|
int i,
|
|
num_cols,
|
|
upd_cols;
|
|
pup_cdata s;
|
|
ConnectionClass *conn;
|
|
ARDFields *opts = SC_get_ARDF(stmt);
|
|
BindInfoClass *bindings = opts->bindings;
|
|
TABLE_INFO *ti;
|
|
FIELD_INFO **fi;
|
|
PQExpBufferData updstr = {0};
|
|
RETCODE ret = SQL_ERROR;
|
|
OID oid;
|
|
UInt4 blocknum;
|
|
UInt2 pgoffset;
|
|
SQLLEN offset;
|
|
SQLLEN *used, kres_ridx;
|
|
Int4 bind_size = opts->bind_size;
|
|
BOOL idx_exist = TRUE;
|
|
char table_fqn[256];
|
|
|
|
s.stmt = stmt;
|
|
s.irow = irow;
|
|
s.global_ridx = global_ridx;
|
|
s.irdflds = SC_get_IRDF(s.stmt);
|
|
fi = s.irdflds->fi;
|
|
if (!(s.res = SC_get_Curres(s.stmt)))
|
|
{
|
|
SC_set_error(s.stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in SC_pos_update.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
MYLOG(0, "entering " FORMAT_POSIROW "+" FORMAT_LEN " fi=%p ti=%p\n", s.irow, QR_get_rowstart_in_cache(s.res), fi, s.stmt->ti);
|
|
if (SC_update_not_ready(stmt))
|
|
parse_statement(s.stmt, TRUE); /* not preferable */
|
|
if (!SC_is_updatable(s.stmt))
|
|
{
|
|
s.stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
SC_set_error(s.stmt, STMT_INVALID_OPTION_IDENTIFIER, "the statement is read-only", func);
|
|
return SQL_ERROR;
|
|
}
|
|
kres_ridx = GIdx2KResIdx(s.global_ridx, s.stmt, s.res);
|
|
if (kres_ridx < 0 || kres_ridx >= s.res->num_cached_keys)
|
|
{
|
|
if (NULL == keyset || keyset->offset == 0)
|
|
{
|
|
SC_set_error(s.stmt, STMT_ROW_OUT_OF_RANGE, "the target keys are out of the rowset", func);
|
|
return SQL_ERROR;
|
|
}
|
|
idx_exist = FALSE;
|
|
}
|
|
if (idx_exist)
|
|
{
|
|
if (!(oid = getOid(s.res, kres_ridx)))
|
|
{
|
|
if (!strcmp(SAFE_NAME(stmt->ti[0]->bestitem), OID_NAME))
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the row was already deleted ?", func);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
getTid(s.res, kres_ridx, &blocknum, &pgoffset);
|
|
s.old_keyset = s.res->keyset[kres_ridx];
|
|
}
|
|
else
|
|
{
|
|
oid = keyset->oid;
|
|
blocknum = keyset->blocknum;
|
|
pgoffset = keyset->offset;
|
|
s.old_keyset = *keyset;
|
|
}
|
|
|
|
ti = s.stmt->ti[0];
|
|
|
|
initPQExpBuffer(&updstr);
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
printfPQExpBuffer(&updstr,
|
|
"update %s set", ti_quote(stmt, oid, table_fqn, sizeof(table_fqn)));
|
|
|
|
num_cols = s.irdflds->nfields;
|
|
offset = opts->row_offset_ptr ? *opts->row_offset_ptr : 0;
|
|
for (i = upd_cols = 0; i < num_cols; i++)
|
|
{
|
|
if (used = bindings[i].used, used != NULL)
|
|
{
|
|
used = LENADDR_SHIFT(used, offset);
|
|
if (bind_size > 0)
|
|
used = LENADDR_SHIFT(used, bind_size * s.irow);
|
|
else
|
|
used = LENADDR_SHIFT(used, s.irow * sizeof(SQLLEN));
|
|
MYLOG(0, "%d used=" FORMAT_LEN ",%p\n", i, *used, used);
|
|
if (*used != SQL_IGNORE && fi[i]->updatable)
|
|
{
|
|
if (upd_cols)
|
|
appendPQExpBuffer(&updstr,
|
|
", \"%s\" = ?", GET_NAME(fi[i]->column_name));
|
|
else
|
|
appendPQExpBuffer(&updstr,
|
|
" \"%s\" = ?", GET_NAME(fi[i]->column_name));
|
|
upd_cols++;
|
|
}
|
|
}
|
|
else
|
|
MYLOG(0, "%d null bind\n", i);
|
|
}
|
|
conn = SC_get_conn(s.stmt);
|
|
s.updyes = FALSE;
|
|
if (upd_cols > 0)
|
|
{
|
|
HSTMT hstmt;
|
|
int j;
|
|
ConnInfo *ci = &(conn->connInfo);
|
|
APDFields *apdopts;
|
|
IPDFields *ipdopts;
|
|
OID fieldtype = 0;
|
|
const char *bestitem = GET_NAME(ti->bestitem);
|
|
const char *bestqual = GET_NAME(ti->bestqual);
|
|
int unknown_sizes = ci->drivers.unknown_sizes;
|
|
|
|
appendPQExpBuffer(&updstr,
|
|
" where ctid = '(%u, %u)'",
|
|
blocknum, pgoffset);
|
|
if (bestqual)
|
|
{
|
|
appendPQExpBuffer(&updstr, " and ");
|
|
appendPQExpBuffer(&updstr, bestqual, oid);
|
|
}
|
|
if (PG_VERSION_GE(conn, 8.2))
|
|
{
|
|
appendPQExpBuffer(&updstr, " returning ctid");
|
|
if (bestitem)
|
|
{
|
|
appendPQExpBuffer(&updstr, ", ");
|
|
appendPQExpBuffer(&updstr, "\"%s\"", bestitem);
|
|
}
|
|
}
|
|
MYLOG(0, "updstr=%s\n", updstr.data);
|
|
if (PGAPI_AllocStmt(conn, &hstmt, 0) != SQL_SUCCESS)
|
|
{
|
|
SC_set_error(s.stmt, STMT_NO_MEMORY_ERROR, "internal AllocStmt error", func);
|
|
goto cleanup;
|
|
}
|
|
s.qstmt = (StatementClass *) hstmt;
|
|
apdopts = SC_get_APDF(s.qstmt);
|
|
apdopts->param_bind_type = opts->bind_size;
|
|
apdopts->param_offset_ptr = opts->row_offset_ptr;
|
|
ipdopts = SC_get_IPDF(s.qstmt);
|
|
SC_set_delegate(s.stmt, s.qstmt);
|
|
extend_iparameter_bindings(ipdopts, num_cols);
|
|
for (i = j = 0; i < num_cols; i++)
|
|
{
|
|
if (used = bindings[i].used, used != NULL)
|
|
{
|
|
used = LENADDR_SHIFT(used, offset);
|
|
if (bind_size > 0)
|
|
used = LENADDR_SHIFT(used, bind_size * s.irow);
|
|
else
|
|
used = LENADDR_SHIFT(used, s.irow * sizeof(SQLLEN));
|
|
MYLOG(0, "%d used=" FORMAT_LEN "\n", i, *used);
|
|
if (*used != SQL_IGNORE && fi[i]->updatable)
|
|
{
|
|
fieldtype = getEffectiveOid(conn, fi[i]);
|
|
PIC_set_pgtype(ipdopts->parameters[j], fieldtype);
|
|
PGAPI_BindParameter(hstmt,
|
|
(SQLUSMALLINT) ++j,
|
|
SQL_PARAM_INPUT,
|
|
bindings[i].returntype,
|
|
pgtype_to_concise_type(s.stmt, fieldtype, i, unknown_sizes),
|
|
fi[i]->column_size > 0 ? fi[i]->column_size : pgtype_column_size(s.stmt, fieldtype, i, unknown_sizes),
|
|
(SQLSMALLINT) fi[i]->decimal_digits,
|
|
bindings[i].buffer,
|
|
bindings[i].buflen,
|
|
bindings[i].used);
|
|
}
|
|
}
|
|
}
|
|
s.qstmt->exec_start_row = s.qstmt->exec_end_row = s.irow;
|
|
s.updyes = TRUE;
|
|
if (PQExpBufferDataBroken(updstr))
|
|
{
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory in SC_pos_updatet()", func);
|
|
goto cleanup;
|
|
}
|
|
ret = PGAPI_ExecDirect(hstmt, (SQLCHAR *) updstr.data, SQL_NTS, 0);
|
|
if (ret == SQL_NEED_DATA)
|
|
{
|
|
pup_cdata *cbdata = (pup_cdata *) malloc(sizeof(pup_cdata));
|
|
if (!cbdata)
|
|
{
|
|
SC_set_error(s.stmt, STMT_NO_MEMORY_ERROR, "Could not allocate memory for cbdata", func);
|
|
ret = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
memcpy(cbdata, &s, sizeof(pup_cdata));
|
|
if (0 == enqueueNeedDataCallback(s.stmt, pos_update_callback, cbdata))
|
|
ret = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = SQL_SUCCESS_WITH_INFO;
|
|
SC_set_error(s.stmt, STMT_INVALID_CURSOR_STATE_ERROR, "update list null", func);
|
|
}
|
|
|
|
ret = pos_update_callback(ret, &s);
|
|
|
|
cleanup:
|
|
#undef return
|
|
if (!PQExpBufferDataBroken(updstr))
|
|
termPQExpBuffer(&updstr);
|
|
return ret;
|
|
}
|
|
RETCODE
|
|
SC_pos_delete(StatementClass *stmt,
|
|
SQLSETPOSIROW irow, SQLULEN global_ridx, const KeySet *keyset)
|
|
{
|
|
CSTR func = "SC_pos_update";
|
|
UWORD offset;
|
|
QResultClass *res, *qres;
|
|
ConnectionClass *conn = SC_get_conn(stmt);
|
|
IRDFields *irdflds = SC_get_IRDF(stmt);
|
|
PQExpBufferData dltstr = {0};
|
|
RETCODE ret;
|
|
SQLLEN kres_ridx;
|
|
OID oid;
|
|
UInt4 blocknum, qflag;
|
|
TABLE_INFO *ti;
|
|
const char *bestitem;
|
|
const char *bestqual;
|
|
BOOL idx_exist = TRUE;
|
|
char table_fqn[256];
|
|
|
|
MYLOG(0, "entering ti=%p\n", stmt->ti);
|
|
if (!(res = SC_get_Curres(stmt)))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in SC_pos_delete.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
if (SC_update_not_ready(stmt))
|
|
parse_statement(stmt, TRUE); /* not preferable */
|
|
if (!SC_is_updatable(stmt))
|
|
{
|
|
stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
SC_set_error(stmt, STMT_INVALID_OPTION_IDENTIFIER, "the statement is read-only", func);
|
|
return SQL_ERROR;
|
|
}
|
|
kres_ridx = GIdx2KResIdx(global_ridx, stmt, res);
|
|
if (kres_ridx < 0 || kres_ridx >= res->num_cached_keys)
|
|
{
|
|
if (NULL == keyset || keyset->offset == 0)
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_OUT_OF_RANGE, "the target keys are out of the rowset", func);
|
|
return SQL_ERROR;
|
|
}
|
|
idx_exist = FALSE;
|
|
}
|
|
ti = stmt->ti[0];
|
|
bestitem = GET_NAME(ti->bestitem);
|
|
bestqual = GET_NAME(ti->bestqual);
|
|
if (idx_exist)
|
|
{
|
|
if (!(oid = getOid(res, kres_ridx)))
|
|
{
|
|
if (bestitem && !strcmp(bestitem, OID_NAME))
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the row was already deleted ?", func);
|
|
return SQL_ERROR;
|
|
}
|
|
}
|
|
getTid(res, kres_ridx, &blocknum, &offset);
|
|
keyset = res->keyset + kres_ridx;
|
|
}
|
|
else
|
|
{
|
|
oid = keyset->oid;
|
|
blocknum = keyset->blocknum;
|
|
offset = keyset->offset;
|
|
}
|
|
initPQExpBuffer(&dltstr);
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
printfPQExpBuffer(&dltstr,
|
|
"delete from %s where ctid = '(%u, %u)'",
|
|
ti_quote(stmt, oid, table_fqn, sizeof(table_fqn)), blocknum, offset);
|
|
if (bestqual && !TI_has_subclass(ti))
|
|
{
|
|
appendPQExpBuffer(&dltstr, " and ");
|
|
appendPQExpBuffer(&dltstr, bestqual, oid);
|
|
}
|
|
|
|
if (PQExpBufferDataBroken(dltstr))
|
|
{
|
|
ret = SQL_ERROR;
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory in SC_pos_delete()", func);
|
|
goto cleanup;
|
|
}
|
|
MYLOG(0, "dltstr=%s\n", dltstr.data);
|
|
qflag = 0;
|
|
if (stmt->external && !CC_is_in_trans(conn) &&
|
|
(!CC_does_autocommit(conn)))
|
|
qflag |= GO_INTO_TRANSACTION;
|
|
qres = CC_send_query(conn, dltstr.data, NULL, qflag, stmt);
|
|
ret = SQL_SUCCESS;
|
|
if (QR_command_maybe_successful(qres))
|
|
{
|
|
int dltcnt;
|
|
const char *cmdstr = QR_get_command(qres);
|
|
|
|
if (cmdstr &&
|
|
sscanf(cmdstr, "DELETE %d", &dltcnt) == 1)
|
|
{
|
|
if (dltcnt == 1)
|
|
{
|
|
RETCODE tret = SC_pos_reload_with_key(stmt, global_ridx, (UInt2 *) 0, SQL_DELETE, keyset);
|
|
if (!SQL_SUCCEEDED(tret))
|
|
ret = tret;
|
|
}
|
|
else if (dltcnt == 0)
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the content was changed before deletion", func);
|
|
ret = SQL_ERROR;
|
|
if (idx_exist && stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN)
|
|
SC_pos_reload(stmt, global_ridx, (UInt2 *) 0, 0);
|
|
}
|
|
else
|
|
ret = SQL_ERROR;
|
|
}
|
|
else
|
|
ret = SQL_ERROR;
|
|
}
|
|
else
|
|
{
|
|
ret = SQL_ERROR;
|
|
if (qres)
|
|
{
|
|
STRCPY_FIXED(res->sqlstate, qres->sqlstate);
|
|
res->message = qres->message;
|
|
qres->message = NULL;
|
|
}
|
|
}
|
|
if (ret == SQL_ERROR && SC_get_errornumber(stmt) == 0)
|
|
{
|
|
SC_set_error(stmt, STMT_ERROR_TAKEN_FROM_BACKEND, "SetPos delete return error", func);
|
|
}
|
|
if (qres)
|
|
QR_Destructor(qres);
|
|
if (SQL_SUCCESS == ret && keyset)
|
|
AddDeleted(res, global_ridx, keyset);
|
|
if (SQL_SUCCESS == ret && keyset && idx_exist)
|
|
{
|
|
res->keyset[kres_ridx].status &= (~KEYSET_INFO_PUBLIC);
|
|
if (CC_is_in_trans(conn))
|
|
{
|
|
res->keyset[kres_ridx].status |= (SQL_ROW_DELETED | CURS_SELF_DELETING);
|
|
}
|
|
else
|
|
res->keyset[kres_ridx].status |= (SQL_ROW_DELETED | CURS_SELF_DELETED);
|
|
MYLOG(DETAIL_LOG_LEVEL, ".status[" FORMAT_ULEN "]=%x\n", global_ridx, res->keyset[kres_ridx].status);
|
|
}
|
|
if (irdflds->rowStatusArray)
|
|
{
|
|
switch (ret)
|
|
{
|
|
case SQL_SUCCESS:
|
|
irdflds->rowStatusArray[irow] = SQL_ROW_DELETED;
|
|
break;
|
|
default:
|
|
irdflds->rowStatusArray[irow] = ret;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
#undef return
|
|
if (!PQExpBufferDataBroken(dltstr))
|
|
termPQExpBuffer(&dltstr);
|
|
return ret;
|
|
}
|
|
|
|
static RETCODE SQL_API
|
|
irow_insert(RETCODE ret, StatementClass *stmt, StatementClass *istmt,
|
|
SQLLEN addpos)
|
|
{
|
|
CSTR func = "irow_insert";
|
|
|
|
if (ret != SQL_ERROR)
|
|
{
|
|
int addcnt;
|
|
OID oid, *poid = NULL;
|
|
ARDFields *opts = SC_get_ARDF(stmt);
|
|
QResultClass *ires = SC_get_Curres(istmt), *tres;
|
|
const char *cmdstr;
|
|
BindInfoClass *bookmark;
|
|
|
|
tres = (ires->next ? ires->next : ires);
|
|
cmdstr = QR_get_command(tres);
|
|
if (cmdstr &&
|
|
sscanf(cmdstr, "INSERT %u %d", &oid, &addcnt) == 2 &&
|
|
addcnt == 1)
|
|
{
|
|
RETCODE qret;
|
|
const char * tidval = NULL;
|
|
char tidv[32];
|
|
KeySet keys;
|
|
|
|
if (NULL != tres->backend_tuples &&
|
|
1 == QR_get_num_cached_tuples(tres))
|
|
{
|
|
KeySetSet(tres->backend_tuples, QR_NumResultCols(tres), QR_NumResultCols(tres), &keys, TRUE);
|
|
oid = keys.oid;
|
|
SPRINTF_FIXED(tidv, "(%u,%hu)", keys.blocknum, keys.offset);
|
|
tidval = tidv;
|
|
}
|
|
if (0 != oid)
|
|
poid = &oid;
|
|
qret = SC_pos_newload(stmt, poid, TRUE, tidval);
|
|
if (SQL_ERROR == qret)
|
|
return qret;
|
|
|
|
if (SQL_NO_DATA_FOUND == qret)
|
|
{
|
|
qret = SC_pos_newload(stmt, poid, FALSE, NULL);
|
|
if (SQL_ERROR == qret)
|
|
return qret;
|
|
}
|
|
bookmark = opts->bookmark;
|
|
if (bookmark && bookmark->buffer)
|
|
{
|
|
SC_set_current_col(stmt, -1);
|
|
SC_Create_bookmark(stmt, bookmark, stmt->bind_row, addpos, &keys);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_ERROR_TAKEN_FROM_BACKEND, "SetPos insert return error", func);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* SQL_NEED_DATA callback for SC_pos_add */
|
|
typedef struct
|
|
{
|
|
BOOL updyes;
|
|
QResultClass *res;
|
|
StatementClass *stmt, *qstmt;
|
|
IRDFields *irdflds;
|
|
SQLSETPOSIROW irow;
|
|
} padd_cdata;
|
|
|
|
static RETCODE
|
|
pos_add_callback(RETCODE retcode, void *para)
|
|
{
|
|
RETCODE ret = retcode;
|
|
padd_cdata *s = (padd_cdata *) para;
|
|
SQLLEN addpos;
|
|
|
|
if (s->updyes)
|
|
{
|
|
SQLSETPOSIROW brow_save;
|
|
|
|
MYLOG(0, "entering ret=%d\n", ret);
|
|
brow_save = s->stmt->bind_row;
|
|
s->stmt->bind_row = s->irow;
|
|
if (QR_get_cursor(s->res))
|
|
addpos = -(SQLLEN)(s->res->ad_count + 1);
|
|
else
|
|
addpos = QR_get_num_total_tuples(s->res);
|
|
ret = irow_insert(ret, s->stmt, s->qstmt, addpos);
|
|
s->stmt->bind_row = brow_save;
|
|
}
|
|
s->updyes = FALSE;
|
|
SC_setInsertedTable(s->qstmt, ret);
|
|
if (ret != SQL_SUCCESS)
|
|
SC_error_copy(s->stmt, s->qstmt, TRUE);
|
|
PGAPI_FreeStmt((HSTMT) s->qstmt, SQL_DROP);
|
|
s->qstmt = NULL;
|
|
if (SQL_SUCCESS == ret && s->res->keyset)
|
|
{
|
|
SQLLEN global_ridx = QR_get_num_total_tuples(s->res) - 1;
|
|
ConnectionClass *conn = SC_get_conn(s->stmt);
|
|
SQLLEN kres_ridx;
|
|
UWORD status = SQL_ROW_ADDED;
|
|
|
|
if (CC_is_in_trans(conn))
|
|
status |= CURS_SELF_ADDING;
|
|
else
|
|
status |= CURS_SELF_ADDED;
|
|
kres_ridx = GIdx2KResIdx(global_ridx, s->stmt, s->res);
|
|
if (kres_ridx >= 0 && kres_ridx < s->res->num_cached_keys)
|
|
{
|
|
s->res->keyset[kres_ridx].status = status;
|
|
}
|
|
}
|
|
if (s->irdflds->rowStatusArray)
|
|
{
|
|
switch (ret)
|
|
{
|
|
case SQL_SUCCESS:
|
|
s->irdflds->rowStatusArray[s->irow] = SQL_ROW_ADDED;
|
|
break;
|
|
default:
|
|
s->irdflds->rowStatusArray[s->irow] = ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
RETCODE
|
|
SC_pos_add(StatementClass *stmt,
|
|
SQLSETPOSIROW irow)
|
|
{
|
|
CSTR func = "SC_pos_add";
|
|
int num_cols,
|
|
add_cols,
|
|
i;
|
|
HSTMT hstmt;
|
|
|
|
padd_cdata s;
|
|
ConnectionClass *conn;
|
|
ConnInfo *ci;
|
|
ARDFields *opts = SC_get_ARDF(stmt);
|
|
APDFields *apdopts;
|
|
IPDFields *ipdopts;
|
|
BindInfoClass *bindings = opts->bindings;
|
|
FIELD_INFO **fi = SC_get_IRDF(stmt)->fi;
|
|
PQExpBufferData addstr = {0};
|
|
RETCODE ret;
|
|
SQLULEN offset;
|
|
SQLLEN *used;
|
|
Int4 bind_size = opts->bind_size;
|
|
OID fieldtype;
|
|
int unknown_sizes;
|
|
int func_cs_count = 0;
|
|
char table_fqn[256];
|
|
|
|
MYLOG(0, "entering fi=%p ti=%p\n", fi, stmt->ti);
|
|
s.stmt = stmt;
|
|
s.irow = irow;
|
|
if (!(s.res = SC_get_Curres(s.stmt)))
|
|
{
|
|
SC_set_error(s.stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in SC_pos_add.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
if (SC_update_not_ready(stmt))
|
|
parse_statement(s.stmt, TRUE); /* not preferable */
|
|
if (!SC_is_updatable(s.stmt))
|
|
{
|
|
s.stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
SC_set_error(s.stmt, STMT_INVALID_OPTION_IDENTIFIER, "the statement is read-only", func);
|
|
return SQL_ERROR;
|
|
}
|
|
s.irdflds = SC_get_IRDF(s.stmt);
|
|
num_cols = s.irdflds->nfields;
|
|
conn = SC_get_conn(s.stmt);
|
|
|
|
if (PGAPI_AllocStmt(conn, &hstmt, 0) != SQL_SUCCESS)
|
|
{
|
|
SC_set_error(s.stmt, STMT_NO_MEMORY_ERROR, "internal AllocStmt error", func);
|
|
return SQL_ERROR;
|
|
}
|
|
initPQExpBuffer(&addstr);
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
printfPQExpBuffer(&addstr,
|
|
"insert into %s (",
|
|
ti_quote(s.stmt, 0, table_fqn, sizeof(table_fqn)));
|
|
if (opts->row_offset_ptr)
|
|
offset = *opts->row_offset_ptr;
|
|
else
|
|
offset = 0;
|
|
s.qstmt = (StatementClass *) hstmt;
|
|
apdopts = SC_get_APDF(s.qstmt);
|
|
apdopts->param_bind_type = opts->bind_size;
|
|
apdopts->param_offset_ptr = opts->row_offset_ptr;
|
|
ipdopts = SC_get_IPDF(s.qstmt);
|
|
SC_set_delegate(s.stmt, s.qstmt);
|
|
ci = &(conn->connInfo);
|
|
unknown_sizes = ci->drivers.unknown_sizes;
|
|
extend_iparameter_bindings(ipdopts, num_cols);
|
|
for (i = add_cols = 0; i < num_cols; i++)
|
|
{
|
|
if (used = bindings[i].used, used != NULL)
|
|
{
|
|
used = LENADDR_SHIFT(used, offset);
|
|
if (bind_size > 0)
|
|
used = LENADDR_SHIFT(used, bind_size * s.irow);
|
|
else
|
|
used = LENADDR_SHIFT(used, s.irow * sizeof(SQLLEN));
|
|
MYLOG(0, "%d used=" FORMAT_LEN "\n", i, *used);
|
|
if (*used != SQL_IGNORE && fi[i]->updatable)
|
|
{
|
|
fieldtype = getEffectiveOid(conn, fi[i]);
|
|
if (add_cols)
|
|
appendPQExpBuffer(&addstr,
|
|
", \"%s\"", GET_NAME(fi[i]->column_name));
|
|
else
|
|
appendPQExpBuffer(&addstr,
|
|
"\"%s\"", GET_NAME(fi[i]->column_name));
|
|
PIC_set_pgtype(ipdopts->parameters[add_cols], fieldtype);
|
|
PGAPI_BindParameter(hstmt,
|
|
(SQLUSMALLINT) ++add_cols,
|
|
SQL_PARAM_INPUT,
|
|
bindings[i].returntype,
|
|
pgtype_to_concise_type(s.stmt, fieldtype, i, unknown_sizes),
|
|
fi[i]->column_size > 0 ? fi[i]->column_size : pgtype_column_size(s.stmt, fieldtype, i, unknown_sizes),
|
|
(SQLSMALLINT) fi[i]->decimal_digits,
|
|
bindings[i].buffer,
|
|
bindings[i].buflen,
|
|
bindings[i].used);
|
|
}
|
|
}
|
|
else
|
|
MYLOG(0, "%d null bind\n", i);
|
|
}
|
|
s.updyes = FALSE;
|
|
ENTER_INNER_CONN_CS(conn, func_cs_count);
|
|
if (add_cols > 0)
|
|
{
|
|
appendPQExpBuffer(&addstr, ") values (");
|
|
for (i = 0; i < add_cols; i++)
|
|
{
|
|
if (i)
|
|
appendPQExpBuffer(&addstr, ", ?");
|
|
else
|
|
appendPQExpBuffer(&addstr, "?");
|
|
}
|
|
appendPQExpBuffer(&addstr, ")");
|
|
if (PG_VERSION_GE(conn, 8.2))
|
|
{
|
|
TABLE_INFO *ti = stmt->ti[0];
|
|
const char *bestitem = GET_NAME(ti->bestitem);
|
|
|
|
appendPQExpBuffer(&addstr, " returning ctid");
|
|
if (bestitem)
|
|
{
|
|
appendPQExpBuffer(&addstr, ", ");
|
|
appendPQExpBuffer(&addstr, "\"%s\"", bestitem);
|
|
}
|
|
}
|
|
if (PQExpBufferDataBroken(addstr))
|
|
{
|
|
ret = SQL_ERROR;
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Out of memory in SC_pos_add()", func);
|
|
goto cleanup;
|
|
}
|
|
MYLOG(0, "addstr=%s\n", addstr.data);
|
|
mylog("addstr=%s\n", addstr.data);
|
|
s.qstmt->exec_start_row = s.qstmt->exec_end_row = s.irow;
|
|
s.updyes = TRUE;
|
|
ret = PGAPI_ExecDirect(hstmt, (SQLCHAR *) addstr.data, SQL_NTS, 0);
|
|
if (ret == SQL_NEED_DATA)
|
|
{
|
|
padd_cdata *cbdata = (padd_cdata *) malloc(sizeof(padd_cdata));
|
|
if (!cbdata)
|
|
{
|
|
SC_set_error(s.stmt, STMT_NO_MEMORY_ERROR, "Could not allocate memory for cbdata", func);
|
|
ret = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
memcpy(cbdata, &s, sizeof(padd_cdata));
|
|
if (0 == enqueueNeedDataCallback(s.stmt, pos_add_callback, cbdata))
|
|
ret = SQL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = SQL_SUCCESS_WITH_INFO;
|
|
SC_set_error(s.stmt, STMT_INVALID_CURSOR_STATE_ERROR, "insert list null", func);
|
|
}
|
|
|
|
ret = pos_add_callback(ret, &s);
|
|
|
|
cleanup:
|
|
#undef return
|
|
CLEANUP_FUNC_CONN_CS(func_cs_count, conn);
|
|
if (!PQExpBufferDataBroken(addstr))
|
|
termPQExpBuffer(&addstr);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Stuff for updatable cursors end.
|
|
*/
|
|
|
|
RETCODE
|
|
SC_pos_refresh(StatementClass *stmt, SQLSETPOSIROW irow , SQLULEN global_ridx)
|
|
{
|
|
RETCODE ret;
|
|
IRDFields *irdflds = SC_get_IRDF(stmt);
|
|
/* save the last_fetch_count */
|
|
SQLLEN last_fetch = stmt->last_fetch_count;
|
|
SQLLEN last_fetch2 = stmt->last_fetch_count_include_ommitted;
|
|
SQLSETPOSIROW bind_save = stmt->bind_row;
|
|
BOOL tuple_reload = FALSE;
|
|
|
|
if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN)
|
|
tuple_reload = TRUE;
|
|
else
|
|
{
|
|
QResultClass *res = SC_get_Curres(stmt);
|
|
if (res && res->keyset)
|
|
{
|
|
SQLLEN kres_ridx = GIdx2KResIdx(global_ridx, stmt, res);
|
|
if (kres_ridx >= 0 && kres_ridx < QR_get_num_cached_tuples(res))
|
|
{
|
|
if (0 != (CURS_NEEDS_REREAD & res->keyset[kres_ridx].status))
|
|
tuple_reload = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (tuple_reload)
|
|
{
|
|
if (!SQL_SUCCEEDED(ret = SC_pos_reload(stmt, global_ridx, (UInt2 *) 0, 0)))
|
|
return ret;
|
|
}
|
|
stmt->bind_row = irow;
|
|
ret = SC_fetch(stmt);
|
|
/* restore the last_fetch_count */
|
|
stmt->last_fetch_count = last_fetch;
|
|
stmt->last_fetch_count_include_ommitted = last_fetch2;
|
|
stmt->bind_row = bind_save;
|
|
if (irdflds->rowStatusArray)
|
|
{
|
|
switch (ret)
|
|
{
|
|
case SQL_ERROR:
|
|
irdflds->rowStatusArray[irow] = SQL_ROW_ERROR;
|
|
break;
|
|
case SQL_SUCCESS:
|
|
irdflds->rowStatusArray[irow] = SQL_ROW_SUCCESS;
|
|
break;
|
|
case SQL_SUCCESS_WITH_INFO:
|
|
default:
|
|
irdflds->rowStatusArray[irow] = ret;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
/* SQL_NEED_DATA callback for PGAPI_SetPos */
|
|
typedef struct
|
|
{
|
|
BOOL need_data_callback, auto_commit_needed;
|
|
QResultClass *res;
|
|
StatementClass *stmt;
|
|
ARDFields *opts;
|
|
GetDataInfo *gdata;
|
|
SQLLEN idx, start_row, end_row, ridx;
|
|
UWORD fOption;
|
|
SQLSETPOSIROW irow, nrow, processed;
|
|
} spos_cdata;
|
|
static
|
|
RETCODE spos_callback(RETCODE retcode, void *para)
|
|
{
|
|
CSTR func = "spos_callback";
|
|
RETCODE ret;
|
|
spos_cdata *s = (spos_cdata *) para;
|
|
QResultClass *res;
|
|
ARDFields *opts;
|
|
ConnectionClass *conn;
|
|
SQLULEN global_ridx;
|
|
SQLLEN kres_ridx, pos_ridx = 0;
|
|
|
|
ret = retcode;
|
|
MYLOG(0, "entering %d in\n", s->need_data_callback);
|
|
if (s->need_data_callback)
|
|
{
|
|
s->processed++;
|
|
if (SQL_ERROR != retcode)
|
|
{
|
|
s->nrow++;
|
|
s->idx++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s->ridx = -1;
|
|
s->idx = s->nrow = s->processed = 0;
|
|
}
|
|
res = s->res;
|
|
opts = s->opts;
|
|
if (!res || !opts)
|
|
{
|
|
SC_set_error(s->stmt, STMT_SEQUENCE_ERROR, "Passed res or opts for spos_callback is NULL", func);
|
|
return SQL_ERROR;
|
|
}
|
|
s->need_data_callback = FALSE;
|
|
for (; SQL_ERROR != ret && s->nrow <= s->end_row; s->idx++)
|
|
{
|
|
global_ridx = RowIdx2GIdx(s->idx, s->stmt);
|
|
if (SQL_ADD != s->fOption)
|
|
{
|
|
if ((int) global_ridx >= QR_get_num_total_tuples(res))
|
|
break;
|
|
if (res->keyset)
|
|
{
|
|
kres_ridx = GIdx2KResIdx(global_ridx, s->stmt, res);
|
|
if (kres_ridx >= res->num_cached_keys)
|
|
break;
|
|
if (kres_ridx >= 0) /* the row may be deleted and not in the rowset */
|
|
{
|
|
if (0 == (res->keyset[kres_ridx].status & CURS_IN_ROWSET))
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (s->nrow < s->start_row)
|
|
{
|
|
s->nrow++;
|
|
continue;
|
|
}
|
|
s->ridx = s->nrow;
|
|
pos_ridx = s->idx;
|
|
if (0 != s->irow || !opts->row_operation_ptr || opts->row_operation_ptr[s->nrow] == SQL_ROW_PROCEED)
|
|
{
|
|
switch (s->fOption)
|
|
{
|
|
case SQL_UPDATE:
|
|
ret = SC_pos_update(s->stmt, s->nrow, global_ridx, NULL);
|
|
break;
|
|
case SQL_DELETE:
|
|
ret = SC_pos_delete(s->stmt, s->nrow, global_ridx, NULL);
|
|
break;
|
|
case SQL_ADD:
|
|
ret = SC_pos_add(s->stmt, s->nrow);
|
|
break;
|
|
case SQL_REFRESH:
|
|
ret = SC_pos_refresh(s->stmt, s->nrow, global_ridx);
|
|
break;
|
|
}
|
|
if (SQL_NEED_DATA == ret)
|
|
{
|
|
spos_cdata *cbdata = (spos_cdata *) malloc(sizeof(spos_cdata));
|
|
if (!cbdata)
|
|
{
|
|
SC_set_error(s->stmt, STMT_NO_MEMORY_ERROR, "Could not allocate memory for cbdata", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
memcpy(cbdata, s, sizeof(spos_cdata));
|
|
cbdata->need_data_callback = TRUE;
|
|
if (0 == enqueueNeedDataCallback(s->stmt, spos_callback, cbdata))
|
|
ret = SQL_ERROR;
|
|
return ret;
|
|
}
|
|
s->processed++;
|
|
}
|
|
if (SQL_ERROR != ret)
|
|
s->nrow++;
|
|
}
|
|
conn = SC_get_conn(s->stmt);
|
|
if (s->auto_commit_needed)
|
|
CC_set_autocommit(conn, TRUE);
|
|
if (s->irow > 0)
|
|
{
|
|
if (SQL_ADD != s->fOption && s->ridx >= 0) /* for SQLGetData */
|
|
{
|
|
s->stmt->currTuple = RowIdx2GIdx(pos_ridx, s->stmt);
|
|
QR_set_position(res, pos_ridx);
|
|
}
|
|
}
|
|
else if (SC_get_IRDF(s->stmt)->rowsFetched)
|
|
*(SC_get_IRDF(s->stmt)->rowsFetched) = s->processed;
|
|
res->recent_processed_row_count = s->stmt->diag_row_count = s->processed;
|
|
if (opts) /* logging */
|
|
{
|
|
MYLOG(DETAIL_LOG_LEVEL, "processed=" FORMAT_POSIROW " ret=%d rowset=" FORMAT_LEN, s->processed, ret, opts->size_of_rowset_odbc2);
|
|
MYPRINTF(DETAIL_LOG_LEVEL, "," FORMAT_LEN "\n", opts->size_of_rowset);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
PGAPI_SetPos(HSTMT hstmt,
|
|
SQLSETPOSIROW irow,
|
|
SQLUSMALLINT fOption,
|
|
SQLUSMALLINT fLock)
|
|
{
|
|
CSTR func = "PGAPI_SetPos";
|
|
RETCODE ret;
|
|
ConnectionClass *conn;
|
|
SQLLEN rowsetSize;
|
|
int i;
|
|
UInt2 gdata_allocated;
|
|
GetDataInfo *gdata_info;
|
|
GetDataClass *gdata = NULL;
|
|
spos_cdata s;
|
|
|
|
s.stmt = (StatementClass *) hstmt;
|
|
if (!s.stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
s.irow = irow;
|
|
s.fOption = fOption;
|
|
s.auto_commit_needed = FALSE;
|
|
s.opts = SC_get_ARDF(s.stmt);
|
|
gdata_info = SC_get_GDTI(s.stmt);
|
|
gdata = gdata_info->gdata;
|
|
MYLOG(0, "entering fOption=%d irow=" FORMAT_POSIROW " lock=%hu currt=" FORMAT_LEN "\n", s.fOption, s.irow, fLock, s.stmt->currTuple);
|
|
if (s.stmt->options.scroll_concurrency != SQL_CONCUR_READ_ONLY)
|
|
;
|
|
else if (s.fOption != SQL_POSITION && s.fOption != SQL_REFRESH)
|
|
{
|
|
SC_set_error(s.stmt, STMT_NOT_IMPLEMENTED_ERROR, "Only SQL_POSITION/REFRESH is supported for PGAPI_SetPos", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
if (!(s.res = SC_get_Curres(s.stmt)))
|
|
{
|
|
SC_set_error(s.stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in PGAPI_SetPos.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
rowsetSize = (s.stmt->transition_status == STMT_TRANSITION_EXTENDED_FETCH ? s.opts->size_of_rowset_odbc2 : s.opts->size_of_rowset);
|
|
if (s.irow == 0) /* bulk operation */
|
|
{
|
|
if (SQL_POSITION == s.fOption)
|
|
{
|
|
SC_set_error(s.stmt, STMT_INVALID_CURSOR_POSITION, "Bulk Position operations not allowed.", func);
|
|
return SQL_ERROR;
|
|
}
|
|
s.start_row = 0;
|
|
s.end_row = rowsetSize - 1;
|
|
}
|
|
else
|
|
{
|
|
if (SQL_ADD != s.fOption && s.irow > s.stmt->last_fetch_count)
|
|
{
|
|
SC_set_error(s.stmt, STMT_ROW_OUT_OF_RANGE, "Row value out of range", func);
|
|
return SQL_ERROR;
|
|
}
|
|
s.start_row = s.end_row = s.irow - 1;
|
|
}
|
|
|
|
gdata_allocated = gdata_info->allocated;
|
|
MYLOG(0, "num_cols=%d gdatainfo=%d\n", QR_NumPublicResultCols(s.res), gdata_allocated);
|
|
/* Reset for SQLGetData */
|
|
if (gdata)
|
|
{
|
|
for (i = 0; i < gdata_allocated; i++)
|
|
GETDATA_RESET(gdata[i]);
|
|
}
|
|
conn = SC_get_conn(s.stmt);
|
|
switch (s.fOption)
|
|
{
|
|
case SQL_UPDATE:
|
|
case SQL_DELETE:
|
|
case SQL_ADD:
|
|
if (s.auto_commit_needed = CC_does_autocommit(conn), s.auto_commit_needed)
|
|
CC_set_autocommit(conn, FALSE);
|
|
break;
|
|
case SQL_POSITION:
|
|
break;
|
|
}
|
|
|
|
s.need_data_callback = FALSE;
|
|
#define return DONT_CALL_RETURN_FROM_HERE???
|
|
ret = spos_callback(SQL_SUCCESS, &s);
|
|
#undef return
|
|
if (SQL_SUCCEEDED(ret) && 0 == s.processed)
|
|
{
|
|
SC_set_error(s.stmt, STMT_ROW_OUT_OF_RANGE, "the row was deleted?", func);
|
|
ret = SQL_ERROR;
|
|
}
|
|
MYLOG(0, "leaving %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Sets options that control the behavior of cursors. */
|
|
RETCODE SQL_API
|
|
PGAPI_SetScrollOptions(HSTMT hstmt,
|
|
SQLUSMALLINT fConcurrency,
|
|
SQLLEN crowKeyset,
|
|
SQLUSMALLINT crowRowset)
|
|
{
|
|
CSTR func = "PGAPI_SetScrollOptions";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
|
|
MYLOG(0, "entering fConcurrency=%d crowKeyset=" FORMAT_LEN " crowRowset=%d\n",
|
|
fConcurrency, crowKeyset, crowRowset);
|
|
SC_set_error(stmt, STMT_NOT_IMPLEMENTED_ERROR, "SetScroll option not implemeted", func);
|
|
|
|
return SQL_ERROR;
|
|
}
|
|
|
|
|
|
/* Set the cursor name on a statement handle */
|
|
RETCODE SQL_API
|
|
PGAPI_SetCursorName(HSTMT hstmt,
|
|
const SQLCHAR * szCursor,
|
|
SQLSMALLINT cbCursor)
|
|
{
|
|
CSTR func = "PGAPI_SetCursorName";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
|
|
MYLOG(0, "entering hstmt=%p, szCursor=%p, cbCursorMax=%d\n", hstmt, szCursor, cbCursor);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
|
|
SET_NAME_DIRECTLY(stmt->cursor_name, make_string(szCursor, cbCursor, NULL, 0));
|
|
return SQL_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Return the cursor name for a statement handle */
|
|
RETCODE SQL_API
|
|
PGAPI_GetCursorName(HSTMT hstmt,
|
|
SQLCHAR * szCursor,
|
|
SQLSMALLINT cbCursorMax,
|
|
SQLSMALLINT * pcbCursor)
|
|
{
|
|
CSTR func = "PGAPI_GetCursorName";
|
|
StatementClass *stmt = (StatementClass *) hstmt;
|
|
size_t len = 0;
|
|
RETCODE result;
|
|
|
|
MYLOG(0, "entering hstmt=%p, szCursor=%p, cbCursorMax=%d, pcbCursor=%p\n", hstmt, szCursor, cbCursorMax, pcbCursor);
|
|
|
|
if (!stmt)
|
|
{
|
|
SC_log_error(func, NULL_STRING, NULL);
|
|
return SQL_INVALID_HANDLE;
|
|
}
|
|
result = SQL_SUCCESS;
|
|
len = strlen(SC_cursor_name(stmt));
|
|
|
|
if (szCursor)
|
|
{
|
|
strncpy_null((char *) szCursor, SC_cursor_name(stmt), cbCursorMax);
|
|
|
|
if (len >= cbCursorMax)
|
|
{
|
|
result = SQL_SUCCESS_WITH_INFO;
|
|
SC_set_error(stmt, STMT_TRUNCATED, "The buffer was too small for the GetCursorName.", func);
|
|
}
|
|
}
|
|
|
|
if (pcbCursor)
|
|
*pcbCursor = (SQLSMALLINT) len;
|
|
|
|
/*
|
|
* Because this function causes no db-access, there's
|
|
* no need to call DiscardStatementSvp()
|
|
*/
|
|
|
|
return result;
|
|
}
|
|
|
|
RETCODE
|
|
SC_fetch_by_bookmark(StatementClass *stmt)
|
|
{
|
|
UInt2 offset;
|
|
SQLLEN kres_ridx;
|
|
OID oidint;
|
|
UInt4 blocknum;
|
|
QResultClass *res, *qres;
|
|
RETCODE ret = SQL_ERROR;
|
|
|
|
HSTMT hstmt = NULL;
|
|
StatementClass *fstmt;
|
|
SQLLEN size_of_rowset, cached_rows;
|
|
SQLULEN cRow;
|
|
UInt2 num_fields;
|
|
ARDFields *opts = SC_get_ARDF(stmt);
|
|
SQLHDESC hdesc;
|
|
APDFields *apdf;
|
|
const int tidbuflen = 20;
|
|
char *tidbuf = NULL, *query = NULL;
|
|
int i, lodlen;
|
|
BindInfoClass *bookmark_orig = opts->bookmark;
|
|
TupleField *otuple, *ituple;
|
|
SQLUSMALLINT *rowStatusArray;
|
|
|
|
MYLOG(0, "entering\n");
|
|
|
|
if (!(res = SC_get_Curres(stmt)))
|
|
{
|
|
SC_set_error(stmt, STMT_INVALID_CURSOR_STATE_ERROR, "Null statement result in SC_fetch_by_bookmark.", __FUNCTION__);
|
|
return SQL_ERROR;
|
|
}
|
|
if (SC_update_not_ready(stmt))
|
|
parse_statement(stmt, TRUE); /* not preferable */
|
|
if (!SC_is_updatable(stmt))
|
|
{
|
|
stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY;
|
|
SC_set_error(stmt, STMT_INVALID_OPTION_IDENTIFIER, "the statement is read-only", __FUNCTION__);
|
|
return SQL_ERROR;
|
|
}
|
|
if (ret = PGAPI_AllocStmt(SC_get_conn(stmt), &hstmt, 0), !SQL_SUCCEEDED(ret))
|
|
{
|
|
SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "internal AllocStmt error", __FUNCTION__);
|
|
return ret;
|
|
}
|
|
size_of_rowset = opts->size_of_rowset;
|
|
SC_MALLOC_gexit_with_error(tidbuf, char, size_of_rowset * tidbuflen, stmt, "Couldn't allocate memory for tidbuf bind.", (ret = SQL_ERROR));
|
|
for (i = 0; i < size_of_rowset; i++)
|
|
{
|
|
PG_BM pg_bm;
|
|
SQLLEN bidx;
|
|
|
|
pg_bm = SC_Resolve_bookmark(opts, i);
|
|
bidx = pg_bm.index;
|
|
|
|
MYLOG(0, "i=%d bidx=" FORMAT_LEN " cached=" FORMAT_ULEN "\n", i, bidx, res->num_cached_keys);
|
|
kres_ridx = GIdx2KResIdx(bidx, stmt, res);
|
|
if (kres_ridx < 0 || kres_ridx >= res->num_cached_keys)
|
|
{
|
|
if (pg_bm.keys.offset > 0)
|
|
{
|
|
QR_get_last_bookmark(res, bidx, &pg_bm.keys);
|
|
blocknum = pg_bm.keys.blocknum;
|
|
offset = pg_bm.keys.offset;
|
|
oidint = pg_bm.keys.oid;
|
|
}
|
|
else
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_OUT_OF_RANGE, "the target rows is out of the rowset", __FUNCTION__);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(oidint = getOid(res, kres_ridx)))
|
|
{
|
|
if (!strcmp(SAFE_NAME(stmt->ti[0]->bestitem), OID_NAME))
|
|
{
|
|
SC_set_error(stmt, STMT_ROW_VERSION_CHANGED, "the row was already deleted ?", __FUNCTION__);
|
|
}
|
|
}
|
|
getTid(res, kres_ridx, &blocknum, &offset);
|
|
}
|
|
snprintf(tidbuf + i * tidbuflen, tidbuflen, "(%u,%u)", blocknum, offset);
|
|
MYLOG(0, "!!!! tidbuf=%s\n", tidbuf + i * tidbuflen);
|
|
}
|
|
if (!SQL_SUCCEEDED(PGAPI_BindParameter(hstmt, 1, SQL_PARAM_INPUT,
|
|
SQL_C_CHAR, SQL_CHAR, tidbuflen, 0,
|
|
tidbuf, tidbuflen, NULL)))
|
|
goto cleanup;
|
|
apdf = SC_get_APDF((StatementClass *) hstmt);
|
|
apdf->paramset_size = size_of_rowset;
|
|
if (!SQL_SUCCEEDED(PGAPI_GetStmtAttr(stmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) &hdesc, SQL_IS_POINTER, NULL)))
|
|
goto cleanup;
|
|
if (!SQL_SUCCEEDED(PGAPI_SetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) hdesc, SQL_IS_POINTER)))
|
|
goto cleanup;
|
|
|
|
lodlen = strlen(stmt->load_statement) + 15;
|
|
SC_MALLOC_gexit_with_error(query, char, lodlen, stmt, "Couldn't allocate memory for query buf.", (ret = SQL_ERROR));
|
|
snprintf(query, lodlen, "%s where ctid=?", stmt->load_statement);
|
|
if (!SQL_SUCCEEDED(ret = PGAPI_ExecDirect(hstmt, (SQLCHAR *) query, SQL_NTS, PODBC_RDONLY)))
|
|
goto cleanup;
|
|
/*
|
|
* Combine multiple results into one
|
|
*/
|
|
fstmt = (StatementClass *) hstmt;
|
|
res = SC_get_Result(fstmt);
|
|
num_fields = QR_NumResultCols(res);
|
|
cached_rows = QR_get_num_cached_tuples(res);
|
|
if (size_of_rowset > res->count_backend_allocated)
|
|
{
|
|
SC_REALLOC_gexit_with_error(res->backend_tuples, TupleField, size_of_rowset * sizeof(TupleField) * num_fields, hstmt, "Couldn't realloc memory for backend.", (ret = SQL_ERROR));
|
|
res->count_backend_allocated = size_of_rowset;
|
|
}
|
|
memset(res->backend_tuples + num_fields * cached_rows, 0, (size_of_rowset - cached_rows) * num_fields * sizeof(TupleField));
|
|
QR_set_num_cached_rows(res, size_of_rowset);
|
|
res->num_total_read = size_of_rowset;
|
|
rowStatusArray = (SC_get_IRDF(stmt))->rowStatusArray;
|
|
for (i = 0, qres = res; i < size_of_rowset && NULL != qres; i++, qres = qres->next)
|
|
{
|
|
if (1 == QR_get_num_cached_tuples(qres))
|
|
{
|
|
otuple = res->backend_tuples + i * num_fields;
|
|
ituple = qres->backend_tuples;
|
|
if (otuple != ituple)
|
|
MoveCachedRows(otuple, ituple, num_fields, 1);
|
|
if (NULL != rowStatusArray)
|
|
rowStatusArray[i] = SQL_ROW_SUCCESS;
|
|
}
|
|
else if (NULL != rowStatusArray)
|
|
rowStatusArray[i] = SQL_ROW_DELETED;
|
|
}
|
|
|
|
/* Fetch and fill bind info */
|
|
cRow = 0;
|
|
opts->bookmark = NULL;
|
|
ret = PGAPI_ExtendedFetch(fstmt, SQL_FETCH_NEXT, 0,
|
|
&cRow, NULL, 0, size_of_rowset);
|
|
MYLOG(0, "cRow=" FORMAT_ULEN "\n", cRow);
|
|
|
|
cleanup:
|
|
if (NULL != hstmt)
|
|
{
|
|
PGAPI_SetStmtAttr(hstmt, SQL_ATTR_APP_ROW_DESC, (SQLPOINTER) NULL, SQL_IS_POINTER);
|
|
PGAPI_FreeStmt(hstmt, SQL_DROP);
|
|
}
|
|
opts->bookmark = bookmark_orig;
|
|
if (NULL != tidbuf)
|
|
free(tidbuf);
|
|
if (NULL != query)
|
|
free(query);
|
|
|
|
return ret;
|
|
}
|