diff --git a/connection.c b/connection.c index 8601cb7..8361691 100644 --- a/connection.c +++ b/connection.c @@ -288,7 +288,7 @@ CC_conninfo_init(ConnInfo *conninfo, UInt4 option) conninfo->bytea_as_longvarbinary = -1; conninfo->use_server_side_prepare = -1; conninfo->lower_case_identifier = -1; - conninfo->rollback_on_error = -1; + conninfo->transaction_error_value = TRANSACTION_ERROR_DEFAULT; conninfo->force_abbrev_connstr = -1; conninfo->bde_environment = -1; conninfo->fake_mss = -1; @@ -321,6 +321,7 @@ CC_copy_conninfo(ConnInfo *ci, const ConnInfo *sci) CORR_STRCPY(username); NAME_TO_NAME(ci->password, sci->password); CORR_STRCPY(protocol); + CORR_STRCPY(transaction_error); CORR_STRCPY(port); CORR_STRCPY(sslmode); CORR_STRCPY(onlyread); @@ -341,7 +342,7 @@ CC_copy_conninfo(ConnInfo *ci, const ConnInfo *sci) CORR_VALCPY(bytea_as_longvarbinary); CORR_VALCPY(use_server_side_prepare); CORR_VALCPY(lower_case_identifier); - CORR_VALCPY(rollback_on_error); + CORR_VALCPY(transaction_error_value); CORR_VALCPY(force_abbrev_connstr); CORR_VALCPY(bde_environment); CORR_VALCPY(fake_mss); @@ -2370,7 +2371,7 @@ int CC_get_max_idlen(ConnectionClass *self) { QResultClass *res; - res = CC_send_query(self, "show max_identifier_length", NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL); + res = CC_send_query(self, "show max_identifier_length", NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL); if (QR_command_maybe_successful(res)) len = self->max_identifier_length = atoi(res->command); QR_Destructor(res); @@ -2539,7 +2540,7 @@ static void CC_clear_cursors(ConnectionClass *self, BOOL on_abort) { snprintf(cmd, sizeof(cmd), "MOVE 0 in \"%s\"", QR_get_cursor(res)); CONNLOCK_RELEASE(self); - wres = CC_send_query(self, cmd, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL); + wres = CC_send_query(self, cmd, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL); QR_set_no_survival_check(res); if (QR_command_maybe_successful(wres)) QR_set_permanent(res); @@ -2733,7 +2734,7 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD BOOL ignore_abort_on_conn = ((flag & IGNORE_ABORT_ON_CONN) != 0), create_keyset = ((flag & CREATE_KEYSET) != 0), issue_begin = ((flag & GO_INTO_TRANSACTION) != 0 && !CC_is_in_trans(self)), - rollback_on_error, query_rollback, end_with_commit; + rollback_error_value, query_rollback, end_with_commit; const char *wq; char swallow, *ptr; @@ -2844,13 +2845,13 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD return res; } - rollback_on_error = (flag & ROLLBACK_ON_ERROR) != 0; + rollback_error_value = (flag & TRANSACTION_ERROR) != 0; end_with_commit = (flag & END_WITH_COMMIT) != 0; #define return DONT_CALL_RETURN_FROM_HERE??? consider_rollback = (issue_begin || (CC_is_in_trans(self) && !CC_is_in_error_trans(self)) || strnicmp(query, "begin", 5) == 0); - if (rollback_on_error) - rollback_on_error = consider_rollback; - query_rollback = (rollback_on_error && !end_with_commit && PG_VERSION_GE(self, 8.0)); + if (rollback_error_value) + rollback_error_value = consider_rollback; + query_rollback = (rollback_error_value && !end_with_commit && PG_VERSION_GE(self, 8.0)); if (!query_rollback && consider_rollback && !end_with_commit) { if (stmt) @@ -3341,7 +3342,7 @@ cleanup: CC_on_abort(self, CONN_DEAD); retres = NULL; } - if (rollback_on_error && CC_is_in_trans(self) && !discard_next_savepoint) + if (rollback_error_value && CC_is_in_trans(self) && !discard_next_savepoint) { char cmd[64]; @@ -3819,10 +3820,10 @@ CC_lookup_lo(ConnectionClass *self) if (PG_VERSION_GE(self, 7.4)) res = CC_send_query(self, "select oid, typbasetype from pg_type where typname = '" PG_TYPE_LO_NAME "'", - NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL); + NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL); else res = CC_send_query(self, "select oid, 0 from pg_type where typname='" PG_TYPE_LO_NAME "'", - NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL); + NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL); if (QR_command_maybe_successful(res) && QR_get_num_cached_tuples(res) > 0) { OID basetype; @@ -4009,7 +4010,7 @@ CC_get_current_schema(ConnectionClass *conn) { QResultClass *res; - if (res = CC_send_query(conn, "select current_schema()", NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL), QR_command_maybe_successful(res)) + if (res = CC_send_query(conn, "select current_schema()", NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL), QR_command_maybe_successful(res)) { if (QR_get_num_total_tuples(res) == 1) conn->current_schema = strdup(QR_get_value_backend_text(res, 0, 0)); @@ -4124,7 +4125,7 @@ int CC_discard_marked_objects(ConnectionClass *conn) snprintf(cmd, sizeof(cmd), "DEALLOCATE \"%s\"", pname + 1); else snprintf(cmd, sizeof(cmd), "CLOSE \"%s\"", pname + 1); - res = CC_send_query(conn, cmd, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL); + res = CC_send_query(conn, cmd, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL); QR_Destructor(res); free(conn->discardp[i]); conn->num_discardp--; @@ -4133,6 +4134,47 @@ int CC_discard_marked_objects(ConnectionClass *conn) return 1; } +/* + * Get text value of TransactionErrorBehavior. + */ +const char * +CC_get_transaction_error_txt(int value) +{ + switch (value) + { + case TRANSACTION_ERROR_NOP: + return TRANSACTION_ERROR_TXT_NOP; + case TRANSACTION_ERROR_TRANSACTION: + return TRANSACTION_ERROR_TXT_TRANSACTION; + case TRANSACTION_ERROR_STATEMENT: + return TRANSACTION_ERROR_TXT_STATEMENT; + case TRANSACTION_ERROR_DEFAULT: + default: + return TRANSACTION_ERROR_TXT_DEFAULT; + } +} + +/* + * Get integer value of TransactionErrorBehavior. + */ +int +CC_get_transaction_error_int(const char *value) +{ + /* Leave if there is nothing */ + if (value == NULL || value[0] == '\0') + return TRANSACTION_ERROR_DEFAULT; + + if (strcmp(TRANSACTION_ERROR_TXT_NOP, value) == 0) + return TRANSACTION_ERROR_NOP; + else if (strcmp(TRANSACTION_ERROR_TXT_TRANSACTION, value) == 0) + return TRANSACTION_ERROR_TRANSACTION; + else if (strcmp(TRANSACTION_ERROR_TXT_STATEMENT, value) == 0) + return TRANSACTION_ERROR_STATEMENT; + + /* Default case */ + return TRANSACTION_ERROR_DEFAULT; +} + #ifdef USE_LIBPQ static int LIBPQ_connect(ConnectionClass *self) diff --git a/connection.h b/connection.h index e92ff61..7e56b79 100644 --- a/connection.h +++ b/connection.h @@ -287,7 +287,16 @@ typedef struct char database[MEDIUM_REGISTRY_LEN]; char username[MEDIUM_REGISTRY_LEN]; pgNAME password; + + /* + * Protocol number, this can be used as well to define the behavior + * of rollback in case of error. + */ char protocol[SMALL_REGISTRY_LEN]; + + /* Value of TransactionErrorBehavior */ + char transaction_error[MEDIUM_REGISTRY_LEN]; + char port[SMALL_REGISTRY_LEN]; char sslmode[16]; char onlyread[SMALL_REGISTRY_LEN]; @@ -308,7 +317,16 @@ typedef struct signed char bytea_as_longvarbinary; signed char use_server_side_prepare; signed char lower_case_identifier; - signed char rollback_on_error; + + /* + * Control behavior of transaction rollback on error. + * + * transaction_error_value is used to store the value given by either + * Protocol (as an extension of the protocol number) or + * TransactionErrorBehavior as a connection parameter. + */ + signed char transaction_error_value; + signed char force_abbrev_connstr; signed char bde_environment; signed char fake_mss; @@ -377,6 +395,25 @@ typedef struct #define PG_VERSION_LE(conn, ver) (! PG_VERSION_GT(conn, ver)) #define PG_VERSION_LT(conn, ver) (! PG_VERSION_GE(conn, ver)) +/* + * Variables used to control the behavior of rollback on error + * Default defines a behavior that defers depending on the backend + * version. Those numerical values can be used as an extension of + * Protocol, and map text values below. + */ +#define TRANSACTION_ERROR_DEFAULT -1 +#define TRANSACTION_ERROR_NOP 0 +#define TRANSACTION_ERROR_TRANSACTION 1 +#define TRANSACTION_ERROR_STATEMENT 2 +/* + * Text versions of precedent parameters. They can be used by + * TransactionErrorBehavior. + */ +#define TRANSACTION_ERROR_TXT_DEFAULT "Default" +#define TRANSACTION_ERROR_TXT_NOP "Nop" +#define TRANSACTION_ERROR_TXT_TRANSACTION "Transaction" +#define TRANSACTION_ERROR_TXT_STATEMENT "Statement" + /* This is used to store cached table information in the connection */ struct col_info { @@ -585,6 +622,12 @@ void ProcessRollback(ConnectionClass *conn, BOOL undo, BOOL partial); const char *CC_get_current_schema(ConnectionClass *conn); int CC_mark_a_object_to_discard(ConnectionClass *conn, int type, const char *plan); int CC_discard_marked_objects(ConnectionClass *conn); +/* + * Procedures to get txt and numerical values of transaction rollback + * behavior. + */ +const char *CC_get_transaction_error_txt(int value); +int CC_get_transaction_error_int(const char *value); int handle_error_message(ConnectionClass *self, char *msgbuf, size_t buflen, char *sqlstate, const char *comment, QResultClass *res); @@ -606,7 +649,7 @@ enum { IGNORE_ABORT_ON_CONN = 1L /* not set the error result even when */ ,CREATE_KEYSET = (1L << 1) /* create keyset for updatable curosrs */ ,GO_INTO_TRANSACTION = (1L << 2) /* issue begin in advance */ - ,ROLLBACK_ON_ERROR = (1L << 3) /* rollback the query when an error occurs */ + ,TRANSACTION_ERROR = (1L << 3) /* rollback the query when an error occurs */ ,END_WITH_COMMIT = (1L << 4) /* the query ends with COMMMIT command */ ,IGNORE_ROUND_TRIP = (1L << 5) /* the commincation round trip time is considered ignorable */ }; diff --git a/dlg_specific.c b/dlg_specific.c index 8e051af..06be993 100644 --- a/dlg_specific.c +++ b/dlg_specific.c @@ -229,16 +229,11 @@ inolog("force_abbrev=%d abbrev=%d\n", ci->force_abbrev_connstr, abbrev); inolog("hlen=%d", hlen); if (!abbrev) { - char protocol_and[16]; - - if (ci->rollback_on_error >= 0) - snprintf(protocol_and, sizeof(protocol_and), "%s-%d", ci->protocol, ci->rollback_on_error); - else - strncpy_null(protocol_and, ci->protocol, sizeof(protocol_and)); olen = snprintf(&connect_string[hlen], nlen, ";" INI_SSLMODE "=%s;" INI_READONLY "=%s;" INI_PROTOCOL "=%s;" + INI_TRANSACTION_ERROR "=%s;" INI_FAKEOIDINDEX "=%s;" INI_SHOWOIDCOLUMN "=%s;" INI_ROWVERSIONING "=%s;" @@ -276,7 +271,8 @@ inolog("hlen=%d", hlen); #endif /* _HANDLE_ENLIST_IN_DTC_ */ ,ci->sslmode ,ci->onlyread - ,protocol_and + ,ci->protocol + ,CC_get_transaction_error_txt(ci->transaction_error_value) ,ci->fake_oid_index ,ci->show_oid_column ,ci->row_versioning @@ -408,22 +404,25 @@ inolog("hlen=%d", hlen); ci->int8_as, ci->drivers.extra_systable_prefixes, EFFECTIVE_BIT_COUNT, flag); - if (olen < nlen && (PROTOCOL_74(ci) || ci->rollback_on_error >= 0)) + if (olen < nlen && + (PROTOCOL_74(ci) || + ci->transaction_error_value != TRANSACTION_ERROR_DEFAULT)) { hlen = strlen(connect_string); nlen = MAX_CONNECT_STRING - hlen; /* * The PROTOCOL setting must be placed after CX flag * so that this option can override the CX setting. + * Complete it with TransactionErrorBehavior. */ - if (ci->rollback_on_error >= 0) + if (ci->transaction_error_value != TRANSACTION_ERROR_DEFAULT) olen = snprintf(&connect_string[hlen], nlen, ";" - ABBR_PROTOCOL "=%s-%d", - ci->protocol, ci->rollback_on_error); + ABBR_PROTOCOL "=%s;" ABBR_TRANSACTION_ERROR "=%s", + ci->protocol, + CC_get_transaction_error_txt(ci->transaction_error_value)); else olen = snprintf(&connect_string[hlen], nlen, ";" - ABBR_PROTOCOL "=%s", - ci->protocol); + ABBR_PROTOCOL "=%s", ci->protocol); } } if (olen < nlen) @@ -541,22 +540,33 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value) else if (stricmp(attribute, INI_PROTOCOL) == 0 || stricmp(attribute, ABBR_PROTOCOL) == 0) { char *ptr; - ptr = strchr(value, '-'); - if (ptr) + + /* + * Copy value controlling rollback on error if an extension of + * Protocol is found and if it is not set already by TransactionErrorBehavior. + */ + if (ptr && ci->transaction_error[0] == '\0') { if ('-' != *value) { *ptr = '\0'; strcpy(ci->protocol, value); } - ci->rollback_on_error = atoi(ptr + 1); - mylog("rollback_on_error=%d\n", ci->rollback_on_error); + ci->transaction_error_value = atoi(ptr + 1); + mylog("transaction_error_value=%d\n", ci->transaction_error_value); } else strcpy(ci->protocol, value); } + else if (stricmp(attribute, INI_TRANSACTION_ERROR) == 0 || + stricmp(attribute, ABBR_TRANSACTION_ERROR) == 0) + { + strcpy(ci->transaction_error, value); + ci->transaction_error_value = CC_get_transaction_error_int(value); + } + else if (stricmp(attribute, INI_SHOWOIDCOLUMN) == 0 || stricmp(attribute, ABBR_SHOWOIDCOLUMN) == 0) strcpy(ci->show_oid_column, value); @@ -663,7 +673,7 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value) else found = FALSE; - mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, ci->conn_settings, ci->disallow_premature); + mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',rollback_error='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, CC_get_transaction_error_txt(ci->transaction_error_value), ci->conn_settings, ci->disallow_premature); return found; } @@ -893,21 +903,45 @@ getDSNinfo(ConnInfo *ci, char overwrite) if (ci->show_system_tables[0] == '\0' || overwrite) SQLGetPrivateProfileString(DSN, INI_SHOWSYSTEMTABLES, "", ci->show_system_tables, sizeof(ci->show_system_tables), ODBC_INI); + /* + * Set up value for Protocol. Error on rollback + */ if (ci->protocol[0] == '\0' || overwrite) { char *ptr; SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI); - if (ptr = strchr(ci->protocol, '-'), NULL != ptr) + + /* + * Check if behavior of rollback on error is set as an extension + * of Protocol. Note that if this parameter has already been set + * by TransactionErrorBehavior, we simply ignore the value set here. + */ + if (ptr = strchr(ci->protocol, '-'), NULL != ptr && + ci->transaction_error[0] == '\0') { *ptr = '\0'; - if (overwrite || ci->rollback_on_error < 0) + if (overwrite || + ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT) { - ci->rollback_on_error = atoi(ptr + 1); - mylog("rollback_on_error=%d\n", ci->rollback_on_error); + ci->transaction_error_value = atoi(ptr + 1); + mylog("transaction_error_value=%d\n", ci->transaction_error_value); } } } + /* + * Set up value for TransactionErrorBehavior. This gets priority over Protocol. + */ + if (ci->transaction_error[0] == '\0' || overwrite) + { + if (ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT + || overwrite) + { + ci->transaction_error_value = CC_get_transaction_error_int(ci->transaction_error); + mylog("transaction_error_value=%d\n", ci->transaction_error_value); + } + } + if (NAME_IS_NULL(ci->conn_settings) || overwrite) { SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", encoded_item, sizeof(encoded_item), ODBC_INI); @@ -1205,12 +1239,14 @@ writeDSNinfo(const ConnInfo *ci) ci->show_system_tables, ODBC_INI); - if (ci->rollback_on_error >= 0) - sprintf(temp, "%s-%d", ci->protocol, ci->rollback_on_error); - else - strncpy_null(temp, ci->protocol, sizeof(temp)); SQLWritePrivateProfileString(DSN, INI_PROTOCOL, + ci->protocol, + ODBC_INI); + + sprintf(temp, "%s", CC_get_transaction_error_txt(ci->transaction_error_value)); + SQLWritePrivateProfileString(DSN, + INI_TRANSACTION_ERROR, temp, ODBC_INI); diff --git a/dlg_specific.h b/dlg_specific.h index c73bb64..d994423 100644 --- a/dlg_specific.h +++ b/dlg_specific.h @@ -75,6 +75,8 @@ extern "C" { #define ABBR_COMMLOG "B3" #define INI_PROTOCOL "Protocol" /* What protocol (6.2) */ #define ABBR_PROTOCOL "A1" +#define INI_TRANSACTION_ERROR "TransactionErrorBehavior" /* Rollback on error */ +#define ABBR_TRANSACTION_ERROR "AA" #define INI_OPTIMIZER "Optimizer" /* Use backend genetic * optimizer */ #define ABBR_OPTIMIZER "B4" diff --git a/dlg_wingui.c b/dlg_wingui.c index 072f542..394f7df 100644 --- a/dlg_wingui.c +++ b/dlg_wingui.c @@ -550,7 +550,7 @@ ds_options2Proc(HWND hdlg, CheckDlgButton(hdlg, DS_PG74, 1); /* How to issue Rollback */ - switch (ci->rollback_on_error) + switch (ci->transaction_error_value) { case 0: CheckDlgButton(hdlg, DS_NO_ROLLBACK, 1); @@ -639,14 +639,14 @@ ds_options2Proc(HWND hdlg, /* Issue rollback command on error */ if (IsDlgButtonChecked(hdlg, DS_NO_ROLLBACK)) - ci->rollback_on_error = 0; + ci->transaction_error_value = TRANSACTION_ERROR_NONE; else if (IsDlgButtonChecked(hdlg, DS_TRANSACTION_ROLLBACK)) - ci->rollback_on_error = 1; + ci->transaction_error_value = TRANSACTION_ERROR_TRANSACTION; else if (IsDlgButtonChecked(hdlg, DS_STATEMENT_ROLLBACK)) - ci->rollback_on_error = 2; + ci->transaction_error_value = TRANSACTION_ERROR_STATEMENT; else /* legacy */ - ci->rollback_on_error = 1; + ci->transaction_error_value = TRANSACTION_ERROR_DEFAULT; /* Int8 As */ if (IsDlgButtonChecked(hdlg, DS_INT8_AS_DEFAULT)) diff --git a/docs/config-opt.html b/docs/config-opt.html index 6673bc2..8e18086 100644 --- a/docs/config-opt.html +++ b/docs/config-opt.html @@ -146,6 +146,17 @@