Commit ddc0b110687 for php.net

commit ddc0b1106878836a294cd05ab02f557ca97f8c7b
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Tue Sep 8 12:19:00 2026 -0400

    odbc: replace SQLError with SQLGetDiagRec (#23614)

    SQLError is the ODBC 2.0 diagnostic call, deprecated since ODBC 3.0, which
    ext/odbc already requires unconditionally via SQLAllocHandle,
    SQLColAttribute and SQLFetchScroll. Both callers now go through one helper
    that selects the statement, connection or environment handle the way
    SQLError did, so the reported SQLSTATE and message are unchanged.
    odbc_cursor() read its state and message buffers uninitialized when the
    diagnostic fetch failed; it now falls back to the same HY000 placeholder
    odbc_sql_error() uses.

    Closes GH-23614

diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index 4b05d8dc445..642307935c7 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -576,11 +576,31 @@ PHP_MINFO_FUNCTION(odbc)
 }
 /* }}} */

+static SQLRETURN odbc_diag_rec(ODBC_SQL_ENV_T henv, ODBC_SQL_CONN_T conn, ODBC_SQL_STMT_T stmt,
+		char *state, char *errormsg, SQLSMALLINT errormsg_size)
+{
+	SQLINTEGER native_error;
+	SQLSMALLINT handle_type;
+	SQLHANDLE handle;
+
+	if (stmt != SQL_NULL_HSTMT) {
+		handle_type = SQL_HANDLE_STMT;
+		handle = (SQLHANDLE) stmt;
+	} else if (conn != SQL_NULL_HDBC) {
+		handle_type = SQL_HANDLE_DBC;
+		handle = (SQLHANDLE) conn;
+	} else {
+		handle_type = SQL_HANDLE_ENV;
+		handle = (SQLHANDLE) henv;
+	}
+
+	return SQLGetDiagRec(handle_type, handle, 1, (SQLCHAR *) state, &native_error,
+			(SQLCHAR *) errormsg, errormsg_size, NULL);
+}
+
 /* {{{ odbc_sql_error */
 void odbc_sql_error(odbc_connection *conn_resource, ODBC_SQL_STMT_T stmt, const char *func, ...)
 {
-	SQLINTEGER	error;        /* Not used */
-	SQLSMALLINT	errormsgsize; /* Not used */
 	RETCODE rc;
 	ODBC_SQL_ENV_T henv;
 	ODBC_SQL_CONN_T conn;
@@ -593,12 +613,7 @@ void odbc_sql_error(odbc_connection *conn_resource, ODBC_SQL_STMT_T stmt, const
 		conn = SQL_NULL_HDBC;
 	}

-	/* This leads to an endless loop in many drivers!
-	 *
-	   while(henv != SQL_NULL_HENV){
-		do {
-	 */
-	rc = SQLError(henv, conn, stmt, (SQLCHAR *) ODBCG(laststate), &error, (SQLCHAR *) ODBCG(lasterrormsg), sizeof(ODBCG(lasterrormsg))-1, &errormsgsize);
+	rc = odbc_diag_rec(henv, conn, stmt, ODBCG(laststate), ODBCG(lasterrormsg), sizeof(ODBCG(lasterrormsg))-1);
 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
 		snprintf(ODBCG(laststate), sizeof(ODBCG(laststate)), "HY000");
 		snprintf(ODBCG(lasterrormsg), sizeof(ODBCG(lasterrormsg)), "Failed to fetch error message");
@@ -619,10 +634,6 @@ void odbc_sql_error(odbc_connection *conn_resource, ODBC_SQL_STMT_T stmt, const
 	} else {
 		php_error_docref(NULL, E_WARNING, "SQL error: %s, SQL state %s", ODBCG(lasterrormsg), ODBCG(laststate));
 	}
-	/*
-		} while (SQL_SUCCEEDED(rc));
-	}
-	*/
 }
 /* }}} */

@@ -1161,14 +1172,16 @@ PHP_FUNCTION(odbc_cursor)
 		cursorname = emalloc(max_len + 1);
 		rc = SQLGetCursorName(result->stmt, (SQLCHAR *) cursorname, (SQLSMALLINT)max_len, &len);
 		if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
-			char        state[6];     /* Not used */
-	 		SQLINTEGER  error;        /* Not used */
+			char        state[6];
 			char        errormsg[SQL_MAX_MESSAGE_LENGTH];
-			SQLSMALLINT errormsgsize; /* Not used */
+			SQLRETURN   diag_rc;

-			SQLError( result->conn_ptr->henv, result->conn_ptr->hdbc,
-						result->stmt, (SQLCHAR *) state, &error, (SQLCHAR *) errormsg,
-						sizeof(errormsg)-1, &errormsgsize);
+			diag_rc = odbc_diag_rec(result->conn_ptr->henv, result->conn_ptr->hdbc, result->stmt,
+					state, errormsg, sizeof(errormsg)-1);
+			if (diag_rc != SQL_SUCCESS && diag_rc != SQL_SUCCESS_WITH_INFO) {
+				snprintf(state, sizeof(state), "HY000");
+				snprintf(errormsg, sizeof(errormsg), "Failed to fetch error message");
+			}
 			if (!strncmp(state,"S1015",5)) {
 				snprintf(cursorname, max_len+1, "php_curs_" ZEND_ULONG_FMT, (zend_ulong)result->stmt);
 				if (SQLSetCursorName(result->stmt, (SQLCHAR *) cursorname, SQL_NTS) != SQL_SUCCESS) {