[Gambas-user] [Gambas Bug Tracker] Bug #1013: gb.db.odbc new GetRecordCount mechanism seem not work with firebird database
bugtracker at ...3416...
bugtracker at ...3416...
Fri Nov 11 13:39:17 CET 2016
http://gambaswiki.org/bugtracker/edit?object=BUG.1013&from=L21haW4-
Comment #12 by zxMarce:
Christian,
Well, it looks like my guess was correct. I patched the function so it now does as follows:
1- Remember the current RowIndex
2- Fetch the index of the first record (firstIndex)
3- Fetch the index of the last record (lastIndex)
4- Go back to the original RowINdex from step 1
5- Return (lastIndex - firstIndex + 1) as rowcount.
This change yielded correct SELECT row count for MSSQL:
gb.db.odbc: 0x84cc934: SELECT * FROM SampleTable ORDER BY Field1, Field2
gb.db.odbc.GetRecordCount: First recno=1
gb.db.odbc.GetRecordCount: Last recno=63
gb.db.odbc.GetRecordCount: Record count=63
gb.db.odbc: -> 63 rows
And also corrected the mishaps for FB, for both a SELECT and a SELECT COUNT(), and you can see that the first record is 0 and not 1 like in MSSQL:
gb.db.odbc: 0x84cc934: SELECT * FROM Colors
gb.db.odbc.GetRecordCount: First recno=0
gb.db.odbc.GetRecordCount: Last recno=7
gb.db.odbc.GetRecordCount: Record count=8
gb.db.odbc: -> 8 rows
gb.db.odbc: 0x84cc934: SELECT COUNT(*) FROM Colors
gb.db.odbc.GetRecordCount: First recno=0
gb.db.odbc.GetRecordCount: Last recno=0
gb.db.odbc.GetRecordCount: Record count=1
gb.db.odbc: -> 1 rows
The current problem I have now is that I get nice SEGFAULTs when I run an USE <someOtherDatabase> command on MSSQL:
gb.db.odbc: 0x9db15ac: USE Redemption
gb.db.odbc: SQLFetchScroll SQL_FETCH_FIRST
gb.db.odbc: H:1:0:[FreeTDS][SQL Server]Fetch type out of range
gb.db.odbc: -> -1 rows
[SEGFAULT!]
So, the function is returning -1 as expected on failure (error occurs when trying to fetch the first record from a recordless command), so the SEGFAULT problem seems to be elsewhere and not in GetRecordCount().
If you want to add the patch yourself and recompile and use the new component, you can grab the following code and give it a try:
/* zxMarce: This is one way -hope there's an easier one- to retrieve a rowset
* count for SELECT statements. Four steps (must have an scrollable cursor!):
* 1- Remember the current row.
* 2- Seek down to the last row in the rowset
* 3- Get the last row's index (recno)
* 4- Seek back to wherever we were at in step 1
* 20161110 zxMarce: Ok, it did not work that OK for Firebird; it looks like
* the FB driver returns one-less than the record count (record count seems to
* be zero-based), so we will instead do as follows, if we have a scrollable
* recordset:
* 1- Remember the current row.
* 2- Seek up to the first row in the rowset
* 3- Get the first row's index (firstRecNo)
* 4- Seek down to the last row in the rowset
* 5- Get the last row's index (lastRecNo)
* 6- Seek back to wherever we were at in step 1
* 7- Return (lastRecNo - firstRecNo + 1).
*/
int GetRecordCount(SQLHANDLE stmtHandle, SQLINTEGER cursorScrollable)
{
SQLRETURN retcode; //ODBC call return values
int formerRecIdx = 0; //Where we were when this all started.
SQLINTEGER myRecCnt = -1; //Default for when there's no cursor.
SQLINTEGER firstRecNo = 0; //20161111 holder for 1st recno.
SQLINTEGER lastRecNo = 0; //20161111 holder for last recno.
char mssg[128]; //Error reporting text.
//Make sure the statement has a cursor
if (!(stmtHandle && (cursorScrollable == SQL_TRUE)))
{
if (DB.IsDebug())
{
fprintf(stderr, "gb.db.odbc: Cannot do GetRecordCount()!\n");
}
return ((int) myRecCnt);
}
//Tell ODBC we won't be actually reading data (speeds process up).
//SQL_ATTR_RETRIEVE_DATA = [SQL_RD_ON] | SQL_RD_OFF
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_OFF, 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLSetStmtAttr SQL_ATTR_RETRIEVE_DATA",
stmtHandle,
SQL_HANDLE_STMT);
}
//Fetch current row's index so we can return to it when done.
retcode = SQLGetStmtAttr(stmtHandle, SQL_ATTR_ROW_NUMBER, &formerRecIdx, 0, 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLGetStmtAttr SQL_ATTR_ROW_NUMBER",
stmtHandle,
SQL_HANDLE_STMT);
}
//Try to get (back?) to the first record, abort if not possible.
retcode = SQLFetchScroll(stmtHandle, SQL_FETCH_FIRST, (SQLINTEGER) 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLFetchScroll SQL_FETCH_FIRST", stmtHandle, SQL_HANDLE_STMT);
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_ON, 0);
return ((int) myRecCnt);
} else {
//Fetch the first record's index
retcode = SQLGetStmtAttr(stmtHandle, SQL_ATTR_ROW_NUMBER, &firstRecNo, 0, 0);
if (SQL_SUCCEEDED(retcode))
{
//Inform first recno if in Debug mode and carry on
if (DB.IsDebug())
{
fprintf(stderr, "gb.db.odbc.GetRecordCount: First recno=%d\n", (int) firstRecNo);
}
} else {
//Could not fetch the first recno: Abort!
reportODBCError("SQLFetchScroll SQL_ATTR_ROW_NUMBER (first recno)", stmtHandle, SQL_HANDLE_STMT);
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_ON, 0);
return ((int) myRecCnt);
}
}
//Advance the cursor to the last record.
retcode = SQLFetchScroll(stmtHandle, SQL_FETCH_LAST, (SQLINTEGER) 0);
if (SQL_SUCCEEDED(retcode))
{
//Fetch the last record's index
retcode = SQLGetStmtAttr(stmtHandle, SQL_ATTR_ROW_NUMBER, &lastRecNo, 0, 0);
if (SQL_SUCCEEDED(retcode))
{
//Set ret value
if (DB.IsDebug())
{
fprintf(stderr, "gb.db.odbc.GetRecordCount: Last recno=%d\n", (int) lastRecNo);
}
} else {
reportODBCError("SQLGetStmtAttr SQL_ATTR_ROW_NUMBER (last recno)", stmtHandle, SQL_HANDLE_STMT);
}
//Return cursor to original row.
retcode = SQLFetchScroll(stmtHandle, SQL_FETCH_ABSOLUTE, (SQLINTEGER) formerRecIdx);
//Since we have set the "do not read data" statement attribute, this call (may) return
//code 100 (SQL_NO_DATA) but that's OK for our purposes of just counting rows.
if (!SQL_SUCCEEDED(retcode) && (retcode != SQL_NO_DATA))
{
snprintf(mssg, sizeof(mssg), "SQLFetchScroll SQL_FETCH_ABSOLUTE (code %d) (rec %d)",
(int)retcode, formerRecIdx);
reportODBCError(mssg, stmtHandle, SQL_HANDLE_STMT);
}
} else {
reportODBCError("SQLFetchScroll SQL_FETCH_LAST", stmtHandle, SQL_HANDLE_STMT);
}
//Tell ODBC we will be reading data now.
//SQL_ATTR_RETRIEVE_DATA = [SQL_RD_ON] | SQL_RD_OFF
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_ON, 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLSetStmtAttr SQL_ATTR_RETRIEVE_DATA", stmtHandle, SQL_HANDLE_STMT);
}
myRecCnt = (lastRecNo - firstRecNo + 1);
if (DB.IsDebug())
{
fprintf(stderr, "gb.db.odbc.GetRecordCount: Record count=%d\n", (int) myRecCnt);
}
return ((int) myRecCnt);
}
Hope that helps. Please give me some feedback as to usability/stability, as I'm afraid I still did not cover all bases.
zxMarce changed the state of the bug to: NeedsInfo.
More information about the User
mailing list