ביקשו ממני לבדוק האם אפשר להציל את המצב,, קיים קוד php ויש בעייה עם חיבור odbc לבסיס נתונים של SQL Server, החיבור היה נכשל כאשר הוא עובד תחת שרת ווב, אבל עובד בצורה תקינה אם מפעילים אותו משורת הפקודה.
כשהגעתי ראיתי שאני מקבל שגיאת Communication link failure עבור בסיס נתונים אחד, אבל לא עבור בסיס נתונים אחר תחת אותו השרת, כלומר מופע אחד של שרת בסיס נתונים, שיש עליו מספר בסיסי נתונים שונים. בדקתי אפילו תחת משתמש sa, ואם בסיס נתונים חדש שייצרתי חם מהתנור.
כשהגעתי ראיתי שאני מקבל שגיאת Communication link failure עבור בסיס נתונים אחד, אבל לא עבור בסיס נתונים אחר תחת אותו השרת, כלומר מופע אחד של שרת בסיס נתונים, שיש עליו מספר בסיסי נתונים שונים. בדקתי אפילו תחת משתמש sa, ואם בסיס נתונים חדש שייצרתי חם מהתנור.
בממשק המשתמש הפעלה של php -f testcase.php הכל עבד בסדר גמור, אבל הפעלה של אותו הדבר תחת apache התעופפה.
יצרתי קובץ שהוא מקרה בוחן :
<?php
$uid="user";
$pwd="secretpassword";
//Connect to a MS SQL servr over a standard ODBC connection
$database_connection = @odbc_connect(
"Driver={SQL Server};Server=MY_SERVER;database=MY_DB;MARS_Connection=yes;",
$uid,
$pwd);
//Check if our connection was valid , unfortunatly we can not use !(is_resource($database_connection) as it fails on 32 bit windows
if (0 == $database_connection or "" == $database_connection)
{
error_log(__FILE__ . "(" + __LINE___ +"): Error in odbc_connect " . odbc_errormsg());//this will write the last odbc stack error, as we are in a testcase, there's a high chance it would be our's and not smeone else.
}
else
{
//Bring back the infromation of the local server and database name , as seen by the server, we do so , to be sure we have a valid connection and that the DB was correctly set
$record_set = @odbc_exec($database_connection, "SELECT @@SERVERNAME,DB_NAME()");
$result = @odbc_result_all($record_set, "border=1");
@odbc_close($database_connection);
}
?>
$uid="user";
$pwd="secretpassword";
//Connect to a MS SQL servr over a standard ODBC connection
$database_connection = @odbc_connect(
"Driver={SQL Server};Server=MY_SERVER;database=MY_DB;MARS_Connection=yes;",
$uid,
$pwd);
//Check if our connection was valid , unfortunatly we can not use !(is_resource($database_connection) as it fails on 32 bit windows
if (0 == $database_connection or "" == $database_connection)
{
error_log(__FILE__ . "(" + __LINE___ +"): Error in odbc_connect " . odbc_errormsg());//this will write the last odbc stack error, as we are in a testcase, there's a high chance it would be our's and not smeone else.
}
else
{
//Bring back the infromation of the local server and database name , as seen by the server, we do so , to be sure we have a valid connection and that the DB was correctly set
$record_set = @odbc_exec($database_connection, "SELECT @@SERVERNAME,DB_NAME()");
$result = @odbc_result_all($record_set, "border=1");
@odbc_close($database_connection);
}
?>
מדובר על מקרה בוחן מאוד פשוט, אני בכוונה לא משתמש ב PDO כי אני רוצה לבדוק את השכבה הנמוכה ביותר, ולא באגים שקיימים ב PDO.
למי שהולך להגיד למה אני מראה שימוש בשכבת ה ODBC (ואחרי זה במערכת OR/M אחרת) ולא ישירות ב PDO או sql_srv כי זה מאפשר עבודה יותר אגנוסטית מבחינת המערכת, אני לא נכנס לנושא האם עדיף או לא להשתמש ב PDO מאשר בקריאות ישירות , זה עניין של טעם וריח, ל PDO ישנן יתתרונות מסויימים אבל יש לו גם חסרונות, אחד החסרונות המשעמעוייתים של PDO היה בגרסאות היישנות של php , בגריסה 5 ,הוא היה מתרסק על ימין ועל שמאל (אתם לא רוצים לדעת איך אני יודע את זה) , לאנשים נשאר הטעם המר שלו. אבל כשאנחנו מייצרים מקרה בוחן עדיף לנו שיהיה הקוד הפשוט ביותר אם הכי פחות רכיבים להכשל.
פתרון הבניים שאני מצאתי נכון לעכשיו הוא להוציא את הגדרת בסיס הנתונים מהDSN וביצוע פעולת use $MY_DB; בשלב ההצלחה. למה זה קורה ? אני ממש לא יודע. יכול להיות עניין של הגדרות חיבור מסויומות ברמת הPHP שהוא יורש מהכין שהוא. בדקתי הגדרות אבטחה והגדרות כלליות בסיסי נתונים השונים ולא מצאתי שום דבר ייחודי שמונע את ההתחברות. אבל מכיוון שזה עובד ב php לבד אבל לא דרך apache זה משהוא הקשור בין שניהם.
למרות שזה לא הגיוני שמדובר על מקרה connection_timeout מכיוון שזה עובד עם בסיס נתונים אחד ולא אחר באותו מופע השרת, הגדלתי את הערך בהגדרות php על המחשב,לצערי הנושא לא עזר. בלוגים של SQL Server לא הצלחתי לראות את הניסונות או הכשלנות הללו , שזה די הגיוני מכייון שמדובר על שגיאה שקוראת בזמן החיבור. כמובן שלפי הדוגמא אני לא משתמש בחיבור מוצפן (זה היה הדבר הראשון שביטלתי בשביל לבדוק) , וניסתי להשתמש גם ב DNS וגם בכתובת אינטרנט מפורשת.
למי שהולך להגיד למה אני מראה שימוש בשכבת ה ODBC (ואחרי זה במערכת OR/M אחרת) ולא ישירות ב PDO או sql_srv כי זה מאפשר עבודה יותר אגנוסטית מבחינת המערכת, אני לא נכנס לנושא האם עדיף או לא להשתמש ב PDO מאשר בקריאות ישירות , זה עניין של טעם וריח, ל PDO ישנן יתתרונות מסויימים אבל יש לו גם חסרונות, אחד החסרונות המשעמעוייתים של PDO היה בגרסאות היישנות של php , בגריסה 5 ,הוא היה מתרסק על ימין ועל שמאל (אתם לא רוצים לדעת איך אני יודע את זה) , לאנשים נשאר הטעם המר שלו. אבל כשאנחנו מייצרים מקרה בוחן עדיף לנו שיהיה הקוד הפשוט ביותר אם הכי פחות רכיבים להכשל.
פתרון הבניים שאני מצאתי נכון לעכשיו הוא להוציא את הגדרת בסיס הנתונים מהDSN וביצוע פעולת use $MY_DB; בשלב ההצלחה. למה זה קורה ? אני ממש לא יודע. יכול להיות עניין של הגדרות חיבור מסויומות ברמת הPHP שהוא יורש מהכין שהוא. בדקתי הגדרות אבטחה והגדרות כלליות בסיסי נתונים השונים ולא מצאתי שום דבר ייחודי שמונע את ההתחברות. אבל מכיוון שזה עובד ב php לבד אבל לא דרך apache זה משהוא הקשור בין שניהם.
למרות שזה לא הגיוני שמדובר על מקרה connection_timeout מכיוון שזה עובד עם בסיס נתונים אחד ולא אחר באותו מופע השרת, הגדלתי את הערך בהגדרות php על המחשב,לצערי הנושא לא עזר. בלוגים של SQL Server לא הצלחתי לראות את הניסונות או הכשלנות הללו , שזה די הגיוני מכייון שמדובר על שגיאה שקוראת בזמן החיבור. כמובן שלפי הדוגמא אני לא משתמש בחיבור מוצפן (זה היה הדבר הראשון שביטלתי בשביל לבדוק) , וניסתי להשתמש גם ב DNS וגם בכתובת אינטרנט מפורשת.
בנתיים אני חופר בקוד תחת ext/odbc/ , אני מנסה לראות איזה שהיא שגיאה שאני לזהות כתבתי די הרבה בשפת C ו C++ בתחומי ה ODBC בעברי, אז יש לי ניסיון. הקוד .. להפעתעתי המוחלטת הקוד כתוב טוב יותר ממה שציפתי מהם האמת,ההערות שלהם ? מה אני אגיד לכם , את הtraining שלי הם לא היו עוברים כלל, אין כמעט שום הערות בקוד כמו שצריך , אין הסבר מה כל פונקציה צריכה לעשות או דוגמאות לאיך אמורים להשתמש בהם.
זה למשל:
זה למשל:
2168 /* Persistent connections: two list-types le_pconn, le_conn and a plist
2169 * where hashed connection info is stored together with index pointer to
2170 * the actual link of type le_pconn in the list. Only persistent
2171 * connections get hashed up.
2172 */
2173 /* {{{ odbc_do_connect */
2174 void odbc_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
2175 {
אני מגדיר כמשהוא סביר בשביל הקוד של פהפ,
לעומת זאת זה :
לעומת זאת זה :
469 /* {{{ odbc_sql_error */
470 void odbc_sql_error(ODBC_SQL_ERROR_PARAMS)
471 {
אני מגדיר כהערות לא מי יודע מה טובות.
אחד הדברים שהיו הכי מוזרים בשבילי שלא מצאתי בכלל הגדרה של TIMEOUT כאטריביוט בקוד :
php/php8.2-8.2.18/ext/odbc$ grep TIMEOUT -rn *
php_odbc.c:112: if (res->stmt && !(PG(connection_status) & PHP_CONNECTION_TIMEOUT)) {
php_odbc.c:165: if (!(PG(connection_status) & PHP_CONNECTION_TIMEOUT)) {
php_odbc.c:198: if (!(PG(connection_status) & PHP_CONNECTION_TIMEOUT)) {
php_odbc.c:112: if (res->stmt && !(PG(connection_status) & PHP_CONNECTION_TIMEOUT)) {
php_odbc.c:165: if (!(PG(connection_status) & PHP_CONNECTION_TIMEOUT)) {
php_odbc.c:198: if (!(PG(connection_status) & PHP_CONNECTION_TIMEOUT)) {
וכמובן הגדרות החיבור
grep SQLSet -rn *
php_odbc.c:840: SQLSetStmtOption(result->stmt, SQL_CURSOR_TYPE, ODBCG(default_cursortype));
php_odbc.c:1114: if (SQLSetCursorName(result->stmt, (SQLCHAR *) cursorname, SQL_NTS) != SQL_SUCCESS) {
php_odbc.c:1115: odbc_sql_error(result->conn_ptr, result->stmt, "SQLSetCursorName");
php_odbc.c:1240: SQLSetStmtOption(result->stmt, SQL_CURSOR_TYPE, ODBCG(default_cursortype));
php_odbc.c:2069: SQLSetConnectOption((*conn)->hdbc, SQL_TRANSLATE_OPTION,
php_odbc.c:2081: rc = SQLSetConnectOption((*conn)->hdbc, SQL_ODBC_CURSORS, cur_opt);
php_odbc.c:2083: odbc_sql_error(*conn, SQL_NULL_HSTMT, "SQLSetConnectOption");
php_odbc.c:2575: rc = SQLSetConnectOption(conn->hdbc, SQL_AUTOCOMMIT, pv_onoff ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF);
php_odbc.c:2674: case 1: /* SQLSetConnectOption */
php_odbc.c:2683: rc = SQLSetConnectOption(conn->hdbc, (unsigned short) pv_opt, pv_val);
php_odbc.c:2689: case 2: /* SQLSetStmtOption */
php_odbc.c:2694: rc = SQLSetStmtOption(result->stmt, (unsigned short) pv_opt, pv_val);
php_odbc.c:2702: zend_argument_value_error(2, "must be 1 for SQLSetConnectOption(), or 2 for SQLSetStmtOption()");
php_odbc.c:840: SQLSetStmtOption(result->stmt, SQL_CURSOR_TYPE, ODBCG(default_cursortype));
php_odbc.c:1114: if (SQLSetCursorName(result->stmt, (SQLCHAR *) cursorname, SQL_NTS) != SQL_SUCCESS) {
php_odbc.c:1115: odbc_sql_error(result->conn_ptr, result->stmt, "SQLSetCursorName");
php_odbc.c:1240: SQLSetStmtOption(result->stmt, SQL_CURSOR_TYPE, ODBCG(default_cursortype));
php_odbc.c:2069: SQLSetConnectOption((*conn)->hdbc, SQL_TRANSLATE_OPTION,
php_odbc.c:2081: rc = SQLSetConnectOption((*conn)->hdbc, SQL_ODBC_CURSORS, cur_opt);
php_odbc.c:2083: odbc_sql_error(*conn, SQL_NULL_HSTMT, "SQLSetConnectOption");
php_odbc.c:2575: rc = SQLSetConnectOption(conn->hdbc, SQL_AUTOCOMMIT, pv_onoff ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF);
php_odbc.c:2674: case 1: /* SQLSetConnectOption */
php_odbc.c:2683: rc = SQLSetConnectOption(conn->hdbc, (unsigned short) pv_opt, pv_val);
php_odbc.c:2689: case 2: /* SQLSetStmtOption */
php_odbc.c:2694: rc = SQLSetStmtOption(result->stmt, (unsigned short) pv_opt, pv_val);
php_odbc.c:2702: zend_argument_value_error(2, "must be 1 for SQLSetConnectOption(), or 2 for SQLSetStmtOption()");
מה שאני ציפיתי לראות היו קריאות SqlSetStmtAttr עם מזהה SQL_QUERY_TIMEOUT (מדבר על כמה זמן בקשה תיקח) או לפחות שימוש ב SQLSetConnectAttr ולא שימוש ב SQLSetStmtOption שהיא רק עבור ODBC 1.0, כן PHP 8.0 נותנים תמיכה לאחור ב API לשנת 1992 , אני לא צוחק!
הייתי מצפה לראות לפחות משהוא כמו
SQLSetConnectAttr(V_OD_hdbc, SQL_LOGIN_TIMEOUT /*103*/, (SQLPOINTER *) timeout_value, 0); //we could use 113 for connection timeout too instead of 103 (SQL_LOGIN_TIMEOUT)(זה כמובן במידה והם היו בוחרים לעבוד בצורה הזאת, ושה timeout_value ייקרא מתוך php.ini)
עכשיו אני יודע שיגיע מישהוא ויגיד לי , אתה לא הסתקלת על odbc_setoption , אמסור לו , הרשו לי לומר, זו אחת ממקרי התיעוד הגרועים ביותר שראיתי, והשימוש בו , ... עוד יותר גרוע. יש לנו מושג שנקרה constants לא סתם,והכי חשוב אין אפילו קריאה אחת בלבד בכל הקוד! אז כן , אני יכול להפעיל אותו אחרי חיבור, אבל בהצלחה להבין איך אני עושה את זה *לפני* שהחיבור התבצע, זה למה הלכתי לקוד לראות מה ישפיע על הגדרות ה attribute בקוד המקוד של PHP, לצערי לא הצלחתי למצוא את הקוד שמפעיל את הגדרות ברירת המחדל כלל. חיפשתי אפילו את הערכים המספריים של הקבועים שמדברים על timeout בקוד המקור ולא הצלחתי לאתר אותם.
אני נגעתי בקוד ה php הזה כתמכנת בשפת C ולא כמתכנת ב php, זה לא הופך אותי למי שמבין בזה או אוהב את זה.
קישור לפרטי הרכיבים בDSN.
קישור למידע אפשרויות החיבור בשרתי SQL Server לדריברים של php.