aboutsummaryrefslogtreecommitdiffstats
path: root/lib/odbc/c_src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/odbc/c_src')
-rw-r--r--lib/odbc/c_src/Makefile.in6
-rw-r--r--lib/odbc/c_src/odbcserver.c180
-rw-r--r--lib/odbc/c_src/odbcserver.h6
3 files changed, 143 insertions, 49 deletions
diff --git a/lib/odbc/c_src/Makefile.in b/lib/odbc/c_src/Makefile.in
index 026da39e6f..6572d28ee8 100644
--- a/lib/odbc/c_src/Makefile.in
+++ b/lib/odbc/c_src/Makefile.in
@@ -108,17 +108,17 @@ docs:
ifdef UNIX_TARGET
$(UNIX_TARGET): $(OBJ_DIR)/odbcserver.o
- $(CC) $(CFLAGS) -o $@ $(OBJ_DIR)/odbcserver.o $(LDFLAGS) $(LIBS)
+ $(V_CC) $(CFLAGS) -o $@ $(OBJ_DIR)/odbcserver.o $(LDFLAGS) $(LIBS)
endif
ifdef WIN32_TARGET
$(WIN32_TARGET): $(OBJ_DIR)/odbcserver.o
- $(LD) $(LDFLAGS) -o $@ $(OBJ_DIR)/odbcserver.o $(ENTRY_OBJ) \
+ $(V_LD) $(LDFLAGS) -o $@ $(OBJ_DIR)/odbcserver.o $(ENTRY_OBJ) \
$(LIBS) $(ENTRY_LDFLAGS)
endif
$(OBJ_DIR)/odbcserver.o: odbcserver.c
- $(CC) $(CFLAGS) $(INCLUDES) $(TARGET_FLAGS) -o $@ -c odbcserver.c
+ $(V_CC) $(CFLAGS) $(INCLUDES) $(TARGET_FLAGS) -o $@ -c odbcserver.c
# ----------------------------------------------------
# Release Target
diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c
index 6d4460014f..a6b3de6e48 100644
--- a/lib/odbc/c_src/odbcserver.c
+++ b/lib/odbc/c_src/odbcserver.c
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 1999-2011. All Rights Reserved.
+ * Copyright Ericsson AB 1999-2013. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
@@ -104,6 +104,7 @@
#ifdef UNIX
#include <unistd.h>
+#include <netinet/tcp.h>
#endif
#if defined WIN32
@@ -152,7 +153,7 @@ static db_result_msg db_describe_table(byte *sql, db_state *state);
/* ------------- Encode/decode functions -------- ------------------------*/
static db_result_msg encode_empty_message(void);
-static db_result_msg encode_error_message(char *reason);
+static db_result_msg encode_error_message(char *reason, char *errCode, SQLINTEGER nativeError);
static db_result_msg encode_atom_message(char *atom);
static db_result_msg encode_result(db_state *state);
static db_result_msg encode_result_set(SQLSMALLINT num_of_columns,
@@ -201,6 +202,7 @@ static byte *receive_msg(int socket);
static Boolean receive_msg_part(int socket, byte * buffer, size_t msg_len);
static Boolean send_msg_part(int socket, byte * buffer, size_t msg_len);
static void close_socket(int socket);
+static void tcp_nodelay(int sock);
#endif
static void clean_socket_lib(void);
@@ -228,7 +230,7 @@ static void init_param_statement(int cols,
static void map_dec_num_2_c_column(col_type *type, int precision,
int scale);
-static db_result_msg map_sql_2_c_column(db_column* column);
+static db_result_msg map_sql_2_c_column(db_column* column, db_state *state);
static param_array * bind_parameter_arrays(byte *buffer, int *index,
@@ -244,7 +246,7 @@ static db_result_msg retrive_scrollable_cursor_support_info(db_state
static int num_out_params(int num_of_params, param_array* params);
/* ------------- Error handling functions --------------------------------*/
-static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle);
+static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle, Boolean extendedErrors);
/* ------------- Boolean functions ---------------------------------------*/
@@ -347,7 +349,7 @@ DWORD WINAPI database_handler(const char *port)
byte *request_buffer = NULL;
db_state state =
{NULL, NULL, NULL, NULL, 0, {NULL, 0, 0},
- FALSE, FALSE, FALSE, FALSE, FALSE, FALSE};
+ FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE};
byte request_id;
#ifdef WIN32
SOCKET socket;
@@ -429,6 +431,20 @@ static db_result_msg handle_db_request(byte *reqstring, db_state *state)
<connStrIn>, returns a message indicating the outcome. */
static db_result_msg db_connect(byte *args, db_state *state)
{
+ /*
+ * Danil Onishchenko aka RubberCthulhu, [email protected]. 2013.01.09.
+ * It's a fix for Oracle ODBC driver for Linux.
+ * The issue: Oracle ODBC driver for Linux ignores setup autocommit mode
+ * during driver initialization before a connection to database has been
+ * established.
+ * Solution: set autocommit mode after a connection to database has been
+ * established.
+ *
+ * BEGIN
+ */
+ SQLLEN auto_commit_mode;
+ /* END */
+
SQLCHAR connStrOut[MAX_CONN_STR_OUT + 1] = {0};
SQLRETURN result;
SQLSMALLINT stringlength2ptr = 0, connlen;
@@ -436,14 +452,16 @@ static db_result_msg db_connect(byte *args, db_state *state)
diagnos diagnos;
byte *connStrIn;
int erl_auto_commit_mode, erl_trace_driver,
- use_srollable_cursors, tuple_row_state, binary_strings;
+ use_srollable_cursors, tuple_row_state, binary_strings,
+ extended_errors;
erl_auto_commit_mode = args[0];
erl_trace_driver = args[1];
use_srollable_cursors = args[2];
tuple_row_state = args[3];
binary_strings = args[4];
- connStrIn = args + 5 * sizeof(byte);
+ extended_errors = args[5];
+ connStrIn = args + 6 * sizeof(byte);
if(tuple_row_state == ON) {
tuple_row(state) = TRUE;
@@ -463,6 +481,12 @@ static db_result_msg db_connect(byte *args, db_state *state)
use_srollable_cursors(state) = FALSE;
}
+ if(extended_errors == ON) {
+ extended_errors(state) = TRUE;
+ } else {
+ extended_errors(state) = FALSE;
+ }
+
init_driver(erl_auto_commit_mode, erl_trace_driver, state);
connlen = (SQLSMALLINT)strlen((const char*)connStrIn);
@@ -473,10 +497,10 @@ static db_result_msg db_connect(byte *args, db_state *state)
&stringlength2ptr, SQL_DRIVER_NOPROMPT);
if (!sql_success(result)) {
- diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state));
+ diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
strcat((char *)diagnos.error_msg,
" Connection to database failed.");
- msg = encode_error_message(diagnos.error_msg);
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError );
if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC,
connection_handle(state))))
@@ -488,6 +512,42 @@ static db_result_msg db_connect(byte *args, db_state *state)
return msg;
}
+ /*
+ * Danil Onishchenko aka RubberCthulhu, [email protected]. 2013.01.09.
+ * It's a fix for Oracle ODBC driver for Linux.
+ * The issue: Oracle ODBC driver for Linux ignores setup autocommit mode
+ * during driver initialization before a connection to database has been
+ * established.
+ * Solution: set autocommit mode after a connection to database has been
+ * established.
+ *
+ * BEGIN
+ */
+ if(erl_auto_commit_mode == ON) {
+ auto_commit_mode = SQL_AUTOCOMMIT_ON;
+ } else {
+ auto_commit_mode = SQL_AUTOCOMMIT_OFF;
+ }
+
+ if(!sql_success(SQLSetConnectAttr(connection_handle(state),
+ SQL_ATTR_AUTOCOMMIT,
+ (SQLPOINTER)auto_commit_mode, 0))) {
+ diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
+ strcat((char *)diagnos.error_msg, " Set autocommit mode failed.");
+
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+
+ if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC,
+ connection_handle(state))))
+ DO_EXIT(EXIT_FREE);
+ if(!sql_success(SQLFreeHandle(SQL_HANDLE_ENV,
+ environment_handle(state))))
+ DO_EXIT(EXIT_FREE);
+
+ return msg;
+ }
+ /* END */
+
msg = retrive_scrollable_cursor_support_info(state);
return msg;
@@ -507,8 +567,8 @@ static db_result_msg db_close_connection(db_state *state)
result = SQLDisconnect(connection_handle(state));
if (!sql_success(result)) {
- diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state));
- return encode_error_message(diagnos.error_msg);
+ diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
+ return encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
}
if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC,
@@ -534,8 +594,8 @@ static db_result_msg db_end_tran(byte compleationtype, db_state *state)
(SQLSMALLINT)compleationtype);
if (!sql_success(result)) {
- diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state));
- return encode_error_message(diagnos.error_msg);
+ diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
+ return encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
} else {
return encode_atom_message("ok");
}
@@ -570,7 +630,7 @@ static db_result_msg db_query(byte *sql, db_state *state)
/* SQL_SUCCESS_WITH_INFO at this point may indicate an error in user input. */
if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) {
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
if(strcmp((char *)diagnos.sqlState, INFO) == 0) {
is_error[0] = 0;
strncat((char *)is_error, (char *)diagnos.error_msg,
@@ -581,12 +641,12 @@ static db_result_msg db_query(byte *sql, db_state *state)
it as we want a nice and clean Erlang API */
if((strcmp((char *)is_error, "error") == 0))
{
- msg = encode_error_message((char *)diagnos.error_msg);
+ msg = encode_error_message((char *)diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
clean_state(state);
return msg;
}
} else {
- msg = encode_error_message((char *)diagnos.error_msg);
+ msg = encode_error_message((char *)diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -659,9 +719,9 @@ static db_result_msg db_select_count(byte *sql, db_state *state)
}
if(!sql_success(SQLExecDirect(statement_handle(state), sql, SQL_NTS))) {
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
clean_state(state);
- return encode_error_message(diagnos.error_msg);
+ return encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
}
if(!sql_success(SQLNumResultCols(statement_handle(state),
@@ -801,13 +861,13 @@ static db_result_msg db_param_query(byte *buffer, db_state *state)
result = SQLExecDirect(statement_handle(state), sql, SQL_NTS);
if (!sql_success(result) || result == SQL_NO_DATA) {
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
}
/* SQL_NO_DATA and SQLSTATE 00000 indicate success for
updates/deletes that affect no rows */
if(!sql_success(result) &&
!(result == SQL_NO_DATA && !strcmp((char *)diagnos.sqlState, INFO))) {
- msg = encode_error_message(diagnos.error_msg);
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
} else {
for (i = 0; i < param_status.params_processed; i++) {
switch (param_status.param_status_array[i]) {
@@ -821,8 +881,8 @@ static db_result_msg db_param_query(byte *buffer, db_state *state)
break;
default:
diagnos =
- get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
- msg = encode_error_message(diagnos.error_msg);
+ get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
i = param_status.params_processed;
break;
}
@@ -891,16 +951,16 @@ static db_result_msg db_describe_table(byte *sql, db_state *state)
DO_EXIT(EXIT_ALLOC);
if (!sql_success(SQLPrepare(statement_handle(state), sql, SQL_NTS))){
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
- msg = encode_error_message(diagnos.error_msg);
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
clean_state(state);
return msg;
}
if(!sql_success(SQLNumResultCols(statement_handle(state),
&num_of_columns))) {
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
- msg = encode_error_message(diagnos.error_msg);
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -949,7 +1009,7 @@ static db_result_msg encode_empty_message(void)
}
/* Description: Encode an error-message to send back to erlang*/
-static db_result_msg encode_error_message(char *reason)
+static db_result_msg encode_error_message(char *reason, char *errCode, SQLINTEGER nativeError )
{
int index;
db_result_msg msg;
@@ -958,6 +1018,12 @@ static db_result_msg encode_error_message(char *reason)
ei_encode_version(NULL, &index);
ei_encode_tuple_header(NULL, &index, 2);
ei_encode_atom(NULL, &index, "error");
+ if (errCode)
+ {
+ ei_encode_tuple_header(NULL, &index, 3);
+ ei_encode_string(NULL, &index, errCode);
+ ei_encode_long(NULL, &index, nativeError);
+ }
ei_encode_string(NULL, &index, reason);
msg.length = index;
@@ -968,6 +1034,12 @@ static db_result_msg encode_error_message(char *reason)
ei_encode_version((char *)msg.buffer, &index);
ei_encode_tuple_header((char *)msg.buffer, &index, 2);
ei_encode_atom((char *)msg.buffer, &index, "error");
+ if (errCode)
+ {
+ ei_encode_tuple_header((char *)msg.buffer, &index, 3);
+ ei_encode_string((char *)msg.buffer, &index, errCode);
+ ei_encode_long((char *)msg.buffer, &index, nativeError);
+ }
ei_encode_string((char *)msg.buffer, &index, reason);
return msg;
@@ -1011,8 +1083,8 @@ static db_result_msg encode_result(db_state *state)
if(!sql_success(SQLNumResultCols(statement_handle(state),
&num_of_columns))) {
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
- msg = encode_error_message(diagnos.error_msg);
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -1028,8 +1100,8 @@ static db_result_msg encode_result(db_state *state)
}
if(!sql_success(SQLRowCount(statement_handle(state), &RowCountPtr))) {
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
- msg = encode_error_message(diagnos.error_msg);
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -1237,7 +1309,7 @@ static db_result_msg encode_column_name_list(SQLSMALLINT num_of_columns,
(columns(state)[i]).type.sql = sql_type;
(columns(state)[i]).type.col_size = size;
- msg = map_sql_2_c_column(&columns(state)[i]);
+ msg = map_sql_2_c_column(&columns(state)[i], state);
if (msg.length > 0) {
return msg; /* An error has occurred */
} else {
@@ -1782,6 +1854,10 @@ static int connect_to_erlang(const char *port)
sin6.sin6_addr = in6addr_loopback;
if (connect(sock, (struct sockaddr*)&sin6, sizeof(sin6)) == 0) {
+ /* Enable TCP_NODELAY to disable Nagel's socket algorithm. (Removes ~40ms delay on Redhat ES 6). */
+ #ifdef UNIX
+ tcp_nodelay(sock);
+ #endif
return sock;
}
close_socket(sock);
@@ -1797,9 +1873,24 @@ static int connect_to_erlang(const char *port)
close_socket(sock);
DO_EXIT(EXIT_SOCKET_CONNECT);
}
+
+ /* Enable TCP_NODELAY to disable Nagel's socket algorithm. (Removes ~40ms delay on Redhat ES 6). */
+ #ifdef UNIX
+ tcp_nodelay(sock);
+ #endif
return sock;
}
+#ifdef UNIX
+static void tcp_nodelay(int sock)
+{
+ int flag = 1;
+ int result = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
+ if (result < 0) {
+ DO_EXIT(EXIT_SOCKET_CONNECT);
+ }
+}
+#endif
#ifdef WIN32
static void close_socket(SOCKET socket)
{
@@ -2325,7 +2416,7 @@ static void map_dec_num_2_c_column(col_type *type, int precision, int scale)
/* Description: Transform SQL columntype to C columntype. Returns a dummy
db_result_msg with length 0 on success and an errormessage otherwise.*/
-static db_result_msg map_sql_2_c_column(db_column* column)
+static db_result_msg map_sql_2_c_column(db_column* column, db_state *state)
{
db_result_msg msg;
@@ -2394,10 +2485,10 @@ static db_result_msg map_sql_2_c_column(db_column* column)
column -> type.strlen_or_indptr = (SQLLEN)NULL;
break;
case SQL_UNKNOWN_TYPE:
- msg = encode_error_message("Unknown column type");
+ msg = encode_error_message("Unknown column type", extended_error(state, ""), 0);
break;
default:
- msg = encode_error_message("Column type not supported");
+ msg = encode_error_message("Column type not supported", extended_error(state, ""), 0);
break;
}
return msg;
@@ -2506,7 +2597,7 @@ static db_column retrive_binary_data(db_column column, int column_nr,
while (result == SQL_SUCCESS_WITH_INFO) {
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
if(strcmp((char *)diagnos.sqlState, TRUNCATED) == 0) {
outputlen = column.type.len - 1;
@@ -2593,10 +2684,10 @@ static db_result_msg more_result_sets(db_state *state)
/* As we found an error we do not care about any potential more result
sets */
exists_more_result_sets(state) = FALSE;
- diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
strcat((char *)diagnos.error_msg,
"Failed to create on of the result sets");
- msg = encode_error_message(diagnos.error_msg);
+ msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
return msg;
}
}
@@ -2613,7 +2704,7 @@ static Boolean sql_success(SQLRETURN result)
diagnostic records scaning for error messages and the sqlstate.
If this function is called when no error has ocurred only the sqlState
field may be referenced.*/
-static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle)
+static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle, Boolean extendedErrors)
{
diagnos diagnos;
SQLINTEGER nativeError;
@@ -2637,17 +2728,16 @@ static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle)
result = SQLGetDiagRec(handleType, handle, record_nr, current_sql_state,
&nativeError, current_errmsg_pos,
(SQLSMALLINT)errmsg_buffer_size, &errmsg_size);
- if(result != SQL_SUCCESS && result != SQL_NO_DATA) {
-
-
- break;
- } else {
+ if(result == SQL_SUCCESS) {
/* update the sqlstate in the diagnos record, because the SQLGetDiagRec
call succeeded */
memcpy(diagnos.sqlState, current_sql_state, SQL_STATE_SIZE);
+ diagnos.nativeError = nativeError;
errmsg_buffer_size = errmsg_buffer_size - errmsg_size;
acc_errmsg_size = acc_errmsg_size + errmsg_size;
current_errmsg_pos = current_errmsg_pos + errmsg_size;
+ } else {
+ break;
}
}
@@ -2655,7 +2745,7 @@ static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle)
strcat((char *)diagnos.error_msg,
"No SQL-driver information available.");
}
- else {
+ else if (!extendedErrors){
strcat(strcat((char *)diagnos.error_msg, " SQLSTATE IS: "),
(char *)diagnos.sqlState);
}
diff --git a/lib/odbc/c_src/odbcserver.h b/lib/odbc/c_src/odbcserver.h
index a76cedf1af..916a7cb31d 100644
--- a/lib/odbc/c_src/odbcserver.h
+++ b/lib/odbc/c_src/odbcserver.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2002-2011. All Rights Reserved.
+ * Copyright Ericsson AB 2002-2013. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
@@ -145,6 +145,7 @@ typedef struct {
typedef struct {
SQLCHAR sqlState[SQL_STATE_SIZE];
+ SQLINTEGER nativeError;
byte error_msg[MAX_ERR_MSG];
} diagnos;
@@ -179,6 +180,7 @@ typedef struct {
Boolean exists_more_result_sets;
Boolean param_query;
Boolean out_params;
+ Boolean extended_errors;
} db_state;
typedef enum {
@@ -198,3 +200,5 @@ typedef enum {
#define exists_more_result_sets(db_state) (db_state -> exists_more_result_sets)
#define param_query(db_state) (db_state -> param_query)
#define out_params(db_state) (db_state -> out_params)
+#define extended_errors(db_state) (db_state -> extended_errors)
+#define extended_error(db_state, errorcode) ( extended_errors(state) ? errorcode : NULL )