C++ OTL doesn't see external database changes - c++

I have a C++ program that is using OTLv4 to connecto to a database. Everything is working fine. I can both insert data into the database and read data out of the database.
However, if I change data in the database from another program, then this isn't reflected in my C++ program. If I for example remove an entry with MySQL workbench, the C++ program will still see the entry. The data I see is the data as it appeared when the program first logged in to the database.
If I log off and log on each time I do a query then I will get the current value, but that does not seem very efficient. Similarly if I run a query from the C++ program that will modifiy the database then the program will start seeing the current values up until that point.
To me this feels like some sort of over-aggressive caching, but I don't know how that works in OTL, haven't seen any mention of caches other than possibly the stream pooling which I know nothing about.
I'm not doing anything fancy. OTL is compiled with these parameters:
#define OTL_ODBC // Compile OTL 4.0/ODBC
#define OTL_UNICODE // Compile OTL with Unicode
#define OTL_UNICODE_EXCEPTION_AND_RLOGON
#define OTL_UNICODE_STRING_TYPE std::wstring
// The following #define is required with MyODBC 3.51.11 and higher
#define OTL_ODBC_SELECT_STM_EXECUTE_BEFORE_DESCRIBE
The code looks something like this:
otl_connect::otl_initialize(1); // Multithreading
otl_connect database;
database.rlogon(...);
// Make queries with otl_stream and direct_exec
otl_stream stream(50, "select * from ...", database);
database.direct_exec("insert ... into ...", otl_exception::disabled);
database.logoff();
Is there something I have missed, some configuration I need to do? Turn off some sort of cache? Maybe i really do need to login and logoff each time?

I found out what is wrong:
Q. OTL: When I insert a new row into a table in MySQL, I can't SELECT it, what's going on?
If you're using a prepared SELECT statement in an otl_stream, and keep executing / reusing the statement to get new rows, you need to commit (call otl_connect::commit()) after the fetch sequence is exhausted each time. The commit call will let your MySQL Server know that your current read only transaction is finished, and the server can start a new transaction, which will make newly inserted rows to be visible to your SELECT statement. In other words, you need to commit your SELECT statements in order to able to see new rows.
From http://otl.sourceforge.net/otl3_faq.htm
So the problem was that whenever I make a SELECT statement I have to call otl_connect::commit(); or MySQL won't understand that the statement is finished.

Related

Using OTL library (c++) to INSERT without binding parameters

I am attempting to use OTL to do a basic insert into a sql server db. For this insert I don't need to bind any parameters.
otl_stream o_stream;
std::string query = "INSERT INTO common VALUES (1,2,3);";
o_stream.open(1, query.c_str(), db_);
o_stream.flush();
o_stream.close();
However, even after flushing and closing the otl_stream, the db is locked on that table (can't read via separateĀ application). I have to close my otl application to unlock the table.
If I parameterize the insert statement then everything works as it should (inserts successful, no table lock).
otl_stream o_stream;
std::string query = "INSERT INTO common VALUES (1,2,a:<int>);";
o_stream.open(1, query.c_str(), db_);
o_stream << 3;
That's not ideal, since ideally I'd like to avoid parameterizing/binding if it's not necessary.
Any suggestions?
EDIT:
Answer Below
From the author of the OTL library:
otl_streams are meant to be reused, that is, a INSERT statement stream
needs to have at least one bind variable to be "flushable". For cases
when there is 0 bind variables, there is this:
http://otl.sourceforge.net/otl3_const_sql.htm.
Missing from that link is the required db.commit() call. Without the commit call the table will be locked.
Solution for the example given in the question:
std::string query = "INSERT INTO common VALUES (1,2,3);";
db_.direct_exec(query.c_str());
db_.commit();

MFC CRecordset: Cannot flush newly added recordset to data source then continue updating it

I am working on refactoring some data-handling code in a MFC application that uses the CRecordset API (actually, a derived class from it, but the failures are coming out of CRecordset itself AFAICT) to talk to an ODBC data source backed by an Oracle database, but have encountered a sequence of operations that the CRecordset API, at least as of the version shipped with Visual Studio 2012 (which I know is old, but am hog-tied to for the time being) seems to be unable to perform.
In particular, in the following sequence of events, intended to flush changes to the record to the DB so that other queries performed during this sequence can see them:
CRecordset aRecordset(myDatabase);
aRecordset.Open(CRecordset::snapshot, "<some query that yields no records>"); // using CRecordset::dynaset doesn't change things
aRecordset.AddNew();
// set some values on aRecordset...
aRecordset.Update();
aRecordset.Requery(); // removing the Requery calls changes the failure mode
aRecordset.Edit(); // This call fails if the Requery is present
// perform query that needs to pick up on the values set on aRecordset above
// set some more values on aRecordset...
aRecordset.Update(); // This call fails if the Requery is not present
aRecordset.Requery();
aRecordset.Edit();
// perform query that needs to pick up on the values set on aRecordset above
// set yet more values on aRecordset...
aRecordset.Update();
aRecordset.Close();
I get two different failure modes, depending on whether the Requery calls are present or not.
With the Requery calls present, I get the following error from the first call to Edit in the sequence:
Error: Edit attempt failed - not on a record.
Operation failed, no current record.
while with them absent, I get a different error, this time from the second call to Update in the sequence, as follows:
Error: failure updating record.
Invalid cursor state
State:24000,Native:0,Origin:[Microsoft][ODBC Driver Manager]
Am I completely off my rocker in expecting CRecordset to be capable of flushing a newly added record to the database then going back to update the row further? Or is this a simple case of API operator error, and if so, what am I missing here? Is my Visual Studio/MFC too old for this fancy footwork?
Furthermore, it turns out that doing a .Requery() is not an option due to a requirement that I be able to .Open() a recordset with multiple rows, then do an .Edit()/.Update()/.Edit()/.Update() sequence on each row. Using the .Requery() in this case causes the cursor to be reset to the beginning with no good way to restore the cursor position, as the Oracle ODBC drivers do not support bookmarking across a requery.

C++ use Stored Procedure to return results

For the past couple years, I've been maintaining a large C++ application (v100) that utilizes some form of non-ADO database connections, but it works great.
During this time, getting a resultset from the database is quite simple. I instantiate the return class, with the database object, then Open a query.
CUpdates cUpdates(GetDatabase());
CString strQuery = "SELECT * FROM Updates";
cUpdates.Open(-1, strQuery);
Just that simple, cUpdates is filled with records.
NOW however, we want to execute a stored procedure, and return the results from it. But no matter what I try, even changing 'EXEC' to 'CALL', the call fails. Is there a similar simple method for executing a stored procedure, and returning the results, without having to totally rewrite how the application handles the database connection and returning of data?
strQuery.Format("EXEC dbo.[GetUpdates_ComputerName] '%s', %d, %d", GetWorkstationName(), m_bRetainUpdates, m_bScheduleUpdate);
cUpdates.Open(-1, strQuery); //FAILS ON EXEC
(I have tested the EXEC statement in SSMS, and it works fine)
We do also use another sql command, for strictly executing statements, but I see no way of returning data with it. Maybe there is a similar command I don't know of?
GetDatabase()->ExecuteSQL(strQuery);
note: for the record, I am C# developer (since 1.0 beta). My only experience in c++ has been learning on the fly over the past 2 years, occasionally maintaining a few of our massive systems.
It would seem that CRecordset cannot handle an EXEC statement inside of it. So we converted the new stored procedure to a Tabular Function, so I can use a SELECT instead... which works properly. (though I'd rather use the stored procedure)

C++ Poco ODBC Transactions - AutoCommit mode

I am currently attempting to use transactions in my C++ app, but I have a problem with the ODBC's auto commit mode.
I am using the POCO libaries to create a connection to a PostgreSQL database on the same machine. Currently, I can send data to this database as single statements, but I cannot get my head around how to use Poco's transaction libraries to be able to send this data more quickly.
As I have several thousand records to insert, and so continuing to use single insert statements is extrememly slow and inpractical - So I am trying to use Poco's transaction to speed this up a bit (a fair bit).
The error I am encountering is a theoretically a simple one - Poco is throwing the following error:
'Invalid access: Session is in auto commit mode.'
I understand, as a result of this, I should somehow set "auto commit" to false - as it only allows me to commit data to the database line by line, rather than as a single transaction.
The problem is how I set this.
Currently, I have a session created from Session.h, that looks alot like this:
session = new Poco::Data::Session(
"ODBC",
connection_data.str()
);
Where connection data is a simple stringstream with the login information, password, database, server and "Driver={PostgreSQL ANSI};" to tell ODBC to utilize PostgreSQL's driver.
I have tried just setting a property "autocommit" to false through the session's setFeature or setProperty settings, this, of course, was to no avail. (it was more of a ditch attempt at this point).
session->setFeature("AUTOCOMMIT", false);
Looking around, I saw a possible alternative method by creating a ODBC sessionImpl directly from ODBC/session/SessionImpl.h instead of using this generic method above, and then creating a new session object from this.
The benefits of this are that ODBC's sessionImpl has references to autocommit mode in the header, which would suggest it would be able to handle this:
void autoCommit(const std::string&, bool val);
/// Sets autocommit property for the session.
However, having not used sessionImpl before, I cannot garuntee if this will work or if can can get this to work with the limited documentation available.
I am using C++ 03 (Not 11), with Visual Studio 2015
Poco 1.7.5
Boost (Where needed)
Would any one know the correct way of setting this feature (above) or a alternative method to achieving this?
edit: Looking at the source of poco, at:
https://github.com/pocoproject/poco/blob/develop/Data/ODBC/src/SessionImpl.cpp#L153
The property seems be named autoCommit, and looking at
https://github.com/pocoproject/poco/blob/develop/Data/include/Poco/Data/AbstractSessionImpl.h#L120
the case of the property names seem to matter. So, does it help if you use session->setFeature("autoCommit", false);?
Cant you just call session->begin(); and session->end(); on the corresponding Session object?
What is returned by session->canTransact()?
According to the doc begin() will start a new transaction, the doc does not mention any property that needs to be set before or after.
See: https://pocoproject.org/docs/Poco.Data.Session.html
Also faced a similar issue.
First of all before begin() need:
m_ses.setFeature("autoCommit", false);
m_ses.begin();
And the second issue is that this feature stays "autoCommit" in false for all other sessions. So don't forget for the next session call
session.setFeature("autoCommit", true);

Addressing to temporary table created through CDatabase::ExecuteSQL

Consider following code and advise, why can I not address the temporary table created in the current session.
CDatabase cdb;
CString csConnectionString = "Dsn=prm2;Driver={INFORMIX 3.34 32 BIT};Host=10.XXX.XXX.XXX;Server=SRVNAME;Service=turbo;Protocol=olsoctcp;Database=DBNAME;Uid=user;Pwd=password";
cdb.OpenEx(csConnectionString, CDatabase::noOdbcDialog);
cdb.ExecuteSQL(CString("Set Isolation to Dirty Read"));
...
CString csStatement1 = "SELECT serno FROM TABLE1 into temp ttt_1;"
CString csStatement2 = "DROP TABLE ttt_1";
cdb.ExecuteSQL(csStatement1); // point1
cdb.ExecuteSQL(csStatement2); // point2
...
cdb.Close();
At point1 everything is fine. At point2 I have:
The specified table (ttt_1) is not in the database. State:S0002,Native:-206,Origin:[Informix][Informix ODBC Driver][Informix]
I tried to specify username as prefix (like user.ttt_1 or "user".ttt_1); I tried to create permanent table within respective statement in csStatement1 and every time it failed at point2. But when I tried to create same temporary table twice within csStatement1 I got the message that the temporary table already exists in session.
Please advise: what is wrong and how can I address created temporary tables.
it is all to do with ODBC autocommit mode. By default ODBC uses the option what is defined during the connection, and according to connectionstrings.com the default settings for Informix is commitretain=false.
You have two options: either set it via the connection string (commitretain=true) or (better option) via the ODBC. For a set of statements where you'd like to retain the temp table activate the manual commit mode via SqlSetConnectAttr, then execute a few statements and then call SqlEndTran. Please note, that in manual mode you do not need to call BEGIN TRANSACTION, as it will start automatically (behaviour similar to Oracle)
Please note that ODBC applications should not use Transact-SQL transaction statements such as BEGIN TRANSACTION, COMMIT TRANSACTION, or ROLLBACK TRANSACTION, but use the ODBC commands.