From 7c41f55da2e84f7a61345865964c5a2e235f565b Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Fri, 27 Dec 2024 14:08:46 -0500 Subject: [PATCH 1/3] Document functions, change catalogfunctions-test to use test_sepecial table which has a primary key and unique index --- connection.c | 18 +++++++-- execute.c | 30 ++++++++++++++ info.c | 12 ++++++ odbcapi.c | 46 ++++++++++++++++++++++ odbcapi30.c | 16 +++++++- results.c | 67 +++++++++++++++++++++++++++----- statement.c | 4 ++ test/sampletables.sql | 3 ++ test/src/catalogfunctions-test.c | 2 +- 9 files changed, 184 insertions(+), 14 deletions(-) diff --git a/connection.c b/connection.c index 56e89576..616e5421 100644 --- a/connection.c +++ b/connection.c @@ -1695,6 +1695,19 @@ CC_from_PGresult(QResultClass *res, StatementClass *stmt, return success; } +/** + * @param[in] *self + * @param[in] rollback_type + * PER_STATEMENT_ROLLBACK + * + * PER_QUERY_ROLLBACK + * sends ROLLBACK TO _per_query_svp_; RELEASE _per_query_svp_ + * @param[in] ignore_abort + * @return + * 1: success + * 0: failure + * + */ int CC_internal_rollback(ConnectionClass *self, int rollback_type, BOOL ignore_abort) { @@ -2762,7 +2775,7 @@ LIBPQ_connect(ConnectionClass *self) int pversion; const char *opts[PROTOCOL3_OPTS_MAX], *vals[PROTOCOL3_OPTS_MAX]; PQconninfoOption *conninfoOption = NULL, *pqopt; - int i, cnt; + int cnt; char login_timeout_str[20]; char keepalive_idle_str[20]; char keepalive_interval_str[20]; @@ -2848,8 +2861,7 @@ LIBPQ_connect(ConnectionClass *self) { const char *keyword, *val; int j; - - for (i = 0, pqopt = conninfoOption; (keyword = pqopt->keyword) != NULL; i++, pqopt++) + for (pqopt = conninfoOption; (keyword = pqopt->keyword) != NULL; pqopt++) { if ((val = pqopt->val) != NULL) { diff --git a/execute.c b/execute.c index 339e0c93..9deacef4 100644 --- a/execute.c +++ b/execute.c @@ -393,6 +393,15 @@ int HowToPrepareBeforeExec(StatementClass *stmt, BOOL checkOnly) return nCallParse; } +/** + * Generate a name for the SVP (Savepoint) for the given + * connection. + * + * @param conn The connection. + * @param wrk The buffer to store the name in. + * @param wrksize The size of the buffer. + * @return The name of the Savepoint. + */ static const char *GetSvpName(const ConnectionClass *conn, char *wrk, int wrksize) { @@ -651,6 +660,12 @@ MYLOG(0, "count_of_deffered=%d has_notice=%d\n", count_of_deffered, stmt->has_no return retval; } +/** + * @param[in] stmt + * @return + * 1: transaction rollback + * 2: statement rollback + */ int StartRollbackState(StatementClass *stmt) { @@ -665,6 +680,7 @@ MYLOG(DETAIL_LOG_LEVEL, "entering %p->external=%d\n", stmt, stmt->external); if (!ci || ci->rollback_on_error < 0) /* default */ { + /* server version greater than or equal to 8.0 ?*/ if (conn && PG_VERSION_GE(conn, 8.0)) ret = 2; /* statement rollback */ else @@ -689,6 +705,20 @@ MYLOG(DETAIL_LOG_LEVEL, "entering %p->external=%d\n", stmt, stmt->external); return ret; } +/** + * @param[in] *conn + * @param[in] type + * INTERNAL_SAVEPOINT_OPERATION or INTERNAL_ROLLBACK_OPERATION + * if type is INTERNAL_SAVEPOINT_OPERATION and conn->internal_svp is FALSE + * then the command is "SAVEPOINT" instead of "RELEASE SAVEPOINT" + * if type is INTERNAL_ROLLBACK_OPERATION and conn->internal_svp is FALSE + * then the command is "ROLLBACK" instead of "ROLLBACK TO SAVEPOINT" + * @param[out] *cmd + * buffer to hold command to be sent + * @param[in] buflen + * @return less than zero if an error or number of characters in command + * + */ int GenerateSvpCommand(ConnectionClass *conn, int type, char *cmd, int buflen) { diff --git a/info.c b/info.c index a32f9884..61c73063 100644 --- a/info.c +++ b/info.c @@ -2901,6 +2901,18 @@ MYLOG(0, " and the data=%s\n", attdef); } +/// @brief Return a resultset of Special Columns as per the ODBC Spec +/// @param hstmt +/// @param fColType +/// @param szTableQualifier +/// @param cbTableQualifier +/// @param szTableOwner +/// @param cbTableOwner +/// @param szTableName +/// @param cbTableName +/// @param fScope +/// @param fNullable +/// @return RETCODE SQL_API PGAPI_SpecialColumns(HSTMT hstmt, SQLUSMALLINT fColType, diff --git a/odbcapi.c b/odbcapi.c index 9852d1bc..25324f32 100644 --- a/odbcapi.c +++ b/odbcapi.c @@ -643,6 +643,12 @@ SQLRowCount(HSTMT StatementHandle, #ifndef UNICODE_SUPPORTXX +/** + * @param[in] StatementHandle + * @param[in] CursorName + * @param[in] NameLength + * @return SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, SQL_INVALID_HANDLE + */ RETCODE SQL_API SQLSetCursorName(HSTMT StatementHandle, SQLCHAR *CursorName, SQLSMALLINT NameLength) @@ -681,6 +687,46 @@ SQLSetParam(HSTMT StatementHandle, #ifndef UNICODE_SUPPORTXX +/** + * @brief The optimal set of columns that uniquely identifies a row in the specified table (when IdentifierType is SQL_BEST_ROWID) + * The columns that are automatically updated when any value in the row is updated (when IdentifierType is SQL_ROWVER) + + * @param HSTMT StatementHandle, // Handle to the statement + * @param SQLUSMALLINT IdentifierType, // Type of column to return + * @param SQLCHAR *CatalogName, // Catalog name + * @param SQLSMALLINT NameLength1, // Length of catalog name + * @param SQLCHAR *SchemaName, // Schema name + * @param SQLSMALLINT NameLength2, // Length of schema name + * @param SQLCHAR *TableName, // Table name + * @param SQLSMALLINT NameLength3, // Length of table name + * @param SQLUSMALLINT Scope, // Minimum required scope + * @param SQLUSMALLINT Nullable, // Whether columns can be NULL + * @return SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, SQL_INVALID_HANDLE + + +Return Values: +SQL_SUCCESS: The function completed successfully +SQL_SUCCESS_WITH_INFO: The function completed with information messages +SQL_ERROR: The function failed +SQL_INVALID_HANDLE: Invalid handle parameter + +Key Implementation Details: +The function performs connection loss checking before proceeding +Uses critical section protection (ENTER_STMT_CS/LEAVE_STMT_CS) + +Implements case-sensitivity handling: +If the initial query returns empty results, it will attempt to rerun the query with adjusted case (upper/lower) based on the connection settings +Supports transaction management through StartRollbackState and DiscardStatementSvp + +The function is particularly useful when: +You need to uniquely identify rows in a table that lacks a primary key +You need to determine which columns are automatically updated +You're implementing optimistic concurrency control using version columns +Note: This implementation is part of the PostgreSQL ODBC driver (psqlodbc) and includes specific handling for PostgreSQL's case-sensitivity rules and transaction management + + + +*/ RETCODE SQL_API SQLSpecialColumns(HSTMT StatementHandle, SQLUSMALLINT IdentifierType, SQLCHAR *CatalogName, diff --git a/odbcapi30.c b/odbcapi30.c index cdfd29cf..fcc156d1 100644 --- a/odbcapi30.c +++ b/odbcapi30.c @@ -190,7 +190,21 @@ SQLEndTran(SQLSMALLINT HandleType, SQLHANDLE Handle, return ret; } -/* SQLExtendedFetch -> SQLFetchScroll */ +/** + * SQLExtendedFetch -> SQLFetchScroll + * SQLFetchScroll returns a specified rowset from the result set. + * Rowsets can be specified by absolute or relative position or by bookmark. + * SQLFetchScroll can be called only while a result set exists - that is, after a call that creates a result set and before the cursor over that result set is closed. + * If any columns are bound, it returns the data in those columns. + * If the application has specified a pointer to a row status array or a buffer in which to return the number of rows fetched, + * SQLFetchScroll returns this information as well. + * Calls to SQLFetchScroll can be mixed with calls to SQLFetch but cannot be mixed with calls to SQLExtendedFetch. + * + * @param[in] StatementHandle + * @param[in] FetchOrientation + * @param[in] FetchOffset + * @return RETCODE SQL_SUCCESS if successful + */ RETCODE SQL_API SQLFetchScroll(HSTMT StatementHandle, SQLSMALLINT FetchOrientation, SQLLEN FetchOffset) diff --git a/results.c b/results.c index 934fce94..4c0dbf18 100644 --- a/results.c +++ b/results.c @@ -136,9 +136,12 @@ MYLOG(DETAIL_LOG_LEVEL, "nfields=%d\n", irdflds->nfields); return exec_ok; } -/* +/** * This returns the number of columns associated with the database * attached to "hstmt". + * @param[in] hstmt handle to the statement + * @param[out] pccol returns the number of columns in the result set + * @return SQL_SUCCESS, SQL_ERROR, SQL_INVALID_HANDLE */ RETCODE SQL_API PGAPI_NumResultCols(HSTMT hstmt, @@ -202,9 +205,19 @@ PGAPI_NumResultCols(HSTMT hstmt, #define USE_FI(fi, unknown) (fi && UNKNOWNS_AS_LONGEST != unknown) -/* +/** * Return information about the database column the user wants * information about. + * @param[in] hstmt handle to the statement + * @param[in] icol column number to retrieve information about + * @param[out] szColName name of the column + * @param[in] cbColNameMax maximum length of the column name + * @param[out] pcbColName returns the actual length of the column name + * @param[out] pfSqlType type of the column + * @param[out] pcbColDef returns the size of the column + * @param[out] pibScale returns the scale of the column + * @param[out] pfNullable returns whether the column is nullable + * @return SQL_SUCCESS, SQL_ERROR, SQL_INVALID_HANDLE */ RETCODE SQL_API PGAPI_DescribeCol(HSTMT hstmt, @@ -460,7 +473,17 @@ MYLOG(DETAIL_LOG_LEVEL, "answering bookmark info\n"); -/* Returns result column descriptor information for a result set. */ +/** + * Returns result column descriptor information for a result set. + * @param[in] hstmt handle to the statement + * @param[in] icol column number to retrieve information about + * @param[in] fDescType type of descriptor information to return + * @param[out] rgbDesc buffer to receive the descriptor information + * @param[in] cbDescMax maximum length of the descriptor information + * @param[out] pcbDesc returns the actual length of the descriptor information + * @param[out] pfDesc returns the descriptor type + * @return SQL_SUCCESS, SQL_ERROR, SQL_INVALID_HANDLE + */ RETCODE SQL_API PGAPI_ColAttributes(HSTMT hstmt, SQLUSMALLINT icol, @@ -906,7 +929,17 @@ MYLOG(DETAIL_LOG_LEVEL, "COLUMN_SCALE=" FORMAT_LEN "\n", value); } -/* Returns result data for a single column in the current row. */ +/** + * Returns the value for a single column in the current row. + * + * @param[in] hstmt Statement handle + * @param[in] icol Column number to retrieve + * @param[in] fCType Column data type + * @param[out] rgbValue Column data + * @param[in] cbValueMax Maximum length of data to return + * @param[out] pcbValue Actual length of data returned + * @return SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE +*/ RETCODE SQL_API PGAPI_GetData(HSTMT hstmt, SQLUSMALLINT icol, @@ -1145,10 +1178,11 @@ MYLOG(DETAIL_LOG_LEVEL, "leaving %d\n", result); } -/* - * Returns data for bound columns in the current row ("hstmt->iCursor"), - * advances the cursor. - */ +/** + * Returns data for bound columns in the current row ("hstmt->iCursor"), + * advances the cursor. +* @param[in] hstmt Statement handle +*/ RETCODE SQL_API PGAPI_Fetch(HSTMT hstmt) { @@ -1347,6 +1381,11 @@ MYPRINTF(DETAIL_LOG_LEVEL, " nearest not found\n"); return -(SQLLEN)count; } +/** + * @param[in] self + * @param[out] res + * @return SQL_NO_DATA_FOUND if the cursor is not valid anymore + */ static void move_cursor_position_if_needed(StatementClass *self, QResultClass *res) { @@ -1407,7 +1446,17 @@ MYLOG(DETAIL_LOG_LEVEL, "RETURN_EOF\n"); \ return SQL_NO_DATA_FOUND; \ } -/* This fetches a block of data (rowset). */ +/** + * This fetches a block of data (rowset). + * @param[in] hstmt Statement handle + * @param[in] fFetchType SQL_FETCH_FIRST, SQL_FETCH_NEXT, SQL_FETCH_PRIOR + * @param[in] irow Row number to fetch + * @param[out] pcrow Number of rows fetched + * @param[out] rgfRowStatus Row status + * @param[in] bookmark_offset Bookmark offset + * @param[out] rowsetSize Rowset size + * + */ RETCODE SQL_API PGAPI_ExtendedFetch(HSTMT hstmt, SQLUSMALLINT fFetchType, diff --git a/statement.c b/statement.c index 831b8dde..f2ad6066 100644 --- a/statement.c +++ b/statement.c @@ -800,6 +800,10 @@ SC_initialize_stmts(StatementClass *self, BOOL initializeOriginal) return 0; } +/// @brief Is the statement currently executing a transaction or cursor +/// @param[in] self +/// @param func name of calling function +/// @return TRUE if statement is executing, otherwise FALSE BOOL SC_opencheck(StatementClass *self, const char *func) { QResultClass *res; diff --git a/test/sampletables.sql b/test/sampletables.sql index d65da90d..9eddeffc 100644 --- a/test/sampletables.sql +++ b/test/sampletables.sql @@ -32,6 +32,9 @@ INSERT INTO booltab VALUES (3, 'true', true); INSERT INTO booltab VALUES (4, 'false', false); INSERT INTO booltab VALUES (5, 'not', false); +CREATE TABLE test_special( id integer primary key, ival integer); +CREATE UNIQUE index test_special_ui on public.test_special(ival); + -- View CREATE VIEW testview AS SELECT * FROM testtab1; diff --git a/test/src/catalogfunctions-test.c b/test/src/catalogfunctions-test.c index 8bbae353..b5b0b027 100644 --- a/test/src/catalogfunctions-test.c +++ b/test/src/catalogfunctions-test.c @@ -105,7 +105,7 @@ main(int argc, char **argv) rc = SQLSpecialColumns(hstmt, SQL_ROWVER, NULL, 0, (SQLCHAR *) "public", SQL_NTS, - (SQLCHAR *) "testtab1", SQL_NTS, + (SQLCHAR *) "test_special", SQL_NTS, SQL_SCOPE_SESSION, SQL_NO_NULLS); CHECK_STMT_RESULT(rc, "SQLSpecialColumns failed", hstmt); From bd0206c3f40fd17f74881dc088110fdf3a6034f3 Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Fri, 27 Dec 2024 14:12:02 -0500 Subject: [PATCH 2/3] change expected so tests pass --- test/expected/catalogfunctions.out | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/expected/catalogfunctions.out b/test/expected/catalogfunctions.out index 3e929ba2..cb54a097 100644 --- a/test/expected/catalogfunctions.out +++ b/test/expected/catalogfunctions.out @@ -34,6 +34,7 @@ contrib_regression public booltab TABLE contrib_regression public byteatab TABLE contrib_regression public intervaltable TABLE contrib_regression public lo_test_tab TABLE +contrib_regression public test_special TABLE contrib_regression public testforeign FOREIGN TABLE contrib_regression public testmatview MATVIEW contrib_regression public testtab1 TABLE @@ -77,6 +78,10 @@ contrib_regression public intervaltable iv 12 interval contrib_regression public intervaltable d 12 varchar contrib_regression public lo_test_tab id 4 int4 contrib_regression public lo_test_tab large_data -4 lo +contrib_regression public test_special id 4 int4 +contrib_regression public test_special ival 4 int4 +contrib_regression public test_special_pkey id 4 int4 +contrib_regression public test_special_ui ival 4 int4 contrib_regression public testforeign c1 4 int4 contrib_regression public testmatview id 4 int4 contrib_regression public testmatview t 12 varchar From 15fe6ea16a413ec84179741ff295f65c768c3035 Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Tue, 31 Dec 2024 07:06:55 -0500 Subject: [PATCH 3/3] document functions and fix SQLSpecialColumns to return unique and primary index columns --- bind.c | 15 +++- connection.c | 4 +- execute.c | 48 ++++++++++--- info.c | 110 ++++++++++++++++++++++------- odbcapi.c | 13 ++-- pgtypes.c | 2 +- statement.c | 10 +-- test/expected/catalogfunctions.out | 15 ++++ test/src/catalogfunctions-test.c | 20 +++++- 9 files changed, 185 insertions(+), 52 deletions(-) diff --git a/bind.c b/bind.c index be1018a8..0d04c2c5 100644 --- a/bind.c +++ b/bind.c @@ -142,7 +142,20 @@ PGAPI_BindParameter(HSTMT hstmt, } -/* Associate a user-supplied buffer with a database column. */ +/** + * @brief a user-supplied buffer with a database column. + * @param hstmt Statement handle + * @param icol Column number (zero-based) + * @param fCType Column data type + * @param rgbValue Column data + * @param cbValueMax Maximum column data size + * @param pcbValue Actual column data size + * @return RETCODE + * SQL_SUCCESS + * SQL_SUCCESS_WITH_INFO + * SQL_ERROR + * SQL_INVALID_HANDLE +*/ RETCODE SQL_API PGAPI_BindCol(HSTMT hstmt, SQLUSMALLINT icol, diff --git a/connection.c b/connection.c index 616e5421..6d14e216 100644 --- a/connection.c +++ b/connection.c @@ -2027,9 +2027,9 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD } /* * There are 2 risks to RELEASE an internal savepoint. - * One is to RELEASE the savepoint invalitated + * One is to RELEASE the savepoint invalidated * due to manually issued ROLLBACK or RELEASE. - * Another is to invalitate manual SAVEPOINTs unexpectedly + * Another is to invalidate manual SAVEPOINTs unexpectedly * by RELEASing the internal savepoint. */ else if (strnicmp(cmdbuffer, svpcmd, strlen(svpcmd)) == 0) diff --git a/execute.c b/execute.c index 9deacef4..0ae459aa 100644 --- a/execute.c +++ b/execute.c @@ -33,7 +33,14 @@ #include "lobj.h" #include "pgapifunc.h" -/* Perform a Prepare on the SQL statement */ +/** + * @brief Perform a Prepare on the SQL statement + * + * @param hstmt Handle to the statement + * @param szSqlStr SQL statement + * @param cbSqlStr Length of the SQL statement + * + */ RETCODE SQL_API PGAPI_Prepare(HSTMT hstmt, const SQLCHAR * szSqlStr, @@ -133,7 +140,14 @@ MYLOG(DETAIL_LOG_LEVEL, "leaving %d\n", retval); } -/* Performs the equivalent of SQLPrepare, followed by SQLExecute. */ +/** + * Performs the equivalent of SQLPrepare, followed by SQLExecute. + * @param hstmt Handle to the statement + * @param szSqlStr SQL statement + * @param cbSqlStr Length of the SQL statement + * @param flag Flags to control the execution of the statement + * +*/ RETCODE SQL_API PGAPI_ExecDirect(HSTMT hstmt, const SQLCHAR * szSqlStr, @@ -447,7 +461,7 @@ RETCODE Exec_with_parameters_resolved(StatementClass *stmt, EXEC_TYPE exec_type, BOOL prepare_before_exec = FALSE; char *stmt_with_params; SQLLEN status_row = stmt->exec_current_row; - int count_of_deffered; + int count_of_deferred; *exec_end = FALSE; conn = SC_get_conn(stmt); @@ -521,7 +535,7 @@ MYLOG(0, "about to begin SC_execute exec_type=%d\n", exec_type); } } } - count_of_deffered = stmt->count_of_deffered; + count_of_deferred = stmt->count_of_deffered; if (DIRECT_EXEC == exec_type) { retval = SC_execute(stmt); @@ -570,8 +584,8 @@ MYLOG(0, "about to begin SC_execute exec_type=%d\n", exec_type); } if (retval == SQL_ERROR) { -MYLOG(0, "count_of_deffered=%d\n", count_of_deffered); - param_status_batch_update(ipdopts, SQL_PARAM_ERROR, stmt->exec_current_row, count_of_deffered); +MYLOG(0, "count_of_deferred=%d\n", count_of_deferred); + param_status_batch_update(ipdopts, SQL_PARAM_ERROR, stmt->exec_current_row, count_of_deferred); stmt->exec_current_row = -1; *exec_end = TRUE; RETURN(retval) @@ -602,11 +616,11 @@ MYLOG(0, "count_of_deffered=%d\n", count_of_deffered); ipdopts->param_status_ptr[status_row] = SQL_PARAM_SUCCESS; break; case SQL_SUCCESS_WITH_INFO: -MYLOG(0, "count_of_deffered=%d has_notice=%d\n", count_of_deffered, stmt->has_notice); - param_status_batch_update(ipdopts, (count_of_deffered > 0 && !stmt->has_notice) ? SQL_PARAM_SUCCESS : SQL_PARAM_SUCCESS_WITH_INFO, status_row, count_of_deffered); +MYLOG(0, "count_of_deffered=%d has_notice=%d\n", count_of_deferred, stmt->has_notice); + param_status_batch_update(ipdopts, (count_of_deferred > 0 && !stmt->has_notice) ? SQL_PARAM_SUCCESS : SQL_PARAM_SUCCESS_WITH_INFO, status_row, count_of_deferred); break; default: - param_status_batch_update(ipdopts, SQL_PARAM_ERROR, status_row, count_of_deffered); + param_status_batch_update(ipdopts, SQL_PARAM_ERROR, status_row, count_of_deferred); break; } } @@ -820,6 +834,14 @@ MYLOG(DETAIL_LOG_LEVEL, "leaving %p->accessed=%d\n", conn, CC_accessed_db(conn)) return ret; } + +/** + * @brief Discards the statement savepoint + * @param stmt + * @param ret + * @param errorOnly + * @return ret +*/ RETCODE DiscardStatementSvp(StatementClass *stmt, RETCODE ret, BOOL errorOnly) { @@ -950,7 +972,13 @@ SC_setInsertedTable(StatementClass *stmt, RETCODE retval) NULL_THE_NAME(conn->schemaIns); } -/* Execute a prepared SQL statement */ +/** + * @brief Execute a prepared SQL statement + * @param hstmt + * @param flag + * @return SQL_SUCCESS if successful + * +*/ RETCODE SQL_API PGAPI_Execute(HSTMT hstmt, UWORD flag) { diff --git a/info.c b/info.c index 61c73063..cbb67173 100644 --- a/info.c +++ b/info.c @@ -2901,18 +2901,20 @@ MYLOG(0, " and the data=%s\n", attdef); } -/// @brief Return a resultset of Special Columns as per the ODBC Spec -/// @param hstmt -/// @param fColType -/// @param szTableQualifier -/// @param cbTableQualifier -/// @param szTableOwner -/// @param cbTableOwner -/// @param szTableName -/// @param cbTableName -/// @param fScope -/// @param fNullable -/// @return +/** @brief Retrieve the optimal set of columns that uniquely identifies a row in the specified table (when IdentifierType is SQL_BEST_ROWID) + * The columns that are automatically updated when any value in the row is updated (when IdentifierType is SQL_ROWVER) + * @param hstmt + * @param fColType + * @param szTableQualifier + * @param cbTableQualifier + * @param szTableOwner + * @param cbTableOwner + * @param szTableName + * @param cbTableName + * @param fScope + * @param fNullable + * @return +*/ RETCODE SQL_API PGAPI_SpecialColumns(HSTMT hstmt, SQLUSMALLINT fColType, @@ -3115,27 +3117,85 @@ MYLOG(DETAIL_LOG_LEVEL, "Add ctid\n"); } else { - /* use the oid value for the rowid */ + if (fColType == SQL_BEST_ROWID) { Int2 the_type = PG_TYPE_OID; int atttypmod = -1; - if (relhasoids[0] != '1') + if (relhasoids[0] == '1') { - ret = SQL_SUCCESS; - goto cleanup; + tuple = QR_AddNew(res); + + set_tuplefield_int2(&tuple[SPECOLS_SCOPE], SQL_SCOPE_SESSION); + set_tuplefield_string(&tuple[SPECOLS_COLUMN_NAME], OID_NAME); + set_tuplefield_int2(&tuple[SPECOLS_DATA_TYPE], PGTYPE_ATTR_TO_CONCISE_TYPE(conn, the_type, atttypmod)); + set_tuplefield_string(&tuple[SPECOLS_TYPE_NAME], pgtype_attr_to_name(conn, the_type, atttypmod, TRUE)); + set_tuplefield_int4(&tuple[SPECOLS_COLUMN_SIZE], PGTYPE_ATTR_COLUMN_SIZE(conn, the_type, atttypmod)); + set_tuplefield_int4(&tuple[SPECOLS_BUFFER_LENGTH], PGTYPE_ATTR_BUFFER_LENGTH(conn, the_type, atttypmod)); + set_tuplefield_int2(&tuple[SPECOLS_DECIMAL_DIGITS], PGTYPE_ATTR_DECIMAL_DIGITS(conn, the_type, atttypmod)); + set_tuplefield_int2(&tuple[SPECOLS_PSEUDO_COLUMN], SQL_PC_PSEUDO); + } else { + /* + +SELECT NULL as \"SCOPE\", + n.nspname AS \"SCHEMA_NAME\"", + c.relname AS \"TABLE_NAME\"", + a.attname AS \"COLUMN_NAME\"", + t.typname AS \"DATA_TYPE\", + t.typename AS \"TYPE_NAME\"", + i.indisunique,i.indisprimary +FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + INNER JOIN pg_attribute a ON a.attrelid = c.oid + INNER JOIN pg_type t on a.atttypid = t.oid + LEFT JOIN pg_index i + ON i.indrelid = c.oid + AND a.attnum = ANY (i.indkey[0:(i.indnkeyatts - 1)]) +WHERE a.attnum > 0 and c.relname like 'testuktab'; + */ + initPQExpBuffer(&columns_query); + printfPQExpBuffer(&columns_query, "select NULL as \"SCOPE\"," + "n.nspname as \"SCHEMA_NAME\"," + "c.relname as \"TABLE_NAME\"," + "a.attname AS \"COLUMN_NAME\"," + "t.typname AS \"DATA_TYPE\"," + "t.typname AS \"TYPE_NAME\"," + "t.typlen AS \"COLUMN_SIZE\"," + "a.attlen AS \"BUFFER_LENGTH\"," + "case " + "when t.typname = 'numeric' then" + " case when a.atttypmod > -1 then 6" + " else a.atttypmod::int4" + " end" + " else 0" + " end AS \"DECIMAL_DIGITS\"," + "1 AS \"PSEUDO_COLUMN\" " + "FROM pg_class c " + "INNER JOIN pg_namespace n ON n.oid = c.relnamespace " + "INNER JOIN pg_attribute a ON a.attrelid = c.oid " + "INNER JOIN pg_type t on a.atttypid = t.oid " + "LEFT JOIN pg_index i ON i.indrelid = c.oid " + "AND a.attnum = ANY (i.indkey[0:(i.indnkeyatts - 1)]) " + "WHERE i.indisunique and a.attnum > 0 and c.relname ='%s'" , szTableName ); + + + if (szTableQualifier != NULL) + appendPQExpBuffer(&columns_query, " and c.relnamespace = %s" , szSchemaName); + + result = PGAPI_ExecDirect(stmt, (SQLCHAR *) columns_query.data, SQL_NTS, PODBC_RDONLY); + if (!SQL_SUCCEEDED(result)) + { + /* + * "Couldn't execute index query (w/SQLExecDirect) in + * SpecialColumns"; + */ + SC_full_error_copy(stmt, stmt, FALSE); + goto cleanup; + } + res = SC_get_Result(stmt); } - tuple = QR_AddNew(res); - set_tuplefield_int2(&tuple[SPECOLS_SCOPE], SQL_SCOPE_SESSION); - set_tuplefield_string(&tuple[SPECOLS_COLUMN_NAME], OID_NAME); - set_tuplefield_int2(&tuple[SPECOLS_DATA_TYPE], PGTYPE_ATTR_TO_CONCISE_TYPE(conn, the_type, atttypmod)); - set_tuplefield_string(&tuple[SPECOLS_TYPE_NAME], pgtype_attr_to_name(conn, the_type, atttypmod, TRUE)); - set_tuplefield_int4(&tuple[SPECOLS_COLUMN_SIZE], PGTYPE_ATTR_COLUMN_SIZE(conn, the_type, atttypmod)); - set_tuplefield_int4(&tuple[SPECOLS_BUFFER_LENGTH], PGTYPE_ATTR_BUFFER_LENGTH(conn, the_type, atttypmod)); - set_tuplefield_int2(&tuple[SPECOLS_DECIMAL_DIGITS], PGTYPE_ATTR_DECIMAL_DIGITS(conn, the_type, atttypmod)); - set_tuplefield_int2(&tuple[SPECOLS_PSEUDO_COLUMN], SQL_PC_PSEUDO); } else if (fColType == SQL_ROWVER) { diff --git a/odbcapi.c b/odbcapi.c index 25324f32..54e910f9 100644 --- a/odbcapi.c +++ b/odbcapi.c @@ -688,7 +688,7 @@ SQLSetParam(HSTMT StatementHandle, #ifndef UNICODE_SUPPORTXX /** - * @brief The optimal set of columns that uniquely identifies a row in the specified table (when IdentifierType is SQL_BEST_ROWID) + * @brief Retrieve the optimal set of columns that uniquely identifies a row in the specified table (when IdentifierType is SQL_BEST_ROWID) * The columns that are automatically updated when any value in the row is updated (when IdentifierType is SQL_ROWVER) * @param HSTMT StatementHandle, // Handle to the statement @@ -728,12 +728,11 @@ Note: This implementation is part of the PostgreSQL ODBC driver (psqlodbc) and i */ RETCODE SQL_API -SQLSpecialColumns(HSTMT StatementHandle, - SQLUSMALLINT IdentifierType, SQLCHAR *CatalogName, - SQLSMALLINT NameLength1, SQLCHAR *SchemaName, - SQLSMALLINT NameLength2, SQLCHAR *TableName, - SQLSMALLINT NameLength3, SQLUSMALLINT Scope, - SQLUSMALLINT Nullable) +SQLSpecialColumns(HSTMT StatementHandle, SQLUSMALLINT IdentifierType, + SQLCHAR *CatalogName, SQLSMALLINT NameLength1, + SQLCHAR *SchemaName, SQLSMALLINT NameLength2, + SQLCHAR *TableName, SQLSMALLINT NameLength3, + SQLUSMALLINT Scope, SQLUSMALLINT Nullable) { CSTR func = "SQLSpecialColumns"; RETCODE ret; diff --git a/pgtypes.c b/pgtypes.c index 741ecf46..4b508d19 100644 --- a/pgtypes.c +++ b/pgtypes.c @@ -383,7 +383,7 @@ getNumericDecimalDigitsX(const ConnectionClass *conn, OID type, int atttypmod, i return adtsize_or_longest; } -static Int4 /* PostgreSQL restritiction */ +static Int4 /* PostgreSQL restriction */ getNumericColumnSizeX(const ConnectionClass *conn, OID type, int atttypmod, int adtsize_or_longest, int handle_unknown_size_as) { Int4 default_column_size = 28; diff --git a/statement.c b/statement.c index f2ad6066..96de5956 100644 --- a/statement.c +++ b/statement.c @@ -800,10 +800,12 @@ SC_initialize_stmts(StatementClass *self, BOOL initializeOriginal) return 0; } -/// @brief Is the statement currently executing a transaction or cursor -/// @param[in] self -/// @param func name of calling function -/// @return TRUE if statement is executing, otherwise FALSE +/** + * @brief Is the statement currently executing a transaction or cursor + * @param[in] self + * @param func name of calling function + * @return TRUE if statement is executing, otherwise FALSE + */ BOOL SC_opencheck(StatementClass *self, const char *func) { QResultClass *res; diff --git a/test/expected/catalogfunctions.out b/test/expected/catalogfunctions.out index cb54a097..a69686b0 100644 --- a/test/expected/catalogfunctions.out +++ b/test/expected/catalogfunctions.out @@ -119,6 +119,21 @@ DECIMAL_DIGITS: SMALLINT(5) digits: 0, nullable PSEUDO_COLUMN: SMALLINT(5) digits: 0, nullable Result set: NULL xmin 4 xid 10 4 0 2 +Check for SQLSpecialColumns +Result set metadata: +SCOPE: LONGVARCHAR(8190) digits: 0, nullable +SCHEMA_NAME: VARCHAR(63) digits: 0, nullable +TABLE_NAME: VARCHAR(63) digits: 0, nullable +COLUMN_NAME: VARCHAR(63) digits: 0, nullable +DATA_TYPE: VARCHAR(63) digits: 0, nullable +TYPE_NAME: VARCHAR(63) digits: 0, nullable +COLUMN_SIZE: SMALLINT(5) digits: 0, nullable +BUFFER_LENGTH: SMALLINT(5) digits: 0, nullable +DECIMAL_DIGITS: INTEGER(10) digits: 0, nullable +PSEUDO_COLUMN: INTEGER(10) digits: 0, nullable +Result set: +NULL public test_special id int4 int4 4 4 0 1 +NULL public test_special ival int4 int4 4 4 0 1 Check for SQLStatistics Result set metadata: TABLE_CAT: VARCHAR(128) digits: 0, nullable diff --git a/test/src/catalogfunctions-test.c b/test/src/catalogfunctions-test.c index b5b0b027..6ce67296 100644 --- a/test/src/catalogfunctions-test.c +++ b/test/src/catalogfunctions-test.c @@ -99,8 +99,8 @@ main(int argc, char **argv) PRINT_RESULT_SERIES(hstmt, sql_column_privileges_ids, -1); rc = SQLFreeStmt(hstmt, SQL_CLOSE); CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt); - - /* Check for SQLSpecialColumns */ + + /* Check for SQLSpecialColumns ROW VERSION*/ printf("Check for SQLSpecialColumns\n"); rc = SQLSpecialColumns(hstmt, SQL_ROWVER, NULL, 0, @@ -109,6 +109,22 @@ main(int argc, char **argv) SQL_SCOPE_SESSION, SQL_NO_NULLS); CHECK_STMT_RESULT(rc, "SQLSpecialColumns failed", hstmt); + + print_result_meta(hstmt); + print_result(hstmt); + rc = SQLFreeStmt(hstmt, SQL_CLOSE); + CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt); + + /* Check for SQLSpecialColumns BEST_ROWID */ + printf("Check for SQLSpecialColumns\n"); + rc = SQLSpecialColumns(hstmt, SQL_BEST_ROWID, + NULL, 0, + (SQLCHAR *) "public", SQL_NTS, + (SQLCHAR *) "test_special", SQL_NTS, + SQL_SCOPE_SESSION, + SQL_NO_NULLS); + CHECK_STMT_RESULT(rc, "SQLSpecialColumns failed", hstmt); + print_result_meta(hstmt); print_result(hstmt); rc = SQLFreeStmt(hstmt, SQL_CLOSE);