/*
* This script demonstrates the use of the threaded ODBC interface to insert
* data into a database based on a timer.
*/
require ("Application");
require ("ODBCThreadSupport");
class ODBCThreadDemo Application
{
DSN = "test"; // The DSN name to use for the database connection
username = "test"; // The user name for connecting to the database
password = "test"; // The password for connecting to the database
tablename = "andrew5"; // The name of the database table
cachefile = "c:/tmp/testcache.txt"; // Base name for the disk cache file
tableclass;
thread;
}
/* This method will be called every time the connection is established to the database.
* If there is something we only want to perform on the first connection, we can test
* is_first_connect to perform the code only once.
*/
method ODBCThreadDemo.onConnect()
{
princ ("Connection succeeded\n");
if (.thread.is_first_connect)
{
// Start the sequence defined by the AddInitStage calls in the constructor
.thread.BeginAsyncInit();
}
}
/* If we get a connection attempt failure, or the connection fails after having been
* connected, this method is called.
*/
method ODBCThreadDemo.onConnectFail()
{
princ ("Connection closed: ", SQLResult.Description, "\n");
}
/* Map the table in the set of table definitions that matches the name in .tablename
* into a Gamma class. This lets us easily convert between class instances and rows
* in the table.
*/
method ODBCThreadDemo.mapTable(name, tabledefinitions)
{
.tableclass = .thread.ClassFromTable(name, tabledefinitions);
}
/* Set up the timer or event handler functions to write to the table. */
method ODBCThreadDemo.startLogging()
{
.TimerEvery(1, `(@self).writeData(#$DataSim:Sine));
.OnChange(#$DataSim:Square, `(@self).writeData(this));
}
method ODBCThreadDemo.writeData(pointsymbol)
{
local row = new (.tableclass);
local pttime, ptltime;
local timestring;
// Generate a timestamp in database-independent format to the millisecond.
// Many databases strip the milliseconds from a timestamp, but it is harmless
// to provide them in case the database can store them.
pttime = WindowsTimeToUnixTime(PointMetadata(pointsymbol).timestamp);
ptltime = localtime(pttime);
timestring = format("{ts '%04d-%02d-%02d %02d:%02d:%02d.%03d'}",
ptltime.year+1900, ptltime.mon+1, ptltime.mday, ptltime.hour, ptltime.min, ptltime.sec,
(pttime % 1) * 1000);
// Fill the row. Since we mapped the table into a Gamma class, we can access
// the columns in the row as member variables of the mapped class.
row.ptname = string(pointsymbol);
row.ptvalue = eval(pointsymbol);
row.pttime = timestring;
// Perform the insertion. In this case we are providing no callback on completion.
.thread.Insert(row, nil);
}
/* Print some statistics for the database thread. */
method ODBCThreadDemo.printStats ()
{
// NCommands = the number of commands successfully transmitted to the database
// thread or stored in cache for later transmission
// NRejected = the number of commands that could not be transmitted or stored
// NStoredPrimary = the number of commands stored in level 1 cache
// NStoredSecondary = the number of commands stored in level 2 cache
// NForwarded = the number of commands executed from level 1 or level 2 cache
// NForwardFail = the number of commands that failed while forwarding from cache
princ ("Commands sent: ", .thread.NCommands, " (", .thread.NRejected, " rejected) Stored: ",
.thread.NStoredPrimary, "/", .thread.NStoredSecondary, " Forwarded: ", .thread.NForwarded,
" (", .thread.NForwardFail, " failed)",
"\n");
}
/* Write the 'main line' of the program here. */
method ODBCThreadDemo.constructor ()
{
// Create and configure the database connection object
.thread = new ODBCThread();
.thread.Configure(.DSN, .username, .password, STORE_AND_FORWARD, .cachefile);
// If we wanted to delete the table on the first connection after the script starts,
// do this. Be careful - re-running the script will start over and delete the table
// again.
.thread.AddInitStage(format("drop table %s", .tablename), nil, t);
// Specify the initialization steps that must be executed in sequence
// First, create the table if it does not exist. The 't' in the onFail argument says
// to ignore errors and continue with the next stage.
.thread.AddInitStage(format("create table %s (ptid int auto_increment primary key, ptname varchar(64),
ptvalue double, pttime datetime )", .tablename),
nil, t);
// Query the table and map it to a class for eash insertion. We want to run an asynchronous event
// within the asynchronous initialization stage, so to do that we specify the special method
// cbInitStage as the callback function of our asynchronous event (GetTableInfo). We deal with
// the return from the GetTableInfo in the onSuccess argument of the init stage.
.thread.AddInitStage(`(@.thread).GetTableInfo("", "", (@.tablename), "TABLE,VIEW",
`(@.thread).cbInitStage()),
`(@self).mapTable(@.tablename, SQLTables), nil);
// Do not start writing data to the table until we have successfully created and mapped
// the table to a class. If we wanted to start writing data immediately, then we would
// create the table class beforehand instead of querying the database for the table
// definition. Then, even if the database were unavailable we could still cache to the
// local disk until the database was ready.
.thread.AddInitStage(nil, `(@self).startLogging(), nil);
// Set up the callback functions for various events from the database thread
.thread.OnConnectionSucceeded = `(@self).onConnect();
.thread.OnConnectionFailed = `(@self).onConnectFail();
.thread.OnFileSystemError = `princ("File System Error: ", SQLResult, "\n");
.thread.OnODBCError = `princ("ODBC Error: ", SQLResult, "\n");
.thread.OnExecuteStored = nil;
// Now that everything is configured, start the thread and begin connecting. All of the
// logic now will be driven through the onConnect callback and then through the init
// stages.
.thread.Start();
// Print statistics about the database thread every 2 seconds.
.TimerEvery(2, `(@self).printStats());
}
/* Any code to be run when the program gets shut down. */
method ODBCThreadDemo.destructor ()
{
}
/* Start the program by instantiating the class. */
ApplicationSingleton (ODBCThreadDemo);
Copyright © 1995-2010 by Cogent Real-Time Systems, Inc. All rights reserved.