diff options
Diffstat (limited to 'src/interfaces/odbc/windev/info.c')
-rw-r--r-- | src/interfaces/odbc/windev/info.c | 3714 |
1 files changed, 3714 insertions, 0 deletions
diff --git a/src/interfaces/odbc/windev/info.c b/src/interfaces/odbc/windev/info.c new file mode 100644 index 00000000000..c77b3c5e898 --- /dev/null +++ b/src/interfaces/odbc/windev/info.c @@ -0,0 +1,3714 @@ +/*-------- + * Module: info.c + * + * Description: This module contains routines related to + * ODBC informational functions. + * + * Classes: n/a + * + * API functions: SQLGetInfo, SQLGetTypeInfo, SQLGetFunctions, + * SQLTables, SQLColumns, SQLStatistics, SQLSpecialColumns, + * SQLPrimaryKeys, SQLForeignKeys, + * SQLProcedureColumns(NI), SQLProcedures(NI), + * SQLTablePrivileges(NI), SQLColumnPrivileges(NI) + * + * Comments: See "notice.txt" for copyright and license information. + *-------- + */ + +#include "psqlodbc.h" + +#include <string.h> +#include <stdio.h> + +#ifndef WIN32 +#include <ctype.h> +#endif + +#include "tuple.h" +#include "pgtypes.h" + +#include "environ.h" +#include "connection.h" +#include "statement.h" +#include "qresult.h" +#include "bind.h" +#include "misc.h" +#include "pgtypes.h" +#include "pgapifunc.h" + + +/* Trigger related stuff for SQLForeign Keys */ +#define TRIGGER_SHIFT 3 +#define TRIGGER_MASK 0x03 +#define TRIGGER_DELETE 0x01 +#define TRIGGER_UPDATE 0x02 + + +/* extern GLOBAL_VALUES globals; */ + + + +RETCODE SQL_API +PGAPI_GetInfo( + HDBC hdbc, + UWORD fInfoType, + PTR rgbInfoValue, + SWORD cbInfoValueMax, + SWORD FAR * pcbInfoValue) +{ + static char *func = "PGAPI_GetInfo"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + ConnInfo *ci; + char *p = NULL, + tmp[MAX_INFO_STRING]; + int len = 0, + value = 0; + RETCODE result; + + mylog("%s: entering...fInfoType=%d\n", func, fInfoType); + + if (!conn) + { + CC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + ci = &(conn->connInfo); + + switch (fInfoType) + { + case SQL_ACCESSIBLE_PROCEDURES: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_ACCESSIBLE_TABLES: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_ACTIVE_CONNECTIONS: /* ODBC 1.0 */ + len = 2; + value = MAX_CONNECTIONS; + break; + + case SQL_ACTIVE_STATEMENTS: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_ALTER_TABLE: /* ODBC 2.0 */ + len = 4; + value = SQL_AT_ADD_COLUMN; + break; + + case SQL_BOOKMARK_PERSISTENCE: /* ODBC 2.0 */ + /* very simple bookmark support */ + len = 4; + value = ci->drivers.use_declarefetch ? 0 : (SQL_BP_SCROLL); + break; + + case SQL_COLUMN_ALIAS: /* ODBC 2.0 */ + p = "N"; + break; + + case SQL_CONCAT_NULL_BEHAVIOR: /* ODBC 1.0 */ + len = 2; + value = SQL_CB_NON_NULL; + break; + + case SQL_CONVERT_BIGINT: + case SQL_CONVERT_BINARY: + case SQL_CONVERT_BIT: + case SQL_CONVERT_CHAR: + case SQL_CONVERT_DATE: + case SQL_CONVERT_DECIMAL: + case SQL_CONVERT_DOUBLE: + case SQL_CONVERT_FLOAT: + case SQL_CONVERT_INTEGER: + case SQL_CONVERT_LONGVARBINARY: + case SQL_CONVERT_LONGVARCHAR: + case SQL_CONVERT_NUMERIC: + case SQL_CONVERT_REAL: + case SQL_CONVERT_SMALLINT: + case SQL_CONVERT_TIME: + case SQL_CONVERT_TIMESTAMP: + case SQL_CONVERT_TINYINT: + case SQL_CONVERT_VARBINARY: + case SQL_CONVERT_VARCHAR: /* ODBC 1.0 */ + len = 4; + value = fInfoType; + break; + + case SQL_CONVERT_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = 0; + break; + + case SQL_CORRELATION_NAME: /* ODBC 1.0 */ + + /* + * Saying no correlation name makes Query not work right. + * value = SQL_CN_NONE; + */ + len = 2; + value = SQL_CN_ANY; + break; + + case SQL_CURSOR_COMMIT_BEHAVIOR: /* ODBC 1.0 */ + len = 2; + value = SQL_CB_CLOSE; + if (ci->updatable_cursors) + if (!ci->drivers.use_declarefetch) + value = SQL_CB_PRESERVE; + break; + + case SQL_CURSOR_ROLLBACK_BEHAVIOR: /* ODBC 1.0 */ + len = 2; + value = SQL_CB_CLOSE; + if (ci->updatable_cursors) + if (!ci->drivers.use_declarefetch) + value = SQL_CB_PRESERVE; + break; + + case SQL_DATA_SOURCE_NAME: /* ODBC 1.0 */ + p = CC_get_DSN(conn); + break; + + case SQL_DATA_SOURCE_READ_ONLY: /* ODBC 1.0 */ + p = CC_is_onlyread(conn) ? "Y" : "N"; + break; + + case SQL_DATABASE_NAME: /* Support for old ODBC 1.0 Apps */ + + /* + * Returning the database name causes problems in MS Query. It + * generates query like: "SELECT DISTINCT a FROM byronnbad3 + * bad3" + * + * p = CC_get_database(conn); + */ + p = ""; + break; + + case SQL_DBMS_NAME: /* ODBC 1.0 */ + p = DBMS_NAME; + break; + + case SQL_DBMS_VER: /* ODBC 1.0 */ + + /* + * The ODBC spec wants ##.##.#### ...whatever... so prepend + * the driver + */ + /* version number to the dbms version string */ + sprintf(tmp, "%s %s", POSTGRESDRIVERVERSION, conn->pg_version); + p = tmp; + break; + + case SQL_DEFAULT_TXN_ISOLATION: /* ODBC 1.0 */ + len = 4; + value = SQL_TXN_READ_COMMITTED; /* SQL_TXN_SERIALIZABLE; */ + break; + + case SQL_DRIVER_NAME: /* ODBC 1.0 */ + p = DRIVER_FILE_NAME; + break; + + case SQL_DRIVER_ODBC_VER: + p = DRIVER_ODBC_VER; + break; + + case SQL_DRIVER_VER: /* ODBC 1.0 */ + p = POSTGRESDRIVERVERSION; + break; + + case SQL_EXPRESSIONS_IN_ORDERBY: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_FETCH_DIRECTION: /* ODBC 1.0 */ + len = 4; + value = ci->drivers.use_declarefetch ? (SQL_FD_FETCH_NEXT) : (SQL_FD_FETCH_NEXT | + SQL_FD_FETCH_FIRST | + SQL_FD_FETCH_LAST | + SQL_FD_FETCH_PRIOR | + SQL_FD_FETCH_ABSOLUTE | + SQL_FD_FETCH_RELATIVE | + SQL_FD_FETCH_BOOKMARK); + break; + + case SQL_FILE_USAGE: /* ODBC 2.0 */ + len = 2; + value = SQL_FILE_NOT_SUPPORTED; + break; + + case SQL_GETDATA_EXTENSIONS: /* ODBC 2.0 */ + len = 4; + value = (SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER | SQL_GD_BOUND | SQL_GD_BLOCK); + break; + + case SQL_GROUP_BY: /* ODBC 2.0 */ + len = 2; + value = SQL_GB_GROUP_BY_EQUALS_SELECT; + break; + + case SQL_IDENTIFIER_CASE: /* ODBC 1.0 */ + + /* + * are identifiers case-sensitive (yes, but only when quoted. + * If not quoted, they default to lowercase) + */ + len = 2; + value = SQL_IC_LOWER; + break; + + case SQL_IDENTIFIER_QUOTE_CHAR: /* ODBC 1.0 */ + /* the character used to quote "identifiers" */ + p = PG_VERSION_LE(conn, 6.2) ? " " : "\""; + break; + + case SQL_KEYWORDS: /* ODBC 2.0 */ + p = ""; + break; + + case SQL_LIKE_ESCAPE_CLAUSE: /* ODBC 2.0 */ + + /* + * is there a character that escapes '%' and '_' in a LIKE + * clause? not as far as I can tell + */ + p = "N"; + break; + + case SQL_LOCK_TYPES: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_LCK_NO_CHANGE | SQL_LCK_EXCLUSIVE | SQL_LCK_UNLOCK) : SQL_LCK_NO_CHANGE; + break; + + case SQL_MAX_BINARY_LITERAL_LEN: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_MAX_CHAR_LITERAL_LEN: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_MAX_COLUMN_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = MAX_COLUMN_LEN; + break; + + case SQL_MAX_COLUMNS_IN_GROUP_BY: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_INDEX: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_ORDER_BY: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_SELECT: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_COLUMNS_IN_TABLE: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_CURSOR_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = MAX_CURSOR_LEN; + break; + + case SQL_MAX_INDEX_SIZE: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_MAX_OWNER_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_PROCEDURE_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_QUALIFIER_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_ROW_SIZE: /* ODBC 2.0 */ + len = 4; + if (PG_VERSION_GE(conn, 7.1)) + { + /* Large Rowa in 7.1+ */ + value = MAX_ROW_SIZE; + } + else + { + /* Without the Toaster we're limited to the blocksize */ + value = BLCKSZ; + } + break; + + case SQL_MAX_ROW_SIZE_INCLUDES_LONG: /* ODBC 2.0 */ + + /* + * does the preceding value include LONGVARCHAR and + * LONGVARBINARY fields? Well, it does include longvarchar, + * but not longvarbinary. + */ + p = "Y"; + break; + + case SQL_MAX_STATEMENT_LEN: /* ODBC 2.0 */ + /* maybe this should be 0? */ + len = 4; + value = CC_get_max_query_len(conn); + break; + + case SQL_MAX_TABLE_NAME_LEN: /* ODBC 1.0 */ + len = 2; + value = MAX_TABLE_LEN; + break; + + case SQL_MAX_TABLES_IN_SELECT: /* ODBC 2.0 */ + len = 2; + value = 0; + break; + + case SQL_MAX_USER_NAME_LEN: + len = 2; + value = 0; + break; + + case SQL_MULT_RESULT_SETS: /* ODBC 1.0 */ + /* Don't support multiple result sets but say yes anyway? */ + p = "Y"; + break; + + case SQL_MULTIPLE_ACTIVE_TXN: /* ODBC 1.0 */ + p = "Y"; + break; + + case SQL_NEED_LONG_DATA_LEN: /* ODBC 2.0 */ + + /* + * Don't need the length, SQLPutData can handle any size and + * multiple calls + */ + p = "N"; + break; + + case SQL_NON_NULLABLE_COLUMNS: /* ODBC 1.0 */ + len = 2; + value = SQL_NNC_NON_NULL; + break; + + case SQL_NULL_COLLATION: /* ODBC 2.0 */ + /* where are nulls sorted? */ + len = 2; + value = SQL_NC_END; + break; + + case SQL_NUMERIC_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = 0; + break; + + case SQL_ODBC_API_CONFORMANCE: /* ODBC 1.0 */ + len = 2; + value = SQL_OAC_LEVEL1; + break; + + case SQL_ODBC_SAG_CLI_CONFORMANCE: /* ODBC 1.0 */ + len = 2; + value = SQL_OSCC_NOT_COMPLIANT; + break; + + case SQL_ODBC_SQL_CONFORMANCE: /* ODBC 1.0 */ + len = 2; + value = SQL_OSC_CORE; + break; + + case SQL_ODBC_SQL_OPT_IEF: /* ODBC 1.0 */ + p = "N"; + break; + + case SQL_OJ_CAPABILITIES: /* ODBC 2.01 */ + len = 4; + if (PG_VERSION_GE(conn, 7.1)) + { + /* OJs in 7.1+ */ + value = (SQL_OJ_LEFT | + SQL_OJ_RIGHT | + SQL_OJ_FULL | + SQL_OJ_NESTED | + SQL_OJ_NOT_ORDERED | + SQL_OJ_INNER | + SQL_OJ_ALL_COMPARISON_OPS); + } + else + /* OJs not in <7.1 */ + value = 0; + break; + + case SQL_ORDER_BY_COLUMNS_IN_SELECT: /* ODBC 2.0 */ + p = (PG_VERSION_LE(conn, 6.3)) ? "Y" : "N"; + break; + + case SQL_OUTER_JOINS: /* ODBC 1.0 */ + if (PG_VERSION_GE(conn, 7.1)) + /* OJs in 7.1+ */ + p = "Y"; + else + /* OJs not in <7.1 */ + p = "N"; + break; + + case SQL_OWNER_TERM: /* ODBC 1.0 */ + p = "owner"; + break; + + case SQL_OWNER_USAGE: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_POS_OPERATIONS: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_POS_POSITION | SQL_POS_REFRESH | SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD) : (SQL_POS_POSITION | SQL_POS_REFRESH); + if (ci->updatable_cursors) + value |= (SQL_POS_UPDATE | SQL_POS_DELETE | SQL_POS_ADD); + break; + + case SQL_POSITIONED_STATEMENTS: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_PS_POSITIONED_DELETE | + SQL_PS_POSITIONED_UPDATE | + SQL_PS_SELECT_FOR_UPDATE) : 0; + break; + + case SQL_PROCEDURE_TERM: /* ODBC 1.0 */ + p = "procedure"; + break; + + case SQL_PROCEDURES: /* ODBC 1.0 */ + p = "Y"; + break; + + case SQL_QUALIFIER_LOCATION: /* ODBC 2.0 */ + len = 2; + value = SQL_QL_START; + break; + + case SQL_QUALIFIER_NAME_SEPARATOR: /* ODBC 1.0 */ + p = ""; + break; + + case SQL_QUALIFIER_TERM: /* ODBC 1.0 */ + p = ""; + break; + + case SQL_QUALIFIER_USAGE: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_QUOTED_IDENTIFIER_CASE: /* ODBC 2.0 */ + /* are "quoted" identifiers case-sensitive? YES! */ + len = 2; + value = SQL_IC_SENSITIVE; + break; + + case SQL_ROW_UPDATES: /* ODBC 1.0 */ + + /* + * Driver doesn't support keyset-driven or mixed cursors, so + * not much point in saying row updates are supported + */ + p = (ci->drivers.lie || ci->updatable_cursors) ? "Y" : "N"; + break; + + case SQL_SCROLL_CONCURRENCY: /* ODBC 1.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_SCCO_READ_ONLY | + SQL_SCCO_LOCK | + SQL_SCCO_OPT_ROWVER | + SQL_SCCO_OPT_VALUES) : + (SQL_SCCO_READ_ONLY); + if (ci->updatable_cursors) + value |= SQL_SCCO_OPT_ROWVER; + break; + + case SQL_SCROLL_OPTIONS: /* ODBC 1.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_SO_FORWARD_ONLY | + SQL_SO_STATIC | + SQL_SO_KEYSET_DRIVEN | + SQL_SO_DYNAMIC | + SQL_SO_MIXED) + : (ci->drivers.use_declarefetch ? SQL_SO_FORWARD_ONLY : (SQL_SO_FORWARD_ONLY | SQL_SO_STATIC)); + if (ci->updatable_cursors) + value |= 0; /* SQL_SO_KEYSET_DRIVEN in the furure */ + break; + + case SQL_SEARCH_PATTERN_ESCAPE: /* ODBC 1.0 */ + p = ""; + break; + + case SQL_SERVER_NAME: /* ODBC 1.0 */ + p = CC_get_server(conn); + break; + + case SQL_SPECIAL_CHARACTERS: /* ODBC 2.0 */ + p = "_"; + break; + + case SQL_STATIC_SENSITIVITY: /* ODBC 2.0 */ + len = 4; + value = ci->drivers.lie ? (SQL_SS_ADDITIONS | SQL_SS_DELETIONS | SQL_SS_UPDATES) : 0; + if (ci->updatable_cursors) + value |= (SQL_SS_ADDITIONS | SQL_SS_DELETIONS | SQL_SS_UPDATES); + break; + + case SQL_STRING_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = (SQL_FN_STR_CONCAT | + SQL_FN_STR_LCASE | + SQL_FN_STR_LENGTH | + SQL_FN_STR_LOCATE | + SQL_FN_STR_LTRIM | + SQL_FN_STR_RTRIM | + SQL_FN_STR_SUBSTRING | + SQL_FN_STR_UCASE); + break; + + case SQL_SUBQUERIES: /* ODBC 2.0 */ + /* postgres 6.3 supports subqueries */ + len = 4; + value = (SQL_SQ_QUANTIFIED | + SQL_SQ_IN | + SQL_SQ_EXISTS | + SQL_SQ_COMPARISON); + break; + + case SQL_SYSTEM_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = 0; + break; + + case SQL_TABLE_TERM: /* ODBC 1.0 */ + p = "table"; + break; + + case SQL_TIMEDATE_ADD_INTERVALS: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_TIMEDATE_DIFF_INTERVALS: /* ODBC 2.0 */ + len = 4; + value = 0; + break; + + case SQL_TIMEDATE_FUNCTIONS: /* ODBC 1.0 */ + len = 4; + value = (SQL_FN_TD_NOW); + break; + + case SQL_TXN_CAPABLE: /* ODBC 1.0 */ + + /* + * Postgres can deal with create or drop table statements in a + * transaction + */ + len = 2; + value = SQL_TC_ALL; + break; + + case SQL_TXN_ISOLATION_OPTION: /* ODBC 1.0 */ + len = 4; + value = SQL_TXN_READ_COMMITTED; /* SQL_TXN_SERIALIZABLE; */ + break; + + case SQL_UNION: /* ODBC 2.0 */ + /* unions with all supported in postgres 6.3 */ + len = 4; + value = (SQL_U_UNION | SQL_U_UNION_ALL); + break; + + case SQL_USER_NAME: /* ODBC 1.0 */ + p = CC_get_username(conn); + break; + + default: + /* unrecognized key */ + conn->errormsg = "Unrecognized key passed to PGAPI_GetInfo."; + conn->errornumber = CONN_NOT_IMPLEMENTED_ERROR; + CC_log_error(func, "", conn); + return SQL_ERROR; + } + + result = SQL_SUCCESS; + + mylog("%s: p='%s', len=%d, value=%d, cbMax=%d\n", func, p ? p : "<NULL>", len, value, cbInfoValueMax); + + /* + * NOTE, that if rgbInfoValue is NULL, then no warnings or errors + * should result and just pcbInfoValue is returned, which indicates + * what length would be required if a real buffer had been passed in. + */ + if (p) + { + /* char/binary data */ + len = strlen(p); + + if (rgbInfoValue) + { + strncpy_null((char *) rgbInfoValue, p, (size_t) cbInfoValueMax); + + if (len >= cbInfoValueMax) + { + result = SQL_SUCCESS_WITH_INFO; + conn->errornumber = STMT_TRUNCATED; + conn->errormsg = "The buffer was too small for tthe InfoValue."; + } + } + } + else + { + /* numeric data */ + if (rgbInfoValue) + { + if (len == 2) + *((WORD *) rgbInfoValue) = (WORD) value; + else if (len == 4) + *((DWORD *) rgbInfoValue) = (DWORD) value; + } + } + + if (pcbInfoValue) + *pcbInfoValue = len; + + return result; +} + + +RETCODE SQL_API +PGAPI_GetTypeInfo( + HSTMT hstmt, + SWORD fSqlType) +{ + static char *func = "PGAPI_GetTypeInfo"; + StatementClass *stmt = (StatementClass *) hstmt; + TupleNode *row; + int i; + + /* Int4 type; */ + Int4 pgType; + Int2 sqlType; + + mylog("%s: entering...fSqlType = %d\n", func, fSqlType); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->result = QR_Constructor(); + if (!stmt->result) + { + SC_log_error(func, "Error creating result.", stmt); + return SQL_ERROR; + } + + extend_bindings(stmt, 15); + + QR_set_num_fields(stmt->result, 15); + QR_set_field_info(stmt->result, 0, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "DATA_TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 2, "PRECISION", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 3, "LITERAL_PREFIX", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "LITERAL_SUFFIX", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "CREATE_PARAMS", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "NULLABLE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 7, "CASE_SENSITIVE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 8, "SEARCHABLE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 9, "UNSIGNED_ATTRIBUTE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 10, "MONEY", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 11, "AUTO_INCREMENT", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 12, "LOCAL_TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 13, "MINIMUM_SCALE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 14, "MAXIMUM_SCALE", PG_TYPE_INT2, 2); + + for (i = 0, sqlType = sqlTypes[0]; sqlType; sqlType = sqlTypes[++i]) + { + pgType = sqltype_to_pgtype(stmt, sqlType); + + if (fSqlType == SQL_ALL_TYPES || fSqlType == sqlType) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (15 - 1) *sizeof(TupleField)); + + /* These values can't be NULL */ + set_tuplefield_string(&row->tuple[0], pgtype_to_name(stmt, pgType)); + set_tuplefield_int2(&row->tuple[1], (Int2) sqlType); + set_tuplefield_int2(&row->tuple[6], pgtype_nullable(stmt, pgType)); + set_tuplefield_int2(&row->tuple[7], pgtype_case_sensitive(stmt, pgType)); + set_tuplefield_int2(&row->tuple[8], pgtype_searchable(stmt, pgType)); + set_tuplefield_int2(&row->tuple[10], pgtype_money(stmt, pgType)); + + /* + * Localized data-source dependent data type name (always + * NULL) + */ + set_tuplefield_null(&row->tuple[12]); + + /* These values can be NULL */ + set_nullfield_int4(&row->tuple[2], pgtype_precision(stmt, pgType, PG_STATIC, PG_STATIC)); + set_nullfield_string(&row->tuple[3], pgtype_literal_prefix(stmt, pgType)); + set_nullfield_string(&row->tuple[4], pgtype_literal_suffix(stmt, pgType)); + set_nullfield_string(&row->tuple[5], pgtype_create_params(stmt, pgType)); + set_nullfield_int2(&row->tuple[9], pgtype_unsigned(stmt, pgType)); + set_nullfield_int2(&row->tuple[11], pgtype_auto_increment(stmt, pgType)); + set_nullfield_int2(&row->tuple[13], pgtype_scale(stmt, pgType, PG_STATIC)); + set_nullfield_int2(&row->tuple[14], pgtype_scale(stmt, pgType, PG_STATIC)); + + QR_add_tuple(stmt->result, row); + } + } + + stmt->status = STMT_FINISHED; + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_GetFunctions( + HDBC hdbc, + UWORD fFunction, + UWORD FAR * pfExists) +{ + static char *func = "PGAPI_GetFunctions"; + ConnectionClass *conn = (ConnectionClass *) hdbc; + ConnInfo *ci = &(conn->connInfo); + + mylog("%s: entering...%u\n", func, fFunction); + + if (fFunction == SQL_API_ALL_FUNCTIONS) + { +#if (ODBCVER < 0x0300) + if (ci->drivers.lie) + { + int i; + + memset(pfExists, 0, sizeof(UWORD) * 100); + + pfExists[SQL_API_SQLALLOCENV] = TRUE; + pfExists[SQL_API_SQLFREEENV] = TRUE; + for (i = SQL_API_SQLALLOCCONNECT; i <= SQL_NUM_FUNCTIONS; i++) + pfExists[i] = TRUE; + for (i = SQL_EXT_API_START; i <= SQL_EXT_API_LAST; i++) + pfExists[i] = TRUE; + } + else +#endif + { + memset(pfExists, 0, sizeof(UWORD) * 100); + + /* ODBC core functions */ + pfExists[SQL_API_SQLALLOCCONNECT] = TRUE; + pfExists[SQL_API_SQLALLOCENV] = TRUE; + pfExists[SQL_API_SQLALLOCSTMT] = TRUE; + pfExists[SQL_API_SQLBINDCOL] = TRUE; + pfExists[SQL_API_SQLCANCEL] = TRUE; + pfExists[SQL_API_SQLCOLATTRIBUTES] = TRUE; + pfExists[SQL_API_SQLCONNECT] = TRUE; + pfExists[SQL_API_SQLDESCRIBECOL] = TRUE; /* partial */ + pfExists[SQL_API_SQLDISCONNECT] = TRUE; + pfExists[SQL_API_SQLERROR] = TRUE; + pfExists[SQL_API_SQLEXECDIRECT] = TRUE; + pfExists[SQL_API_SQLEXECUTE] = TRUE; + pfExists[SQL_API_SQLFETCH] = TRUE; + pfExists[SQL_API_SQLFREECONNECT] = TRUE; + pfExists[SQL_API_SQLFREEENV] = TRUE; + pfExists[SQL_API_SQLFREESTMT] = TRUE; + pfExists[SQL_API_SQLGETCURSORNAME] = TRUE; + pfExists[SQL_API_SQLNUMRESULTCOLS] = TRUE; + pfExists[SQL_API_SQLPREPARE] = TRUE; /* complete? */ + pfExists[SQL_API_SQLROWCOUNT] = TRUE; + pfExists[SQL_API_SQLSETCURSORNAME] = TRUE; + pfExists[SQL_API_SQLSETPARAM] = FALSE; /* odbc 1.0 */ + pfExists[SQL_API_SQLTRANSACT] = TRUE; + + /* ODBC level 1 functions */ + pfExists[SQL_API_SQLBINDPARAMETER] = TRUE; + pfExists[SQL_API_SQLCOLUMNS] = TRUE; + pfExists[SQL_API_SQLDRIVERCONNECT] = TRUE; + pfExists[SQL_API_SQLGETCONNECTOPTION] = TRUE; /* partial */ + pfExists[SQL_API_SQLGETDATA] = TRUE; + pfExists[SQL_API_SQLGETFUNCTIONS] = TRUE; + pfExists[SQL_API_SQLGETINFO] = TRUE; + pfExists[SQL_API_SQLGETSTMTOPTION] = TRUE; /* partial */ + pfExists[SQL_API_SQLGETTYPEINFO] = TRUE; + pfExists[SQL_API_SQLPARAMDATA] = TRUE; + pfExists[SQL_API_SQLPUTDATA] = TRUE; + pfExists[SQL_API_SQLSETCONNECTOPTION] = TRUE; /* partial */ + pfExists[SQL_API_SQLSETSTMTOPTION] = TRUE; + pfExists[SQL_API_SQLSPECIALCOLUMNS] = TRUE; + pfExists[SQL_API_SQLSTATISTICS] = TRUE; + pfExists[SQL_API_SQLTABLES] = TRUE; + + /* ODBC level 2 functions */ + pfExists[SQL_API_SQLBROWSECONNECT] = FALSE; + pfExists[SQL_API_SQLCOLUMNPRIVILEGES] = FALSE; + pfExists[SQL_API_SQLDATASOURCES] = FALSE; /* only implemented by + * DM */ + pfExists[SQL_API_SQLDESCRIBEPARAM] = FALSE; /* not properly + * implemented */ + pfExists[SQL_API_SQLDRIVERS] = FALSE; /* only implemented by + * DM */ + pfExists[SQL_API_SQLEXTENDEDFETCH] = TRUE; + pfExists[SQL_API_SQLFOREIGNKEYS] = TRUE; + pfExists[SQL_API_SQLMORERESULTS] = TRUE; + pfExists[SQL_API_SQLNATIVESQL] = TRUE; + pfExists[SQL_API_SQLNUMPARAMS] = TRUE; + pfExists[SQL_API_SQLPARAMOPTIONS] = FALSE; + pfExists[SQL_API_SQLPRIMARYKEYS] = TRUE; + pfExists[SQL_API_SQLPROCEDURECOLUMNS] = FALSE; + if (PG_VERSION_LT(conn, 6.5)) + pfExists[SQL_API_SQLPROCEDURES] = FALSE; + else + pfExists[SQL_API_SQLPROCEDURES] = TRUE; + pfExists[SQL_API_SQLSETPOS] = TRUE; + pfExists[SQL_API_SQLSETSCROLLOPTIONS] = TRUE; /* odbc 1.0 */ + pfExists[SQL_API_SQLTABLEPRIVILEGES] = FALSE; + } + } + else + { + if (ci->drivers.lie) + *pfExists = TRUE; + else + { + switch (fFunction) + { + case SQL_API_SQLALLOCCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLALLOCENV: + *pfExists = TRUE; + break; + case SQL_API_SQLALLOCSTMT: + *pfExists = TRUE; + break; + case SQL_API_SQLBINDCOL: + *pfExists = TRUE; + break; + case SQL_API_SQLCANCEL: + *pfExists = TRUE; + break; + case SQL_API_SQLCOLATTRIBUTES: + *pfExists = TRUE; + break; + case SQL_API_SQLCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLDESCRIBECOL: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLDISCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLERROR: + *pfExists = TRUE; + break; + case SQL_API_SQLEXECDIRECT: + *pfExists = TRUE; + break; + case SQL_API_SQLEXECUTE: + *pfExists = TRUE; + break; + case SQL_API_SQLFETCH: + *pfExists = TRUE; + break; + case SQL_API_SQLFREECONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLFREEENV: + *pfExists = TRUE; + break; + case SQL_API_SQLFREESTMT: + *pfExists = TRUE; + break; + case SQL_API_SQLGETCURSORNAME: + *pfExists = TRUE; + break; + case SQL_API_SQLNUMRESULTCOLS: + *pfExists = TRUE; + break; + case SQL_API_SQLPREPARE: + *pfExists = TRUE; + break; + case SQL_API_SQLROWCOUNT: + *pfExists = TRUE; + break; + case SQL_API_SQLSETCURSORNAME: + *pfExists = TRUE; + break; + case SQL_API_SQLSETPARAM: + *pfExists = FALSE; + break; /* odbc 1.0 */ + case SQL_API_SQLTRANSACT: + *pfExists = TRUE; + break; + + /* ODBC level 1 functions */ + case SQL_API_SQLBINDPARAMETER: + *pfExists = TRUE; + break; + case SQL_API_SQLCOLUMNS: + *pfExists = TRUE; + break; + case SQL_API_SQLDRIVERCONNECT: + *pfExists = TRUE; + break; + case SQL_API_SQLGETCONNECTOPTION: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLGETDATA: + *pfExists = TRUE; + break; + case SQL_API_SQLGETFUNCTIONS: + *pfExists = TRUE; + break; + case SQL_API_SQLGETINFO: + *pfExists = TRUE; + break; + case SQL_API_SQLGETSTMTOPTION: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLGETTYPEINFO: + *pfExists = TRUE; + break; + case SQL_API_SQLPARAMDATA: + *pfExists = TRUE; + break; + case SQL_API_SQLPUTDATA: + *pfExists = TRUE; + break; + case SQL_API_SQLSETCONNECTOPTION: + *pfExists = TRUE; + break; /* partial */ + case SQL_API_SQLSETSTMTOPTION: + *pfExists = TRUE; + break; + case SQL_API_SQLSPECIALCOLUMNS: + *pfExists = TRUE; + break; + case SQL_API_SQLSTATISTICS: + *pfExists = TRUE; + break; + case SQL_API_SQLTABLES: + *pfExists = TRUE; + break; + + /* ODBC level 2 functions */ + case SQL_API_SQLBROWSECONNECT: + *pfExists = FALSE; + break; + case SQL_API_SQLCOLUMNPRIVILEGES: + *pfExists = FALSE; + break; + case SQL_API_SQLDATASOURCES: + *pfExists = FALSE; + break; /* only implemented by DM */ + case SQL_API_SQLDESCRIBEPARAM: + *pfExists = FALSE; + break; /* not properly implemented */ + case SQL_API_SQLDRIVERS: + *pfExists = FALSE; + break; /* only implemented by DM */ + case SQL_API_SQLEXTENDEDFETCH: + *pfExists = TRUE; + break; + case SQL_API_SQLFOREIGNKEYS: + *pfExists = TRUE; + break; + case SQL_API_SQLMORERESULTS: + *pfExists = TRUE; + break; + case SQL_API_SQLNATIVESQL: + *pfExists = TRUE; + break; + case SQL_API_SQLNUMPARAMS: + *pfExists = TRUE; + break; + case SQL_API_SQLPARAMOPTIONS: + *pfExists = FALSE; + break; + case SQL_API_SQLPRIMARYKEYS: + *pfExists = TRUE; + break; + case SQL_API_SQLPROCEDURECOLUMNS: + *pfExists = FALSE; + break; + case SQL_API_SQLPROCEDURES: + if (PG_VERSION_LT(conn, 6.5)) + *pfExists = FALSE; + else + *pfExists = TRUE; + break; + case SQL_API_SQLSETPOS: + *pfExists = TRUE; + break; + case SQL_API_SQLSETSCROLLOPTIONS: + *pfExists = TRUE; + break; /* odbc 1.0 */ + case SQL_API_SQLTABLEPRIVILEGES: + *pfExists = FALSE; + break; + } + } + } + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Tables( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szTableType, + SWORD cbTableType) +{ + static char *func = "PGAPI_Tables"; + StatementClass *stmt = (StatementClass *) hstmt; + StatementClass *tbl_stmt; + TupleNode *row; + HSTMT htbl_stmt; + RETCODE result; + char *tableType; + char tables_query[INFO_INQUIRY_LEN]; + char table_name[MAX_INFO_STRING], + table_owner[MAX_INFO_STRING], + relkind_or_hasrules[MAX_INFO_STRING]; + ConnectionClass *conn; + ConnInfo *ci; + char *prefix[32], + prefixes[MEDIUM_REGISTRY_LEN]; + char *table_type[32], + table_types[MAX_INFO_STRING]; + char show_system_tables, + show_regular_tables, + show_views; + char regular_table, + view, + systable; + int i; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + conn = SC_get_conn(stmt); + ci = &(conn->connInfo); + + result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_Tables result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + tbl_stmt = (StatementClass *) htbl_stmt; + + /* + * Create the query to find out the tables + */ + if (PG_VERSION_GE(conn, 7.1)) + { + /* view is represented by its relkind since 7.1 */ + strcpy(tables_query, "select relname, usename, relkind from pg_class, pg_user"); + strcat(tables_query, " where relkind in ('r', 'v')"); + } + else + { + strcpy(tables_query, "select relname, usename, relhasrules from pg_class, pg_user"); + strcat(tables_query, " where relkind = 'r'"); + } + + my_strcat(tables_query, " and usename like '%.*s'", szTableOwner, cbTableOwner); + my_strcat(tables_query, " and relname like '%.*s'", szTableName, cbTableName); + + /* Parse the extra systable prefix */ + strcpy(prefixes, ci->drivers.extra_systable_prefixes); + i = 0; + prefix[i] = strtok(prefixes, ";"); + while (prefix[i] && i < 32) + prefix[++i] = strtok(NULL, ";"); + + /* Parse the desired table types to return */ + show_system_tables = FALSE; + show_regular_tables = FALSE; + show_views = FALSE; + + /* make_string mallocs memory */ + tableType = make_string(szTableType, cbTableType, NULL); + if (tableType) + { + strcpy(table_types, tableType); + free(tableType); + i = 0; + table_type[i] = strtok(table_types, ","); + while (table_type[i] && i < 32) + table_type[++i] = strtok(NULL, ","); + + /* Check for desired table types to return */ + i = 0; + while (table_type[i]) + { + if (strstr(table_type[i], "SYSTEM TABLE")) + show_system_tables = TRUE; + else if (strstr(table_type[i], "TABLE")) + show_regular_tables = TRUE; + else if (strstr(table_type[i], "VIEW")) + show_views = TRUE; + i++; + } + } + else + { + show_regular_tables = TRUE; + show_views = TRUE; + } + + /* + * If not interested in SYSTEM TABLES then filter them out to save + * some time on the query. If treating system tables as regular + * tables, then dont filter either. + */ + if (!atoi(ci->show_system_tables) && !show_system_tables) + { + strcat(tables_query, " and relname !~ '^" POSTGRES_SYS_PREFIX); + + /* Also filter out user-defined system table types */ + i = 0; + while (prefix[i]) + { + strcat(tables_query, "|^"); + strcat(tables_query, prefix[i]); + i++; + } + strcat(tables_query, "'"); + } + + /* match users */ + if (PG_VERSION_LT(conn, 7.1)) + /* filter out large objects in older versions */ + strcat(tables_query, " and relname !~ '^xinv[0-9]+'"); + + strcat(tables_query, " and usesysid = relowner"); + strcat(tables_query, " order by relname"); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_CHAR, + table_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_CHAR, + table_owner, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR, + relkind_or_hasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_Tables result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + extend_bindings(stmt, 5); + + /* set the field names */ + QR_set_num_fields(stmt->result, 5); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "TABLE_TYPE", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "REMARKS", PG_TYPE_TEXT, 254); + + /* add the tuples */ + result = PGAPI_Fetch(htbl_stmt); + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + /* + * Determine if this table name is a system table. If treating + * system tables as regular tables, then no need to do this test. + */ + systable = FALSE; + if (!atoi(ci->show_system_tables)) + { + if (strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0) + systable = TRUE; + + else + { + /* Check extra system table prefixes */ + i = 0; + while (prefix[i]) + { + mylog("table_name='%s', prefix[%d]='%s'\n", table_name, i, prefix[i]); + if (strncmp(table_name, prefix[i], strlen(prefix[i])) == 0) + { + systable = TRUE; + break; + } + i++; + } + } + } + + /* Determine if the table name is a view */ + if (PG_VERSION_GE(conn, 7.1)) + /* view is represented by its relkind since 7.1 */ + view = (relkind_or_hasrules[0] == 'v'); + else + view = (relkind_or_hasrules[0] == '1'); + + /* It must be a regular table */ + regular_table = (!systable && !view); + + + /* Include the row in the result set if meets all criteria */ + + /* + * NOTE: Unsupported table types (i.e., LOCAL TEMPORARY, ALIAS, + * etc) will return nothing + */ + if ((systable && show_system_tables) || + (view && show_views) || + (regular_table && show_regular_tables)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (5 - 1) *sizeof(TupleField)); + + set_tuplefield_string(&row->tuple[0], ""); + + /* + * I have to hide the table owner from Access, otherwise it + * insists on referring to the table as 'owner.table'. (this + * is valid according to the ODBC SQL grammar, but Postgres + * won't support it.) + * + * set_tuplefield_string(&row->tuple[1], table_owner); + */ + + mylog("%s: table_name = '%s'\n", func, table_name); + + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], systable ? "SYSTEM TABLE" : (view ? "VIEW" : "TABLE")); + set_tuplefield_string(&row->tuple[4], ""); + + QR_add_tuple(stmt->result, row); + } + result = PGAPI_Fetch(htbl_stmt); + } + if (result != SQL_NO_DATA_FOUND) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Columns( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + static char *func = "PGAPI_Columns"; + StatementClass *stmt = (StatementClass *) hstmt; + TupleNode *row; + HSTMT hcol_stmt; + StatementClass *col_stmt; + char columns_query[INFO_INQUIRY_LEN]; + RETCODE result; + char table_owner[MAX_INFO_STRING], + table_name[MAX_INFO_STRING], + field_name[MAX_INFO_STRING], + field_type_name[MAX_INFO_STRING]; + Int2 field_number, + result_cols, + scale; + Int4 field_type, + the_type, + field_length, + mod_length, + precision; + char useStaticPrecision; + char not_null[MAX_INFO_STRING], + relhasrules[MAX_INFO_STRING]; + ConnInfo *ci; + ConnectionClass *conn; + + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + conn = SC_get_conn(stmt); + ci = &(conn->connInfo); + + /* + * Create the query to find out the columns (Note: pre 6.3 did not + * have the atttypmod field) + */ + sprintf(columns_query, "select u.usename, c.relname, a.attname, a.atttypid" + ", t.typname, a.attnum, a.attlen, %s, a.attnotnull, c.relhasrules" + " from pg_user u, pg_class c, pg_attribute a, pg_type t" + " where u.usesysid = c.relowner" + " and c.oid= a.attrelid and a.atttypid = t.oid and (a.attnum > 0)", + PG_VERSION_LE(conn, 6.2) ? "a.attlen" : "a.atttypmod"); + + my_strcat(columns_query, " and c.relname like '%.*s'", szTableName, cbTableName); + my_strcat(columns_query, " and u.usename like '%.*s'", szTableOwner, cbTableOwner); + my_strcat(columns_query, " and a.attname like '%.*s'", szColumnName, cbColumnName); + + /* + * give the output in the order the columns were defined when the + * table was created + */ + strcat(columns_query, " order by attnum"); + + result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_Columns result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + col_stmt = (StatementClass *) hcol_stmt; + + mylog("%s: hcol_stmt = %u, col_stmt = %u\n", func, hcol_stmt, col_stmt); + + result = PGAPI_ExecDirect(hcol_stmt, columns_query, + strlen(columns_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 1, SQL_C_CHAR, + table_owner, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 2, SQL_C_CHAR, + table_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 3, SQL_C_CHAR, + field_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 4, SQL_C_LONG, + &field_type, 4, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 5, SQL_C_CHAR, + field_type_name, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 6, SQL_C_SHORT, + &field_number, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 7, SQL_C_LONG, + &field_length, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 8, SQL_C_LONG, + &mod_length, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 9, SQL_C_CHAR, + not_null, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 10, SQL_C_CHAR, + relhasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_Columns result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 14; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "DATA_TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 5, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "PRECISION", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 7, "LENGTH", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 8, "SCALE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 9, "RADIX", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 10, "NULLABLE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 11, "REMARKS", PG_TYPE_TEXT, 254); + + /* User defined fields */ + QR_set_field_info(stmt->result, 12, "DISPLAY_SIZE", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 13, "FIELD_TYPE", PG_TYPE_INT4, 4); + + result = PGAPI_Fetch(hcol_stmt); + + /* + * Only show oid if option AND there are other columns AND it's not + * being called by SQLStatistics . Always show OID if it's a system + * table + */ + + if (result != SQL_ERROR && !stmt->internal) + { + if (relhasrules[0] != '1' && + (atoi(ci->show_oid_column) || + strncmp(table_name, POSTGRES_SYS_PREFIX, strlen(POSTGRES_SYS_PREFIX)) == 0)) + { + /* For OID fields */ + the_type = PG_TYPE_OID; + row = (TupleNode *) malloc(sizeof(TupleNode) + + (result_cols - 1) *sizeof(TupleField)); + + set_tuplefield_string(&row->tuple[0], ""); + /* see note in SQLTables() */ + /* set_tuplefield_string(&row->tuple[1], table_owner); */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], "oid"); + set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, the_type)); + set_tuplefield_string(&row->tuple[5], "OID"); + + set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC)); + + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, the_type, PG_STATIC)); + set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, the_type)); + set_tuplefield_int2(&row->tuple[10], SQL_NO_NULLS); + set_tuplefield_string(&row->tuple[11], ""); + + set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[13], the_type); + + QR_add_tuple(stmt->result, row); + } + } + + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + + (result_cols - 1) *sizeof(TupleField)); + + + set_tuplefield_string(&row->tuple[0], ""); + /* see note in SQLTables() */ + /* set_tuplefield_string(&row->tuple[1], table_owner); */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], field_name); + set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, field_type)); + set_tuplefield_string(&row->tuple[5], field_type_name); + + + /*---------- + * Some Notes about Postgres Data Types: + * + * VARCHAR - the length is stored in the pg_attribute.atttypmod field + * BPCHAR - the length is also stored as varchar is + * + * NUMERIC - the scale is stored in atttypmod as follows: + * + * precision =((atttypmod - VARHDRSZ) >> 16) & 0xffff + * scale = (atttypmod - VARHDRSZ) & 0xffff + * + *---------- + */ + qlog("PGAPI_Columns: table='%s',field_name='%s',type=%d,sqltype=%d,name='%s'\n", + table_name, field_name, field_type, pgtype_to_sqltype, field_type_name); + + useStaticPrecision = TRUE; + + if (field_type == PG_TYPE_NUMERIC) + { + if (mod_length >= 4) + mod_length -= 4; /* the length is in atttypmod - 4 */ + + if (mod_length >= 0) + { + useStaticPrecision = FALSE; + + precision = (mod_length >> 16) & 0xffff; + scale = mod_length & 0xffff; + + mylog("%s: field type is NUMERIC: field_type = %d, mod_length=%d, precision=%d, scale=%d\n", func, field_type, mod_length, precision, scale); + + set_tuplefield_int4(&row->tuple[7], precision + 2); /* sign+dec.point */ + set_tuplefield_int4(&row->tuple[6], precision); + set_tuplefield_int4(&row->tuple[12], precision + 2); /* sign+dec.point */ + set_nullfield_int2(&row->tuple[8], scale); + } + } + + if ((field_type == PG_TYPE_VARCHAR) || + (field_type == PG_TYPE_BPCHAR)) + { + useStaticPrecision = FALSE; + + if (mod_length >= 4) + mod_length -= 4; /* the length is in atttypmod - 4 */ + + if (mod_length > ci->drivers.max_varchar_size || mod_length <= 0) + mod_length = ci->drivers.max_varchar_size; + + mylog("%s: field type is VARCHAR,BPCHAR: field_type = %d, mod_length = %d\n", func, field_type, mod_length); + + set_tuplefield_int4(&row->tuple[7], mod_length); + set_tuplefield_int4(&row->tuple[6], mod_length); + set_tuplefield_int4(&row->tuple[12], mod_length); + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, field_type, PG_STATIC)); + } + + if (useStaticPrecision) + { + mylog("%s: field type is OTHER: field_type = %d, pgtype_length = %d\n", func, field_type, pgtype_length(stmt, field_type, PG_STATIC, PG_STATIC)); + + set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, field_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, field_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, field_type, PG_STATIC, PG_STATIC)); + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, field_type, PG_STATIC)); + } + + set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, field_type)); + set_tuplefield_int2(&row->tuple[10], (Int2) (not_null[0] == '1' ? SQL_NO_NULLS : pgtype_nullable(stmt, field_type))); + set_tuplefield_string(&row->tuple[11], ""); + set_tuplefield_int4(&row->tuple[13], field_type); + + QR_add_tuple(stmt->result, row); + + + result = PGAPI_Fetch(hcol_stmt); + + } + if (result != SQL_NO_DATA_FOUND) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + /* + * Put the row version column at the end so it might not be mistaken + * for a key field. + */ + if (relhasrules[0] != '1' && !stmt->internal && atoi(ci->row_versioning)) + { + /* For Row Versioning fields */ + the_type = PG_TYPE_INT4; + + row = (TupleNode *) malloc(sizeof(TupleNode) + + (result_cols - 1) *sizeof(TupleField)); + + set_tuplefield_string(&row->tuple[0], ""); + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + set_tuplefield_string(&row->tuple[3], "xmin"); + set_tuplefield_int2(&row->tuple[4], pgtype_to_sqltype(stmt, the_type)); + set_tuplefield_string(&row->tuple[5], pgtype_to_name(stmt, the_type)); + set_tuplefield_int4(&row->tuple[6], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[7], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC)); + set_nullfield_int2(&row->tuple[8], pgtype_scale(stmt, the_type, PG_STATIC)); + set_nullfield_int2(&row->tuple[9], pgtype_radix(stmt, the_type)); + set_tuplefield_int2(&row->tuple[10], SQL_NO_NULLS); + set_tuplefield_string(&row->tuple[11], ""); + set_tuplefield_int4(&row->tuple[12], pgtype_display_size(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[13], the_type); + + QR_add_tuple(stmt->result, row); + } + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_SpecialColumns( + HSTMT hstmt, + UWORD fColType, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UWORD fScope, + UWORD fNullable) +{ + static char *func = "PGAPI_SpecialColumns"; + TupleNode *row; + StatementClass *stmt = (StatementClass *) hstmt; + ConnInfo *ci; + HSTMT hcol_stmt; + StatementClass *col_stmt; + char columns_query[INFO_INQUIRY_LEN]; + RETCODE result; + char relhasrules[MAX_INFO_STRING]; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + ci = &(SC_get_conn(stmt)->connInfo); + + stmt->manual_result = TRUE; + + /* + * Create the query to find out if this is a view or not... + */ + sprintf(columns_query, "select c.relhasrules " + "from pg_user u, pg_class c where " + "u.usesysid = c.relowner"); + + my_strcat(columns_query, " and c.relname like '%.*s'", szTableName, cbTableName); + my_strcat(columns_query, " and u.usename like '%.*s'", szTableOwner, cbTableOwner); + + + result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for SQLSpecialColumns result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + col_stmt = (StatementClass *) hcol_stmt; + + mylog("%s: hcol_stmt = %u, col_stmt = %u\n", func, hcol_stmt, col_stmt); + + result = PGAPI_ExecDirect(hcol_stmt, columns_query, + strlen(columns_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(hcol_stmt, 1, SQL_C_CHAR, + relhasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(hcol_stmt); + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + + stmt->result = QR_Constructor(); + extend_bindings(stmt, 8); + + QR_set_num_fields(stmt->result, 8); + QR_set_field_info(stmt->result, 0, "SCOPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 1, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "DATA_TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 3, "TYPE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "PRECISION", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 5, "LENGTH", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 6, "SCALE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 7, "PSEUDO_COLUMN", PG_TYPE_INT2, 2); + + if (relhasrules[0] != '1') + { + /* use the oid value for the rowid */ + if (fColType == SQL_BEST_ROWID) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (8 - 1) *sizeof(TupleField)); + + set_tuplefield_int2(&row->tuple[0], SQL_SCOPE_SESSION); + set_tuplefield_string(&row->tuple[1], "oid"); + set_tuplefield_int2(&row->tuple[2], pgtype_to_sqltype(stmt, PG_TYPE_OID)); + set_tuplefield_string(&row->tuple[3], "OID"); + set_tuplefield_int4(&row->tuple[4], pgtype_precision(stmt, PG_TYPE_OID, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[5], pgtype_length(stmt, PG_TYPE_OID, PG_STATIC, PG_STATIC)); + set_tuplefield_int2(&row->tuple[6], pgtype_scale(stmt, PG_TYPE_OID, PG_STATIC)); + set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO); + + QR_add_tuple(stmt->result, row); + + } + else if (fColType == SQL_ROWVER) + { + Int2 the_type = PG_TYPE_INT4; + + if (atoi(ci->row_versioning)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (8 - 1) *sizeof(TupleField)); + + set_tuplefield_null(&row->tuple[0]); + set_tuplefield_string(&row->tuple[1], "xmin"); + set_tuplefield_int2(&row->tuple[2], pgtype_to_sqltype(stmt, the_type)); + set_tuplefield_string(&row->tuple[3], pgtype_to_name(stmt, the_type)); + set_tuplefield_int4(&row->tuple[4], pgtype_precision(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int4(&row->tuple[5], pgtype_length(stmt, the_type, PG_STATIC, PG_STATIC)); + set_tuplefield_int2(&row->tuple[6], pgtype_scale(stmt, the_type, PG_STATIC)); + set_tuplefield_int2(&row->tuple[7], SQL_PC_PSEUDO); + + QR_add_tuple(stmt->result, row); + } + } + } + + stmt->status = STMT_FINISHED; + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_Statistics( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UWORD fUnique, + UWORD fAccuracy) +{ + static char *func = "PGAPI_Statistics"; + StatementClass *stmt = (StatementClass *) hstmt; + char index_query[INFO_INQUIRY_LEN]; + HSTMT hindx_stmt; + RETCODE result; + char *table_name; + char index_name[MAX_INFO_STRING]; + short fields_vector[16]; + char isunique[10], + isclustered[10], + ishash[MAX_INFO_STRING]; + SDWORD index_name_len, + fields_vector_len; + TupleNode *row; + int i; + HSTMT hcol_stmt; + StatementClass *col_stmt, + *indx_stmt; + char column_name[MAX_INFO_STRING], + relhasrules[MAX_INFO_STRING]; + char **column_names = 0; + SQLINTEGER column_name_len; + int total_columns = 0; + char error = TRUE; + ConnInfo *ci; + char buf[256]; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + ci = &(SC_get_conn(stmt)->connInfo); + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_Statistics result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + extend_bindings(stmt, 13); + + /* set the field names */ + QR_set_num_fields(stmt->result, 13); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "NON_UNIQUE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 4, "INDEX_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "INDEX_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "TYPE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 7, "SEQ_IN_INDEX", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 8, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 9, "COLLATION", PG_TYPE_CHAR, 1); + QR_set_field_info(stmt->result, 10, "CARDINALITY", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 11, "PAGES", PG_TYPE_INT4, 4); + QR_set_field_info(stmt->result, 12, "FILTER_CONDITION", PG_TYPE_TEXT, MAX_INFO_STRING); + + /* + * only use the table name... the owner should be redundant, and we + * never use qualifiers. + */ + table_name = make_string(szTableName, cbTableName, NULL); + if (!table_name) + { + stmt->errormsg = "No table name passed to PGAPI_Statistics."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* + * we need to get a list of the field names first, so we can return + * them later. + */ + result = PGAPI_AllocStmt(stmt->hdbc, &hcol_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = "PGAPI_AllocStmt failed in PGAPI_Statistics for columns."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + goto SEEYA; + } + + col_stmt = (StatementClass *) hcol_stmt; + + /* + * "internal" prevents SQLColumns from returning the oid if it is + * being shown. This would throw everything off. + */ + col_stmt->internal = TRUE; + result = PGAPI_Columns(hcol_stmt, "", 0, "", 0, + table_name, (SWORD) strlen(table_name), "", 0); + col_stmt->internal = FALSE; + + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; /* "SQLColumns failed in + * SQLStatistics."; */ + stmt->errornumber = col_stmt->errornumber; /* STMT_EXEC_ERROR; */ + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + } + result = PGAPI_BindCol(hcol_stmt, 4, SQL_C_CHAR, + column_name, MAX_INFO_STRING, &column_name_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = col_stmt->errormsg; + stmt->errornumber = col_stmt->errornumber; + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + + } + + result = PGAPI_Fetch(hcol_stmt); + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + total_columns++; + + column_names = + (char **) realloc(column_names, + total_columns * sizeof(char *)); + column_names[total_columns - 1] = + (char *) malloc(strlen(column_name) + 1); + strcpy(column_names[total_columns - 1], column_name); + + mylog("%s: column_name = '%s'\n", func, column_name); + + result = PGAPI_Fetch(hcol_stmt); + } + + if (result != SQL_NO_DATA_FOUND || total_columns == 0) + { + stmt->errormsg = SC_create_errormsg(hcol_stmt); /* "Couldn't get column + * names in + * SQLStatistics."; */ + stmt->errornumber = col_stmt->errornumber; + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + goto SEEYA; + + } + + PGAPI_FreeStmt(hcol_stmt, SQL_DROP); + + /* get a list of indexes on this table */ + result = PGAPI_AllocStmt(stmt->hdbc, &hindx_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = "PGAPI_AllocStmt failed in SQLStatistics for indices."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + goto SEEYA; + + } + indx_stmt = (StatementClass *) hindx_stmt; + + sprintf(index_query, "select c.relname, i.indkey, i.indisunique" + ", i.indisclustered, a.amname, c.relhasrules" + " from pg_index i, pg_class c, pg_class d, pg_am a" + " where d.relname = '%s'" + " and d.oid = i.indrelid" + " and i.indexrelid = c.oid" + " and c.relam = a.oid" + ,table_name); + if (PG_VERSION_GT(SC_get_conn(stmt), 6.4)) + strcat(index_query, " order by i.indisprimary desc"); + + result = PGAPI_ExecDirect(hindx_stmt, index_query, strlen(index_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + /* + * "Couldn't execute index query (w/SQLExecDirect) in + * SQLStatistics."; + */ + stmt->errormsg = SC_create_errormsg(hindx_stmt); + + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + /* bind the index name column */ + result = PGAPI_BindCol(hindx_stmt, 1, SQL_C_CHAR, + index_name, MAX_INFO_STRING, &index_name_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + /* bind the vector column */ + result = PGAPI_BindCol(hindx_stmt, 2, SQL_C_DEFAULT, + fields_vector, 32, &fields_vector_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + /* bind the "is unique" column */ + result = PGAPI_BindCol(hindx_stmt, 3, SQL_C_CHAR, + isunique, sizeof(isunique), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + /* bind the "is clustered" column */ + result = PGAPI_BindCol(hindx_stmt, 4, SQL_C_CHAR, + isclustered, sizeof(isclustered), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + + /* bind the "is hash" column */ + result = PGAPI_BindCol(hindx_stmt, 5, SQL_C_CHAR, + ishash, sizeof(ishash), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; /* "Couldn't bind column + * in SQLStatistics."; */ + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + + } + + result = PGAPI_BindCol(hindx_stmt, 6, SQL_C_CHAR, + relhasrules, MAX_INFO_STRING, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = indx_stmt->errormsg; + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + /* fake index of OID */ + if (relhasrules[0] != '1' && atoi(ci->show_oid_column) && atoi(ci->fake_oid_index)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + + (13 - 1) *sizeof(TupleField)); + + /* no table qualifier */ + set_tuplefield_string(&row->tuple[0], ""); + /* don't set the table owner, else Access tries to use it */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + + /* non-unique index? */ + set_tuplefield_int2(&row->tuple[3], (Int2) (ci->drivers.unique_index ? FALSE : TRUE)); + + /* no index qualifier */ + set_tuplefield_string(&row->tuple[4], ""); + + sprintf(buf, "%s_idx_fake_oid", table_name); + set_tuplefield_string(&row->tuple[5], buf); + + /* + * Clustered/HASH index? + */ + set_tuplefield_int2(&row->tuple[6], (Int2) SQL_INDEX_OTHER); + set_tuplefield_int2(&row->tuple[7], (Int2) 1); + + set_tuplefield_string(&row->tuple[8], "oid"); + set_tuplefield_string(&row->tuple[9], "A"); + set_tuplefield_null(&row->tuple[10]); + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + + QR_add_tuple(stmt->result, row); + } + + result = PGAPI_Fetch(hindx_stmt); + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + /* If only requesting unique indexs, then just return those. */ + if (fUnique == SQL_INDEX_ALL || + (fUnique == SQL_INDEX_UNIQUE && atoi(isunique))) + { + i = 0; + /* add a row in this table for each field in the index */ + while (i < 16 && fields_vector[i] != 0) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + + (13 - 1) *sizeof(TupleField)); + + /* no table qualifier */ + set_tuplefield_string(&row->tuple[0], ""); + /* don't set the table owner, else Access tries to use it */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], table_name); + + /* non-unique index? */ + if (ci->drivers.unique_index) + set_tuplefield_int2(&row->tuple[3], (Int2) (atoi(isunique) ? FALSE : TRUE)); + else + set_tuplefield_int2(&row->tuple[3], TRUE); + + /* no index qualifier */ + set_tuplefield_string(&row->tuple[4], ""); + set_tuplefield_string(&row->tuple[5], index_name); + + /* + * Clustered/HASH index? + */ + set_tuplefield_int2(&row->tuple[6], (Int2) + (atoi(isclustered) ? SQL_INDEX_CLUSTERED : + (!strncmp(ishash, "hash", 4)) ? SQL_INDEX_HASHED : SQL_INDEX_OTHER)); + set_tuplefield_int2(&row->tuple[7], (Int2) (i + 1)); + + if (fields_vector[i] == OID_ATTNUM) + { + set_tuplefield_string(&row->tuple[8], "oid"); + mylog("%s: column name = oid\n", func); + } + else if (fields_vector[i] < 0 || fields_vector[i] > total_columns) + { + set_tuplefield_string(&row->tuple[8], "UNKNOWN"); + mylog("%s: column name = UNKNOWN\n", func); + } + else + { + set_tuplefield_string(&row->tuple[8], column_names[fields_vector[i] - 1]); + mylog("%s: column name = '%s'\n", func, column_names[fields_vector[i] - 1]); + } + + set_tuplefield_string(&row->tuple[9], "A"); + set_tuplefield_null(&row->tuple[10]); + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + + QR_add_tuple(stmt->result, row); + i++; + } + } + + result = PGAPI_Fetch(hindx_stmt); + } + if (result != SQL_NO_DATA_FOUND) + { + /* "SQLFetch failed in SQLStatistics."; */ + stmt->errormsg = SC_create_errormsg(hindx_stmt); + stmt->errornumber = indx_stmt->errornumber; + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + goto SEEYA; + } + + PGAPI_FreeStmt(hindx_stmt, SQL_DROP); + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + error = FALSE; + +SEEYA: + /* These things should be freed on any error ALSO! */ + free(table_name); + for (i = 0; i < total_columns; i++) + free(column_names[i]); + free(column_names); + + mylog("%s: EXIT, %s, stmt=%u\n", func, error ? "error" : "success", stmt); + + if (error) + { + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + else + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_ColumnPrivileges( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + static char *func = "PGAPI_ColumnPrivileges"; + + mylog("%s: entering...\n", func); + + /* Neither Access or Borland care about this. */ + + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; +} + + +/* + * SQLPrimaryKeys() + * + * Retrieve the primary key columns for the specified table. + */ +RETCODE SQL_API +PGAPI_PrimaryKeys( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName) +{ + static char *func = "PGAPI_PrimaryKeys"; + StatementClass *stmt = (StatementClass *) hstmt; + ConnectionClass *conn; + TupleNode *row; + RETCODE result; + int seq = 0; + HSTMT htbl_stmt; + StatementClass *tbl_stmt; + char tables_query[INFO_INQUIRY_LEN]; + char attname[MAX_INFO_STRING]; + SDWORD attname_len; + char pktab[MAX_TABLE_LEN + 1]; + Int2 result_cols; + int qno, + qstart, + qend; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_PrimaryKeys result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 6; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "TABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "COLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "KEY_SEQ", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 5, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + + + result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for Primary Key result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + tbl_stmt = (StatementClass *) htbl_stmt; + + pktab[0] = '\0'; + make_string(szTableName, cbTableName, pktab); + if (pktab[0] == '\0') + { + stmt->errormsg = "No Table specified to PGAPI_PrimaryKeys."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_CHAR, + attname, MAX_INFO_STRING, &attname_len); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + conn = SC_get_conn(stmt); + if (PG_VERSION_LE(conn, 6.4)) + qstart = 2; + else + qstart = 1; + qend = 2; + for (qno = qstart; qno <= qend; qno++) + { + switch (qno) + { + case 1: + + /* + * Simplified query to remove assumptions about number of + * possible index columns. Courtesy of Tom Lane - thomas + * 2000-03-21 + */ + sprintf(tables_query, "select ta.attname, ia.attnum" + " from pg_attribute ta, pg_attribute ia, pg_class c, pg_index i" + " where c.relname = '%s'" + " AND c.oid = i.indrelid" + " AND i.indisprimary = 't'" + " AND ia.attrelid = i.indexrelid" + " AND ta.attrelid = i.indrelid" + " AND ta.attnum = i.indkey[ia.attnum-1]" + " order by ia.attnum", pktab); + break; + case 2: + + /* + * Simplified query to search old fashoned primary key + */ + sprintf(tables_query, "select ta.attname, ia.attnum" + " from pg_attribute ta, pg_attribute ia, pg_class c, pg_index i" + " where c.relname = '%s_pkey'" + " AND c.oid = i.indexrelid" + " AND ia.attrelid = i.indexrelid" + " AND ta.attrelid = i.indrelid" + " AND ta.attnum = i.indkey[ia.attnum-1]" + " order by ia.attnum", pktab); + break; + } + mylog("%s: tables_query='%s'\n", func, tables_query); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(htbl_stmt); + if (result != SQL_NO_DATA_FOUND) + break; + } + + while ((result == SQL_SUCCESS) || (result == SQL_SUCCESS_WITH_INFO)) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField)); + + set_tuplefield_null(&row->tuple[0]); + + /* + * I have to hide the table owner from Access, otherwise it + * insists on referring to the table as 'owner.table'. (this is + * valid according to the ODBC SQL grammar, but Postgres won't + * support it.) + */ + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], pktab); + set_tuplefield_string(&row->tuple[3], attname); + set_tuplefield_int2(&row->tuple[4], (Int2) (++seq)); + set_tuplefield_null(&row->tuple[5]); + + QR_add_tuple(stmt->result, row); + + mylog(">> primaryKeys: pktab = '%s', attname = '%s', seq = %d\n", pktab, attname, seq); + + result = PGAPI_Fetch(htbl_stmt); + } + + if (result != SQL_NO_DATA_FOUND) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + mylog("%s: EXIT, stmt=%u\n", func, stmt); + return SQL_SUCCESS; +} + + +#ifdef MULTIBYTE +/* + * Multibyte support stuff for SQLForeignKeys(). + * There may be much more effective way in the + * future version. The way is very forcible currently. + */ +static BOOL +isMultibyte(const unsigned char *str) +{ + for (; *str; str++) + { + if (*str >= 0x80) + return TRUE; + } + return FALSE; +} +static char * +getClientTableName(ConnectionClass *conn, char *serverTableName, BOOL *nameAlloced) +{ + char query[1024], + saveoid[24], + *ret = serverTableName; + BOOL continueExec = TRUE, + bError = FALSE; + QResultClass *res; + + *nameAlloced = FALSE; + if (!conn->client_encoding || !isMultibyte(serverTableName)) + return ret; + if (!conn->server_encoding) + { + if (res = CC_send_query(conn, "select getdatabaseencoding()", NULL), res) + { + if (QR_get_num_tuples(res) > 0) + conn->server_encoding = strdup(QR_get_value_backend_row(res, 0, 0)); + QR_Destructor(res); + } + } + if (!conn->server_encoding) + return ret; + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->server_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (!bError && continueExec) + { + sprintf(query, "select OID from pg_class where relname = '%s'", serverTableName); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + strcpy(saveoid, QR_get_value_backend_row(res, 0, 0)); + else + { + continueExec = FALSE; + bError = QR_get_aborted(res); + } + QR_Destructor(res); + } + else + bError = TRUE; + } + continueExec = (continueExec && !bError); + if (bError && CC_is_in_trans(conn)) + { + if (res = CC_send_query(conn, "abort", NULL), res) + QR_Destructor(res); + CC_set_no_trans(conn); + bError = FALSE; + } + /* restore the client encoding */ + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->client_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (bError || !continueExec) + return ret; + sprintf(query, "select relname from pg_class where OID = %s", saveoid); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + { + ret = strdup(QR_get_value_backend_row(res, 0, 0)); + *nameAlloced = TRUE; + } + QR_Destructor(res); + } + return ret; +} +static char * +getClientColumnName(ConnectionClass *conn, const char *serverTableName, char *serverColumnName, BOOL *nameAlloced) +{ + char query[1024], + saveattrelid[24], + saveattnum[16], + *ret = serverColumnName; + BOOL continueExec = TRUE, + bError = FALSE; + QResultClass *res; + + *nameAlloced = FALSE; + if (!conn->client_encoding || !isMultibyte(serverColumnName)) + return ret; + if (!conn->server_encoding) + { + if (res = CC_send_query(conn, "select getdatabaseencoding()", NULL), res) + { + if (QR_get_num_tuples(res) > 0) + conn->server_encoding = strdup(QR_get_value_backend_row(res, 0, 0)); + QR_Destructor(res); + } + } + if (!conn->server_encoding) + return ret; + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->server_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (!bError && continueExec) + { + sprintf(query, "select attrelid, attnum from pg_class, pg_attribute " + "where relname = '%s' and attrelid = pg_class.oid " + "and attname = '%s'", serverTableName, serverColumnName); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + { + strcpy(saveattrelid, QR_get_value_backend_row(res, 0, 0)); + strcpy(saveattnum, QR_get_value_backend_row(res, 0, 1)); + } + else + { + continueExec = FALSE; + bError = QR_get_aborted(res); + } + QR_Destructor(res); + } + else + bError = TRUE; + } + continueExec = (continueExec && !bError); + if (bError && CC_is_in_trans(conn)) + { + if (res = CC_send_query(conn, "abort", NULL), res) + QR_Destructor(res); + CC_set_no_trans(conn); + bError = FALSE; + } + /* restore the cleint encoding */ + sprintf(query, "SET CLIENT_ENCODING TO '%s'", conn->client_encoding); + if (res = CC_send_query(conn, query, NULL), res) + { + bError = QR_get_aborted(res); + QR_Destructor(res); + } + else + bError = TRUE; + if (bError || !continueExec) + return ret; + sprintf(query, "select attname from pg_attribute where attrelid = %s and attnum = %s", saveattrelid, saveattnum); + if (res = CC_send_query(conn, query, NULL), res) + { + if (QR_get_num_tuples(res) > 0) + { + ret = strdup(QR_get_value_backend_row(res, 0, 0)); + *nameAlloced = TRUE; + } + QR_Destructor(res); + } + return ret; +} +#endif /* MULTIBYTE */ + +RETCODE SQL_API +PGAPI_ForeignKeys( + HSTMT hstmt, + UCHAR FAR * szPkTableQualifier, + SWORD cbPkTableQualifier, + UCHAR FAR * szPkTableOwner, + SWORD cbPkTableOwner, + UCHAR FAR * szPkTableName, + SWORD cbPkTableName, + UCHAR FAR * szFkTableQualifier, + SWORD cbFkTableQualifier, + UCHAR FAR * szFkTableOwner, + SWORD cbFkTableOwner, + UCHAR FAR * szFkTableName, + SWORD cbFkTableName) +{ + static char *func = "PGAPI_ForeignKeys"; + StatementClass *stmt = (StatementClass *) hstmt; + TupleNode *row; + HSTMT htbl_stmt, + hpkey_stmt; + StatementClass *tbl_stmt; + RETCODE result, + keyresult; + char tables_query[INFO_INQUIRY_LEN]; + char trig_deferrable[2]; + char trig_initdeferred[2]; + char trig_args[1024]; + char upd_rule[MAX_TABLE_LEN], + del_rule[MAX_TABLE_LEN]; + char pk_table_needed[MAX_TABLE_LEN + 1]; + char fk_table_needed[MAX_TABLE_LEN + 1]; + char *pkey_ptr, + *pkey_text, + *fkey_ptr, + *fkey_text, + *pk_table, + *pkt_text, + *fk_table, + *fkt_text; + +#ifdef MULTIBYTE + BOOL pkey_alloced, + fkey_alloced, + pkt_alloced, + fkt_alloced; + ConnectionClass *conn; +#endif /* MULTIBYTE */ + int i, + j, + k, + num_keys; + SWORD trig_nargs, + upd_rule_type = 0, + del_rule_type = 0; + +#if (ODBCVER >= 0x0300) + SWORD defer_type; +#endif + char pkey[MAX_INFO_STRING]; + Int2 result_cols; + + mylog("%s: entering...stmt=%u\n", func, stmt); + + if (!stmt) + { + SC_log_error(func, "", NULL); + return SQL_INVALID_HANDLE; + } + + stmt->manual_result = TRUE; + stmt->errormsg_created = TRUE; + + stmt->result = QR_Constructor(); + if (!stmt->result) + { + stmt->errormsg = "Couldn't allocate memory for PGAPI_ForeignKeys result."; + stmt->errornumber = STMT_NO_MEMORY_ERROR; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + /* the binding structure for a statement is not set up until */ + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 14; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "PKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "PKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "PKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "PKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "FKTABLE_QUALIFIER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "FKTABLE_OWNER", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "FKTABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 7, "FKCOLUMN_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 8, "KEY_SEQ", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 9, "UPDATE_RULE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 10, "DELETE_RULE", PG_TYPE_INT2, 2); + QR_set_field_info(stmt->result, 11, "FK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 12, "PK_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 13, "TRIGGER_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); +#if (ODBCVER >= 0x0300) + QR_set_field_info(stmt->result, 14, "DEFERRABILITY", PG_TYPE_INT2, 2); +#endif /* ODBCVER >= 0x0300 */ + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + + result = PGAPI_AllocStmt(stmt->hdbc, &htbl_stmt); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_ForeignKeys result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + tbl_stmt = (StatementClass *) htbl_stmt; + + pk_table_needed[0] = '\0'; + fk_table_needed[0] = '\0'; + + make_string(szPkTableName, cbPkTableName, pk_table_needed); + make_string(szFkTableName, cbFkTableName, fk_table_needed); + +#ifdef MULTIBYTE + pkey_text = fkey_text = pkt_text = fkt_text = NULL; + pkey_alloced = fkey_alloced = pkt_alloced = fkt_alloced = FALSE; + conn = SC_get_conn(stmt); +#endif /* MULTIBYTE */ + + /* + * Case #2 -- Get the foreign keys in the specified table (fktab) that + * refer to the primary keys of other table(s). + */ + if (fk_table_needed[0] != '\0') + { + mylog("%s: entering Foreign Key Case #2", func); + sprintf(tables_query, "SELECT pt.tgargs, " + " pt.tgnargs, " + " pt.tgdeferrable, " + " pt.tginitdeferred, " + " pg_proc.proname, " + " pg_proc_1.proname " + "FROM pg_class pc, " + " pg_proc pg_proc, " + " pg_proc pg_proc_1, " + " pg_trigger pg_trigger, " + " pg_trigger pg_trigger_1, " + " pg_proc pp, " + " pg_trigger pt " + "WHERE pt.tgrelid = pc.oid " + "AND pp.oid = pt.tgfoid " + "AND pg_trigger.tgconstrrelid = pc.oid " + "AND pg_proc.oid = pg_trigger.tgfoid " + "AND pg_trigger_1.tgfoid = pg_proc_1.oid " + "AND pg_trigger_1.tgconstrrelid = pc.oid " + "AND ((pc.relname='%s') " + "AND (pp.proname LIKE '%%ins') " + "AND (pg_proc.proname LIKE '%%upd') " + "AND (pg_proc_1.proname LIKE '%%del') " + "AND (pg_trigger.tgrelid=pt.tgconstrrelid) " + "AND (pg_trigger.tgconstrname=pt.tgconstrname) " + "AND (pg_trigger_1.tgrelid=pt.tgconstrrelid) " + "AND (pg_trigger_1.tgconstrname=pt.tgconstrname))", + fk_table_needed); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_BINARY, + trig_args, sizeof(trig_args), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_SHORT, + &trig_nargs, 0, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR, + trig_deferrable, sizeof(trig_deferrable), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 4, SQL_C_CHAR, + trig_initdeferred, sizeof(trig_initdeferred), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 5, SQL_C_CHAR, + upd_rule, sizeof(upd_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 6, SQL_C_CHAR, + del_rule, sizeof(del_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(htbl_stmt); + if (result == SQL_NO_DATA_FOUND) + return SQL_SUCCESS; + + if (result != SQL_SUCCESS) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + keyresult = PGAPI_AllocStmt(stmt->hdbc, &hpkey_stmt); + if ((keyresult != SQL_SUCCESS) && (keyresult != SQL_SUCCESS_WITH_INFO)) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't allocate statement for PGAPI_ForeignKeys (pkeys) result."; + SC_log_error(func, "", stmt); + return SQL_ERROR; + } + + keyresult = PGAPI_BindCol(hpkey_stmt, 4, SQL_C_CHAR, + pkey, sizeof(pkey), NULL); + if (keyresult != SQL_SUCCESS) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't bindcol for primary keys for PGAPI_ForeignKeys result."; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hpkey_stmt, SQL_DROP); + return SQL_ERROR; + } + + while (result == SQL_SUCCESS) + { + /* Compute the number of keyparts. */ + num_keys = (trig_nargs - 4) / 2; + + mylog("Foreign Key Case#2: trig_nargs = %d, num_keys = %d\n", trig_nargs, num_keys); + + pk_table = trig_args; + + /* Get to the PK Table Name */ + for (k = 0; k < 2; k++) + pk_table += strlen(pk_table) + 1; + +#ifdef MULTIBYTE + fk_table = trig_args + strlen(trig_args) + 1; + pkt_text = getClientTableName(conn, pk_table, &pkt_alloced); +#else + pkt_text = pk_table; +#endif /* MULTIBYTE */ + /* If there is a pk table specified, then check it. */ + if (pk_table_needed[0] != '\0') + { + /* If it doesn't match, then continue */ + if (strcmp(pkt_text, pk_table_needed)) + { + result = PGAPI_Fetch(htbl_stmt); + continue; + } + } + + keyresult = PGAPI_PrimaryKeys(hpkey_stmt, NULL, 0, NULL, 0, pkt_text, SQL_NTS); + if (keyresult != SQL_SUCCESS) + { + stmt->errornumber = STMT_NO_MEMORY_ERROR; + stmt->errormsg = "Couldn't get primary keys for PGAPI_ForeignKeys result."; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(hpkey_stmt, SQL_DROP); + return SQL_ERROR; + } + + + /* Get to first primary key */ + pkey_ptr = trig_args; + for (i = 0; i < 5; i++) + pkey_ptr += strlen(pkey_ptr) + 1; + + for (k = 0; k < num_keys; k++) + { + /* Check that the key listed is the primary key */ + keyresult = PGAPI_Fetch(hpkey_stmt); + if (keyresult != SQL_SUCCESS) + { + num_keys = 0; + break; + } +#ifdef MULTIBYTE + pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced); +#else + pkey_text = pkey_ptr; +#endif /* MULTIBYTE */ + mylog("%s: pkey_ptr='%s', pkey='%s'\n", func, pkey_text, pkey); + if (strcmp(pkey_text, pkey)) + { + num_keys = 0; + break; + } +#ifdef MULTIBYTE + if (pkey_alloced) + free(pkey_text); +#endif /* MULTIBYTE */ + /* Get to next primary key */ + for (k = 0; k < 2; k++) + pkey_ptr += strlen(pkey_ptr) + 1; + + } + + /* Set to first fk column */ + fkey_ptr = trig_args; + for (k = 0; k < 4; k++) + fkey_ptr += strlen(fkey_ptr) + 1; + + /* Set update and delete actions for foreign keys */ + if (!strcmp(upd_rule, "RI_FKey_cascade_upd")) + upd_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_upd")) + upd_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_upd")) + upd_rule_type = SQL_SET_NULL; + + if (!strcmp(upd_rule, "RI_FKey_cascade_del")) + del_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_del")) + del_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_del")) + del_rule_type = SQL_SET_NULL; + +#if (ODBCVER >= 0x0300) + /* Set deferrability type */ + if (!strcmp(trig_initdeferred, "y")) + defer_type = SQL_INITIALLY_DEFERRED; + else if (!strcmp(trig_deferrable, "y")) + defer_type = SQL_INITIALLY_IMMEDIATE; + else + defer_type = SQL_NOT_DEFERRABLE; +#endif /* ODBCVER >= 0x0300 */ + + /* Get to first primary key */ + pkey_ptr = trig_args; + for (i = 0; i < 5; i++) + pkey_ptr += strlen(pkey_ptr) + 1; + + for (k = 0; k < num_keys; k++) + { + row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField)); + +#ifdef MULTIBYTE + pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced); + fkey_text = getClientColumnName(conn, fk_table, fkey_ptr, &fkey_alloced); +#else + pkey_text = pkey_ptr; + fkey_text = fkey_ptr; +#endif /* MULTIBYTE */ + mylog("%s: pk_table = '%s', pkey_ptr = '%s'\n", func, pkt_text, pkey_text); + set_tuplefield_null(&row->tuple[0]); + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], pkt_text); + set_tuplefield_string(&row->tuple[3], pkey_text); + + mylog("%s: fk_table_needed = '%s', fkey_ptr = '%s'\n", func, fk_table_needed, fkey_text); + set_tuplefield_null(&row->tuple[4]); + set_tuplefield_string(&row->tuple[5], ""); + set_tuplefield_string(&row->tuple[6], fk_table_needed); + set_tuplefield_string(&row->tuple[7], fkey_text); + + mylog("%s: upd_rule_type = '%i', del_rule_type = '%i'\n, trig_name = '%s'", func, upd_rule_type, del_rule_type, trig_args); + set_tuplefield_int2(&row->tuple[8], (Int2) (k + 1)); + set_tuplefield_int2(&row->tuple[9], (Int2) upd_rule_type); + set_tuplefield_int2(&row->tuple[10], (Int2) del_rule_type); + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + set_tuplefield_string(&row->tuple[13], trig_args); +#if (ODBCVER >= 0x0300) + set_tuplefield_int2(&row->tuple[14], defer_type); +#endif /* ODBCVER >= 0x0300 */ + + QR_add_tuple(stmt->result, row); +#ifdef MULTIBYTE + if (fkey_alloced) + free(fkey_text); + fkey_alloced = FALSE; + if (pkey_alloced) + free(pkey_text); + pkey_alloced = FALSE; +#endif /* MULTIBYTE */ + /* next primary/foreign key */ + for (i = 0; i < 2; i++) + { + fkey_ptr += strlen(fkey_ptr) + 1; + pkey_ptr += strlen(pkey_ptr) + 1; + } + } +#ifdef MULTIBYTE + if (pkt_alloced) + free(pkt_text); + pkt_alloced = FALSE; +#endif /* MULTIBYTE */ + + result = PGAPI_Fetch(htbl_stmt); + } + PGAPI_FreeStmt(hpkey_stmt, SQL_DROP); + } + + /* + * Case #1 -- Get the foreign keys in other tables that refer to the + * primary key in the specified table (pktab). i.e., Who points to + * me? + */ + else if (pk_table_needed[0] != '\0') + { + sprintf(tables_query, "SELECT pg_trigger.tgargs, " + " pg_trigger.tgnargs, " + " pg_trigger.tgdeferrable, " + " pg_trigger.tginitdeferred, " + " pg_proc.proname, " + " pg_proc_1.proname " + "FROM pg_class pg_class, " + " pg_class pg_class_1, " + " pg_class pg_class_2, " + " pg_proc pg_proc, " + " pg_proc pg_proc_1, " + " pg_trigger pg_trigger, " + " pg_trigger pg_trigger_1, " + " pg_trigger pg_trigger_2 " + "WHERE pg_trigger.tgconstrrelid = pg_class.oid " + " AND pg_trigger.tgrelid = pg_class_1.oid " + " AND pg_trigger_1.tgfoid = pg_proc_1.oid " + " AND pg_trigger_1.tgconstrrelid = pg_class_1.oid " + " AND pg_trigger_2.tgconstrrelid = pg_class_2.oid " + " AND pg_trigger_2.tgfoid = pg_proc.oid " + " AND pg_class_2.oid = pg_trigger.tgrelid " + " AND (" + " (pg_class.relname='%s') " + " AND (pg_proc.proname Like '%%upd') " + " AND (pg_proc_1.proname Like '%%del')" + " AND (pg_trigger_1.tgrelid = pg_trigger.tgconstrrelid) " + " AND (pg_trigger_2.tgrelid = pg_trigger.tgconstrrelid) " + " )", + pk_table_needed); + + result = PGAPI_ExecDirect(htbl_stmt, tables_query, strlen(tables_query)); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 1, SQL_C_BINARY, + trig_args, sizeof(trig_args), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 2, SQL_C_SHORT, + &trig_nargs, 0, NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 3, SQL_C_CHAR, + trig_deferrable, sizeof(trig_deferrable), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 4, SQL_C_CHAR, + trig_initdeferred, sizeof(trig_initdeferred), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 5, SQL_C_CHAR, + upd_rule, sizeof(upd_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_BindCol(htbl_stmt, 6, SQL_C_CHAR, + del_rule, sizeof(del_rule), NULL); + if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)) + { + stmt->errormsg = tbl_stmt->errormsg; + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + result = PGAPI_Fetch(htbl_stmt); + if (result == SQL_NO_DATA_FOUND) + return SQL_SUCCESS; + + if (result != SQL_SUCCESS) + { + stmt->errormsg = SC_create_errormsg(htbl_stmt); + stmt->errornumber = tbl_stmt->errornumber; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } + + while (result == SQL_SUCCESS) + { + /* Calculate the number of key parts */ + num_keys = (trig_nargs - 4) / 2;; + + /* Handle action (i.e., 'cascade', 'restrict', 'setnull') */ + if (!strcmp(upd_rule, "RI_FKey_cascade_upd")) + upd_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_upd")) + upd_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_upd")) + upd_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_upd")) + upd_rule_type = SQL_SET_NULL; + + if (!strcmp(upd_rule, "RI_FKey_cascade_del")) + del_rule_type = SQL_CASCADE; + else if (!strcmp(upd_rule, "RI_FKey_noaction_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_restrict_del")) + del_rule_type = SQL_NO_ACTION; + else if (!strcmp(upd_rule, "RI_FKey_setdefault_del")) + del_rule_type = SQL_SET_DEFAULT; + else if (!strcmp(upd_rule, "RI_FKey_setnull_del")) + del_rule_type = SQL_SET_NULL; + +#if (ODBCVER >= 0x0300) + /* Set deferrability type */ + if (!strcmp(trig_initdeferred, "y")) + defer_type = SQL_INITIALLY_DEFERRED; + else if (!strcmp(trig_deferrable, "y")) + defer_type = SQL_INITIALLY_IMMEDIATE; + else + defer_type = SQL_NOT_DEFERRABLE; +#endif /* ODBCVER >= 0x0300 */ + + mylog("Foreign Key Case#1: trig_nargs = %d, num_keys = %d\n", trig_nargs, num_keys); + + /* Get to first primary key */ + pkey_ptr = trig_args; + for (i = 0; i < 5; i++) + pkey_ptr += strlen(pkey_ptr) + 1; + + /* Get to first foreign table */ + fk_table = trig_args; + fk_table += strlen(fk_table) + 1; +#ifdef MULTIBYTE + pk_table = fk_table + strlen(fk_table) + 1; + fkt_text = getClientTableName(conn, fk_table, &fkt_alloced); +#else + fkt_text = fk_table; +#endif /* MULTIBYTE */ + + /* Get to first foreign key */ + fkey_ptr = trig_args; + for (k = 0; k < 4; k++) + fkey_ptr += strlen(fkey_ptr) + 1; + + for (k = 0; k < num_keys; k++) + { +#ifdef MULTIBYTE + pkey_text = getClientColumnName(conn, pk_table, pkey_ptr, &pkey_alloced); + fkey_text = getClientColumnName(conn, fk_table, fkey_ptr, &fkey_alloced); +#else + pkey_text = pkey_ptr; + fkey_text = fkey_ptr; +#endif /* MULTIBYTE */ + mylog("pkey_ptr = '%s', fk_table = '%s', fkey_ptr = '%s'\n", pkey_text, fkt_text, fkey_text); + + row = (TupleNode *) malloc(sizeof(TupleNode) + (result_cols - 1) *sizeof(TupleField)); + + mylog("pk_table_needed = '%s', pkey_ptr = '%s'\n", pk_table_needed, pkey_text); + set_tuplefield_null(&row->tuple[0]); + set_tuplefield_string(&row->tuple[1], ""); + set_tuplefield_string(&row->tuple[2], pk_table_needed); + set_tuplefield_string(&row->tuple[3], pkey_text); + + mylog("fk_table = '%s', fkey_ptr = '%s'\n", fkt_text, fkey_text); + set_tuplefield_null(&row->tuple[4]); + set_tuplefield_string(&row->tuple[5], ""); + set_tuplefield_string(&row->tuple[6], fkt_text); + set_tuplefield_string(&row->tuple[7], fkey_text); + + set_tuplefield_int2(&row->tuple[8], (Int2) (k + 1)); + + mylog("upd_rule = %d, del_rule= %d", upd_rule_type, del_rule_type); + set_nullfield_int2(&row->tuple[9], (Int2) upd_rule_type); + set_nullfield_int2(&row->tuple[10], (Int2) del_rule_type); + + set_tuplefield_null(&row->tuple[11]); + set_tuplefield_null(&row->tuple[12]); + + set_tuplefield_string(&row->tuple[13], trig_args); + +#if (ODBCVER >= 0x0300) + mylog("defer_type = '%s'", defer_type); + set_tuplefield_int2(&row->tuple[14], defer_type); +#endif /* ODBCVER >= 0x0300 */ + + QR_add_tuple(stmt->result, row); +#ifdef MULTIBYTE + if (pkey_alloced) + free(pkey_text); + pkey_alloced = FALSE; + if (fkey_alloced) + free(fkey_text); + fkey_alloced = FALSE; +#endif /* MULTIBYTE */ + + /* next primary/foreign key */ + for (j = 0; j < 2; j++) + { + pkey_ptr += strlen(pkey_ptr) + 1; + fkey_ptr += strlen(fkey_ptr) + 1; + } + } +#ifdef MULTIBYTE + if (fkt_alloced) + free(fkt_text); + fkt_alloced = FALSE; +#endif /* MULTIBYTE */ + result = PGAPI_Fetch(htbl_stmt); + } + } + else + { + stmt->errormsg = "No tables specified to PGAPI_ForeignKeys."; + stmt->errornumber = STMT_INTERNAL_ERROR; + SC_log_error(func, "", stmt); + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + return SQL_ERROR; + } +#ifdef MULTIBYTE + if (pkt_alloced) + free(pkt_text); + if (pkey_alloced) + free(pkey_text); + if (fkt_alloced) + free(fkt_text); + if (fkey_alloced) + free(fkey_text); +#endif /* MULTIBYTE */ + + PGAPI_FreeStmt(htbl_stmt, SQL_DROP); + + mylog("PGAPI_ForeignKeys(): EXIT, stmt=%u\n", stmt); + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_ProcedureColumns( + HSTMT hstmt, + UCHAR FAR * szProcQualifier, + SWORD cbProcQualifier, + UCHAR FAR * szProcOwner, + SWORD cbProcOwner, + UCHAR FAR * szProcName, + SWORD cbProcName, + UCHAR FAR * szColumnName, + SWORD cbColumnName) +{ + static char *func = "PGAPI_ProcedureColumns"; + + mylog("%s: entering...\n", func); + + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; +} + + +RETCODE SQL_API +PGAPI_Procedures( + HSTMT hstmt, + UCHAR FAR * szProcQualifier, + SWORD cbProcQualifier, + UCHAR FAR * szProcOwner, + SWORD cbProcOwner, + UCHAR FAR * szProcName, + SWORD cbProcName) +{ + static char *func = "PGAPI_Procedures"; + StatementClass *stmt = (StatementClass *) hstmt; + ConnectionClass *conn = SC_get_conn(stmt); + char proc_query[INFO_INQUIRY_LEN]; + QResultClass *res; + + mylog("%s: entering...\n", func); + + if (PG_VERSION_LT(conn, 6.5)) + { + stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; + stmt->errormsg = "Version is too old"; + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; + } + if (!SC_recycle_statement(stmt)) + return SQL_ERROR; + + /* + * The following seems the simplest implementation + */ + strcpy(proc_query, "select '' as " "PROCEDURE_CAT" ", '' as " "PROCEDURE_SCHEM" "," + " proname as " "PROCEDURE_NAME" ", '' as " "NUM_INPUT_PARAMS" "," + " '' as " "NUM_OUTPUT_PARAMS" ", '' as " "NUM_RESULT_SETS" "," + " '' as " "REMARKS" "," + " case when prorettype =0 then 1::int2 else 2::int2 end as " "PROCEDURE_TYPE" " from pg_proc"); + my_strcat(proc_query, " where proname like '%.*s'", szProcName, cbProcName); + + res = CC_send_query(conn, proc_query, NULL); + if (!res || QR_aborted(res)) + { + if (res) + QR_Destructor(res); + stmt->errornumber = STMT_EXEC_ERROR; + stmt->errormsg = "PGAPI_Procedures query error"; + return SQL_ERROR; + } + stmt->result = res; + + /* + * also, things need to think that this statement is finished so the + * results can be retrieved. + */ + stmt->status = STMT_FINISHED; + extend_bindings(stmt, 8); + /* set up the current tuple pointer for SQLFetch */ + stmt->currTuple = -1; + stmt->rowset_start = -1; + stmt->current_col = -1; + + return SQL_SUCCESS; +} + + +RETCODE SQL_API +PGAPI_TablePrivileges( + HSTMT hstmt, + UCHAR FAR * szTableQualifier, + SWORD cbTableQualifier, + UCHAR FAR * szTableOwner, + SWORD cbTableOwner, + UCHAR FAR * szTableName, + SWORD cbTableName) +{ + StatementClass *stmt = (StatementClass *) hstmt; + static char *func = "PGAPI_TablePrivileges"; + Int2 result_cols; + + mylog("%s: entering...\n", func); + + /* + * a statement is actually executed, so we'll have to do this + * ourselves. + */ + result_cols = 7; + extend_bindings(stmt, result_cols); + + /* set the field names */ + QR_set_num_fields(stmt->result, result_cols); + QR_set_field_info(stmt->result, 0, "TABLE_CAT", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 1, "TABLE_SCHEM", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 2, "TABLE_NAME", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 3, "GRANTOR", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 4, "GRANTEE", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 5, "PRIVILEGE", PG_TYPE_TEXT, MAX_INFO_STRING); + QR_set_field_info(stmt->result, 6, "IS_GRANTABLE", PG_TYPE_TEXT, MAX_INFO_STRING); + + SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); + return SQL_ERROR; +} |