Database
The Database interface is an abstraction of different database libraries to allow acces do different databases (PostgreSQL and MySQL are supported).
The interface only allows easy access to the underlying database system, which will be enough in most cases. However, nearly everything you can do with the
SQL features supported by the database engine, you can do with IDatabase. IDatabase won't translate the non-standard SQL syntax from one database engine
to an other. So while they can (and maybe have to be) used, changing the underlying database engine will probably fail.
NOTE: The SQL engine used and provided by the AppPlatform is PostgreSQL. There is no mySQL server running on the AppPlatform. But because there are a
lot of servers in the world wide web (or elsewhere) running MySQL, it is supported by the AppPlatform SDK, too. For AppPlatform app developtment, normaly only
PostgreSQL knowledge is needed.
File information
Public functions
Functions to initialize
extern IDatabaseProvider * CreatePostgreSQLDatabaseProvider();
extern IDatabaseProvider * CreateMySQLDatabaseProvider();
This functions will be used to create a database provider, wich will return an instance to an IDatabase instance for the database engine to use.
Note that the returned instance must be deleted, if no longer needed.
CreatePostgreSQLDatabaseProvider
Return value
Returns a IDatabaseProvider which creates an IDatabase instance using the PostgreSQL database driver.
CreateMySQLDatabaseProvider
Return value
Returns a IDatabaseProvider which creates an IDatabase instance using the MySQL database driver.
Remarks
Because the official MySQL library doesn't support asynchronous communication, the driver itself will run in a seperate task to support
nonblocking database operations.
Helper functions
inline const char * DBErrorToStr(db_error_t err);
DBErrorToStr
Converts the given databas_error_t value to a human readable format. The result will be just a string of the name
of the enum itself. To get a real error message, call IDatabase::GetLastErrorMessage() instead.
Parameters
db_error_t err | The error to convert to a string. |
Return value
The name of the error enum as a string.
Classes
IDatabaseProvider
class IDatabaseProvider {
public:
virtual ~IDatabaseProvider() {}
virtual class IDatabase * CreateDatabase(class IIoMux * const iomux, class UDatabase * const user, class IInstanceLog * log) = 0;
};
A database provider is needed to create an IDatabase instance. What kind of database driver used by the returned instance is defined
by the type of IDatabaseProvider itself. The IDatabase instance returned by an IDatabaseProvider must be deleted, if no longer used.
Public functions
CreateDatabase
Creates the IDatabase instance.
Parameters
class IIoMux * const iomux | The IIoMux instance of the app. |
class UDatabase * const user | The UDatabase instance to receive the callbacks. |
class IInstanceLog * log | The IInstanceLog instance used for logging purposes. The flag LOG_DATABASE must be set to enable IDatabase logging. |
Return Value
The create IDatabase intsance, that must be deleted if no longer used.
IDatabase
lass IDatabase {
public:
virtual ~IDatabase() {}
virtual void Connect(const char * address, const char * dbname, const char * user, const char * password);
virtual bool Connected();
virtual void Shutdown();
virtual size_t QueryPrint(char * buffer, size_t bufferSize, const char * sqlcmd, ...);
virtual size_t QueryPrintV(char * buffer, size_t bufferSize, const char * sqlcmd, va_list argList);
virtual void ExecSQL(UDatabase * const user, dword flags, const char * sqlcmd, ...);
virtual void ExecSQLV(UDatabase * const user, dword flags, const char * sqlcmd, va_list argList);
virtual void InsertSQL(UDatabase * const user, const char * sqlcmd, ...);
virtual void InsertSQLV(UDatabase * const user, const char * sqlcmd, va_list argList);
virtual void BeginTransaction(UDatabase * const user, const char * lockTableCmd = nullptr);
virtual void EndTransaction(UDatabase * const user, bool doRollback = false);
virtual void PrepareStatement(UDatabase * user, const char * sqlcmd);
virtual void ExecSQL(UDatabase * const user, dword flags, class IPreparedStatement * const statement);
virtual void InsertSQL(UDatabase * const user, class IPreparedStatement * const statement);
virtual size_t EscapeSearchLiterals(const char * value, char * buffer, size_t bufferSize);
virtual const char * GetLastErrorMessage();
virtual const char * GetLastErrorCode();
};
This is the main calss that handels the communication between the Application and the database server. The interface is primarly for executing queries while the results
of that queries need to be handled by an UDatabase instance. Each queries posted will be add to an internal queue and processed with the fifo priciple.
Queries posted during a call of BeginTransaction() and EndTransaction() will be part of that transaction. Nested transactions are not allowed.
When sending SQL queries, a user can be given to override the default user set with IDatabaseProvider::CreateDatabase(). If an other user than the default one is given
to IDatabase::BeginTransaction(), all queries posted of that user will be prioritized because of that transaction. So queries posted from other users (or with no special
user to use the default one) will be placed at the end of the queue, until the user who opens the tansaction closes it (IDatabase::EndTransaction()). This shows also a
limitation at this point: if multiple UDatabase instances as users are needed for one transaction (e. G. by starting a transaction to update a set of data from different
sources), it won't work as long as there is some kind of "master" user the other ones. Gladly there is a solution. For that case,
DatabaseSwitch can be used.
Using multiple users will also raise the question, how error handling worlks. Actually that depends on the error and how that error will
influence the internal state of IDatabase:
-
If an SQL command of a user failes, all pending commands of the user that sends that user will be removed from the internal queue.
Then the user itself will receive an UDatabase::DatabaseError(). If the user had an active transaction, IDatabase will make a rollback
and unlock tables if they are locked.
-
If an critical error occures that makes it impossible to continue using IDatabase, all pending commands of all users will be discarded. If there
is an active transaction, it will be rolled back. Then the UDatabase::DatabaseError() function will be called for all those users who had pending
queries inside the queue. After all the users had been informed, IDatabase internaly will call Shutdown() to close the database connection and
call UDatabase::DatabaseShutdown() callback of the main user.
Public functions
Connect
Establishes a connection to the database server.
Parameters
const char * address | The address to the database server. Can be an IPv4 or IPv6 address, an URI or nullptr to connecto to the local server using much faster unix domain sockets (recommended for connecting to the PostgreSQL server on the AppPlatform). |
const char * dbname | The name of the database to connect to. |
const char * user | The user to use for the connection. |
const char * dbname | The password for that user. |
Callbacks
If the connection had been established, UDatabase::DatabaseConnectComplete() will be called. If an error leads to an UDatabase::DatabaseShutdown() callback with an error code to indicate
what was going wrong.
Connected
Return value
Return true, if IDatabase is connected to the server, else false.
Remarks
After calling IDatabase::Shutdown(), Connected() will return false even if the connection to the server still exists. So Connected() cannot be used for
celanup or shutdown checks, instead handle them inside the UDatabase::DatabaseShutdown() callback.
Shutdown
Closes the database connection. If there are pending queries inside the queue, those will be handled first before the connection actually will be closed.
Callbacks
After the connection had been closed, UDatabase::DatabaseShutdown() will be called.
QueryPrint
Used to build up a query string. The query will be stored inside buffer with a maximum of bufferSize. The SQL command itself has to be passed in sqlcmd.
That string could have placeholders, which will be replaced with the values of the data given in a variable parameters list after sqlcmd. The used
placeholder must combine with the data given. Also (depending on the placeholder) some critical characters will be escaped. Using QueryPrint is an
elegant way to dynamically build up queries while preventing SQL interjection from user input at the same time.
Note that wile IDatabase::ExecSQL() does the same (which means, the SQL query with the variable parameters can directly given to ExecSQL()), QueryPrint()
helps to bild up a SQL query that can be insert into an other for nested SQL queries, or to pepare a query for later use.
Parameters
char * buffer | The buffer to store the processed query string to. |
size bufferSize | The size of the given buffer. |
const char * sqlcmd | The SQL command string to process. Can contain placeholders. |
... ... | Additional values for the placeholders. Must be the same order and datatypes as the placeholders defines them. |
Return value
The size of the final query string.
Remarks
The function supports the following placeholders:
- %i Placeholder for an integer
- %u Placeholder for an unsigned integer (dword)
- %f Placeholder for a floating point value (double)
- %lli Placeholder for an 64 bit integer (long64)
- %llu Placeholder for an 64 bit unsigned integer (ulong64)
- %b Placeholder for a bool
-
%q Placeholder for a string that will be copied directly, without quoting or escaping of the strings data.
Can be used to insert an alrady prepared query string.
-
%du Placeholder for a buffer. The value of the buffer will be copied as a hex string, starting with \x. Note
that an additional parameter must be passed (size_t) to tell the size of the buffer.
- %s Placeholder for a string. The string will be enclosed by '.
- %S Placeholder for a string. The string will be enclosed by ".
- %n Placeholder for a string that will be insert to the SQL command without enclosing it to ' or ".
- %e Same as %n with the exception that search literals (like _ and &) will be escaped to use them as charater and not as search wildcard.
-
%p Suffix to pass a pointer instead of a value.
Only in combination with integer or bool variants (%pi, %pu, %pb, %plli, %pllu).
When using with prepared statements, that suffix will be ignored.
Note: This can be used to set a field to NULL by pasing NULL
instead of a valid pointer. If you need to set a string field to NULL,
simply pass NULL when using %s, %S or %n.
- %% An escaped % sign.
QueryPrintV
Does the same as QueryPrint. The difference here is that a pointer to the variable parameters can be passed.
Parameters
char * buffer | The buffer to store the processed query string to. |
size bufferSize | The size of the given buffer. |
const char * sqlcmd | The SQL command to execute. Can contain placeholders. |
va_list argList | Pointer to the start of the variable parameters. |
Return value
The size of the final query string.
ExecSQL (overloaded)
Executes the given SQL command. SQL query given can contain place holders to put variable values inside the queries. Also by using that
placeholders, the values (especially strings) will be used in a way to prevent SQL interjection. The function internally calls IDatabase::QueryPrint(),
so preparing the query first and passing it to ExecSQL() actually will be a bad idea performance wise. However, in some cases QueryPrint() is still
a nice helper to prepare various queries for later use.
Note: for the placeholders and datatypes supported, look to the QueryPrint description.
Parameters
UDatabase * const user | The user to callback with the result of the SQL execution (or nullptr, to call the default user). |
dword flags | Additional flags. Can be 0 or DB_EXEC_FETCH_ALL is supported to get all results at once (see IDataSet for details). |
const char * sqlcmd | The SQL command string to process. Can contain placeholders. |
... ... | Additional values for the placeholders. Must be the same order and datatypes as the placeholders defines them. |
Callbacks
On success, UDatabase::DatabaseExecSQLResult() will be called, or UDatabase::DatabaseError(), if an error occures.
Remarks
Note that there is a limit of a maximum length for SQL commands of 250*1024-1 (255999) characters. This is the size for the finaly SQL query string,
after parsing all placeholders and inserting the values for them at the prorpriate place.
ExecSQL (overloaded)
Executes the given prepared statement. See IPreparedStatement to learn more about how to create and use them.
Parameters
UDatabase * const user | The user to callback with the result of the SQL execution (or nullptr, to call the default user). |
dword flags | Additional flags. Can be 0 or DB_EXEC_FETCH_ALL is supported to get all results at once (see IDataSet for details). |
IPreparedStatement * const preparedStatement | The prepared statement to execute. |
Callbacks
On success, UDatabase::DatabaseExecSQLResult() will be called, or UDatabase::DatabaseError(), if an error occures.
ExecSQLV
Same as IDatabase::ExecSQL(), so that instead of a variable list of parameters for the placeholders, a pointer to the start of the variable list must be given. For more information
about the function see the describtion of IDatabase::ExecSQL() above.
Parameters
UDatabase * const user | The user to callback with the result of the SQL execution (or nullptr, to call the default user). |
dword flags | Additional flags. Can be 0 or DB_EXEC_FETCH_ALL is supported to get all results at once (see IDataSet for details). |
const char * sqlcmd | The SQL command string to process. Can contain placeholders. |
va_list argList | Pointer to the start of the variable parameters. |
Callbacks
On success, UDatabase::DatabaseExecSQLResult() will be called, or UDatabase::DatabaseError(), if an error occures.
Remarks
Note that there is a limit of a maximum length for SQL commands of 250*1024-1 (255999) characters. This is the size for the finaly SQL query string,
after parsing all placeholders and inserting the values for them at the prorpriate place.
InsertSQL (overloaded)
Executes a query as a query to insert data. The difference in compartion to IDatabase::ExecSQL() is, that UDatabase::DatabaseInserSQLResult() will be called after execution,
holding the ID of the new insert dataset. The rules for the sqlcmd and the placeholder are the same as for IDatabase::ExecSQL() and IDatabse::QueryPrint(). So look to the
IDatabase::QueryPrint() description for more details. Like IDatabase::ExecSQL(), InsertSQL() calls IDatabase::QueryPrint() internally to pepare the given SQL command.
Parameters
UDatabase * const user | The user to callback with the result of the SQL execution (or nullptr, to call the default user). |
const char * sqlcmd | The SQL command string to process. Can contain placeholders. |
... ... | Additional values for the placeholders. Must be the same order and datatypes as the placeholders defines them. |
Callbacks
On success, UDatabase::DatabaseInserSQLResult() will be called, or UDatabase::DatabaseError(), if an error occures.
Remarks
Note that there is a limit of a maximum length for SQL commands of 250*1024-1 (255999) characters. This is the size for the finaly SQL query string,
after parsing all placeholders and inserting the values for them at the prorpriate place.
To make it work, it is necessary that the data base table you're going to address with an InsertSQL() call, has a field named id, wich must be an 64bit, autoincrement
primary key field. Read the PostgreSQL or MySQL documentation to know how to create such a field. For PostgreSQL there is no need to add the 'RETURNING id' part to the query, because
it will be added by the InsertSQL() function.
InsertSQL (overloaded)
Executes the given prepared statement to insert data. See IPreparedStatement to learn more about how to create and use them.
The difference in compartion to IDatabase::ExecSQL() is, that UDatabase::DatabaseInserSQLResult() will be called after execution, holding the ID of the
new insert dataset.
Parameters
UDatabase * const user | The user to callback with the result of the SQL execution (or nullptr, to call the default user). |
const char * sqlcmd | The SQL command string to process. Can contain placeholders. |
IPreparedStatement * const preparedStatement | The prepared statement to execute. |
Callbacks
On success, UDatabase::DatabaseInserSQLResult() will be called, or UDatabase::DatabaseError(), if an error occures.
Remarks
To make it work, it is necessary that the data base table you're going to address with an InsertSQL() call, has a field named id, wich must be an 64bit, autoincrement
primary key field. Read the PostgreSQL or MySQL documentation to know how to create such a field. For PostgreSQL, the 'RETURNING id' part must be added to the query when creating
the prepared statement via IDatabase::PrepareQuery.
InsertSQLV
Same as IDatabase::InsertSQL(), so that instead of a variable list of parameters for the placeholders, a pointer to the start of the variable list must be given. For more information
about the function see the describtion of IDatabase::InsertSQL() above.
Parameters
UDatabase * const user | The user to callback with the result of the SQL execution (or nullptr, to call the default user). |
const char * sqlcmd | The SQL command string to process. Can contain placeholders. |
va_list argList | Pointer to the start of the variable parameters. |
Callbacks
On success, UDatabase::DatabaseInserSQLResult() will be called, or UDatabase::DatabaseError(), if an error occures.
Remarks
Note that there is a limit of a maximum length for SQL commands of 250*1024-1 (255999) characters. This is the size for the finaly SQL query string,
after parsing all placeholders and inserting the values for them at the prorpriate place.
To make it work, it is necessary that the data base table you're going to address with an InsertSQL() call, has a field named id, wich must be an 64bit, autoincrement
primary key field. Read the PostgreSQL or MySQL documentation to know how to create such a field.
BeginTransaction
Will start a transaction block. After calling this function, all SQL queries posted by other users than the one who was given to BeginTransaction() will be
put to the and of the list (including other BeginTransaction() calls). So it is guaranteed that only the SQL queries from the user who started the transaction
will also be part of it. If nescesarry, an additional SQL command for locking a table can be given. More information about that can be found in the documentation
of PostgreSQL or MySQL. A transaction can be closed by calling IDatabase::EndTransaction().
it must be given to lockTableCmd.
Note: Never send a lock command with a normal ExecSQL, or the lock will not been
canceld if an error occures. BeginTransaction() will call LockQueue() to group the commands.
On success, UDatabase::ExecSQLResult() will be called.
Parameters
UDatabase * const user | The user to callback with the result after starting the transaction. |
const char * lockTableCmd | (Default: nullptr) An additional SQL command to lock tables or nullptr, if no additional command should be send. |
Callbacks
After starting the transaction, UDatabase::DatabaseBeginTransactionResult() will be called.
Remarks
If a table must be locked, only do that with IDatabase::BeginTransaction(). Never send a lock command directly using IDatabase::ExecSQL(), or the table won't be unlocked automatically!
If there are other commands in the queue, they will be processed before. So UDatabase::DatabaseBeginTransactionResult() really will be called after the transaction is active. So that
callback will be the best place to send SQL commands for that transaction.
EndTransaction
Will end a transaction. If needed, each change made during the active transaction can be rolled back when the flag is set to do so. Note that on an internal error of IDatabase
(e. g. some SQL command posted has a syntax error), the transaction automatically will be rolled back and closed. So there is no need to call EndTransaction() in case of a
database related error. If a table had been lock by IDatabase::BeginTransaction(), that table will be unlocked.
Parameters
UDatabase * const user | The user to close the transaction for. Must be the same as given to IDatabase::BeginTransaction(). |
bool roolBack | (Default: false) If true, IDatabase will rollback each changed made since the call of IDatabase::BeginTransaction(). |
Callbacks
After ending the transaction, UDatabase::DatabaseEndTransactionResult() will be called.
EscapeSearchLiterals
Escapes search literals used in LIKE queries so that they are not interpreted as search pattern by the database (e.g. 'Search%' will be 'Search\%' afterwards).
In PostgreSQL and MySQL, the patterns '%' and '_' are escaped. So those will be interpreted as characters. If you whant to call IDatabase::ExecSQL() or
IDatabase::InsertSQL() using a query string, it will be better to use the %e placeholder, which does the same. However, EscapeSeaerchLiterals() will come
in handy when using a string as as value for prepared statements.
Parameters
const char * value | The string the holds (or can hold) search literals to escape. |
const char * buffer | The buffer to store the escaped string to. |
size_t bufferSize | The size of the buffer to store the data to. |
Remarks
The buffer given must be big enough to hold the result. So to be on the safe side, it should be able to hold strlen(value) * 2 + 1 bytes.
PrepareStatement
Creates a prepared statement for the given SQL query. See IPreparedStatement for details.
Parameters
UDatabase * const user | The user to receive the prepared statement (or nullptr to use the default user). |
const char * sqlcmd | The SQL command to create the prepared statment for. |
Callbacks
After creating the prepares tatement, UDatabase::DatabasePrepareStatementResult() will be called. The IPraparedStatement instance given to that callback must
be deleted if no longer needed. In case of an error, the IDatabase will call UDatabase::DatabaseError().
Remarks
For PostgreSQL, if the created perpared statement is an INSERT statement and so will be passed to IDatabase::InsertSQL(), the "RETURNING id" statement must be added to the query!
GetLastErrorMessage
Return value
Returns the error message of the last error occurred.
Remarks
GetLastErrorMessage() helps to log the error or to see what acutally was wrong. UDatabase::DatabaseError() will be a good place to call the message. Don't save the pointer
returned. Instead, of the error message should be stored for later use, you must make a copy of it.
GetLastErrorCode
Return value
Returns the error code returned by the database server. Note that this value is a string, not a nummeric value.
Remarks
UDatabase::DatabaseError() will be a good place to call the message. Don't save the pointer
returned. Instead, of the error message should be stored for later use, you must make a copy of it.
IPreparedStatement
class IPreparedStatement {
public:
virtual ~IPreparedStatement() {}
virtual void SetParam(dword idx, int value);
virtual void SetParam(dword idx, dword value);
virtual void SetParam(dword idx, long64 value);
virtual void SetParam(dword idx, ulong64 value);
virtual void SetParam(dword idx, bool value);
virtual void SetParam(dword idx, double value);
virtual void SetParam(dword idx, int * value);
virtual void SetParam(dword idx, dword * value);
virtual void SetParam(dword idx, long64 * value);
virtual void SetParam(dword idx, ulong64 * value);
virtual void SetParam(dword idx, double * value);
virtual void SetParam(dword idx, bool * value);
virtual void SetParam(dword idx, const char * value);
virtual void SetParam(dword idx, const byte * buffer, size_t size);
};
Prepared statements are some kind of ready-to-use SQL queries. While they needs some managament overhead (create them, stored them on some place for usage
and deleted them when no longer used), they have some other advantage: ease of use and performance. As a thumb rule you can say: for
each SQL query that is used more than once on a regular basis, it is worth to use a prepared statement. Because when using prepared statment (after
creating them), there is no need to build up an SQL string, escape strings to prevent SQL injection and also there is no need to parse the SQL
string again on the database server side. So using prepared statements (while consuming a little memory footprint) can spead up the applications
performance a bit. That said, according to the documentation of PostgreSQL and MySQL, prepared statements can also have no benefit at all or even
slow down the performance under some circumstances. So finally it depends on tests. However, using prepared statements for often called SQL queries will
be a good start.
To create an IPreparedStatement instance, call IDatabase::PrepareStatement(). The instance will then passed to UDatabase::DatabasePrepareStatementResult() and must
be deleted, if no longer used. To execute a prepared statement, simply set the parameters and pass it to IDatabase::ExecSQL() or IDatabase::InsertSQL().
The index of a parameter depends on the position of the parameters place holder in the SQL string. So for a query like "select * from users where user_name=%s and
age > %i", the index of the parameter for %s will be 0, and for the parameter for %i 1. The placeholders have the same format as for IDatabase::QueryPrint(), so read
the description of that function for more information. However, because of the nature of prepared statements, there are some differences for the placeholders:
- %p will be ignored. To pass a pointer, use one of the IPreparedStatement::SetParam() functions that accept the pointer of the format needed.
- %s, %S and %n will be handled in the same way: the string will be used without beeing enclosed by ' or ".
- %s is not supported. Use %s instead and call IDatabase::EscapeSearchLiterals() to get the same effect.
After the parameters had been set and the prepared statment instance had been passed to IDatabase::ExecSQL() or IDatabase::InsertSQL(), the IPreparedStatement instance
can directly be used again and the parameters can be changed. Because the prepared statements will manage the parameters internally by reference counting so that they
are fixed as soon as the prepared statement instance sits inside the IDatabase execution queue.
Public functions
SetParam (overloaded)
Set the parameters for the query. There are various functions for the different datatypes supported by IDatabase as well as a version that accepts a pointer for that
datatype. When passing nullptr to that pointer, the datafield inside the database will be set to NULL. See the source code part above for details.
Parameters
dword idx | The index of the parameter value to set. |
various types value | The value for that parameter. |
Remarks
It can be that you must do some typecasting to let the compiler now, which overloaded function you actually whant to use. If e. G. SetParam() will be called with value 1,
the compiler must know, how to interpret this number: as dword, long64, bool, ...
SetParam (overloaded)
This version takes three arguments instead as two like the others. It is for passing buffers and so does also need the buffer size.
Parameters
dword idx | The index of the parameter value to set. |
const byte * buffer | The buffer holding the data to write to the database. |
size_t size | The size of the buffer. |
IDataSet
class IDataSet {
public:
virtual ~IDataSet() {}
virtual size_t GetColumnCount();
virtual bool Eot();
virtual void Next();
virtual void FetchNextRow();
virtual bool IsSingleRowMode();
virtual ulong64 GetAffectedRows();
virtual const char * GetColumnName(int column);
virtual int GetColumnID(const char * columnName);
virtual bool ColumnIsNull(const char * columnName);
virtual bool ColumnIsNull(int column);
virtual bool ColumnIsString(int column);
virtual int GetIntValue(const char * columnName);
virtual dword GetUIntValue(const char * columnName);
virtual long64 GetLong64Value(const char * columnName);
virtual ulong64 GetULong64Value(const char * columnName);
virtual bool GetBoolValue(const char * columnName);
virtual double GetDoubleValue(const char * columnName);
virtual const char * GetStringValue(const char * columnName);
virtual const char * GetStringValueWithNull(const char * columnName);
virtual size_t GetDataSize(const char * columnName);
virtual const byte * GetDataValue(const char * columnName);
virtual int GetIntValue(int column);
virtual dword GetUIntValue(int column);
virtual long64 GetLong64Value(int column);
virtual ulong64 GetULong64Value(int column);
virtual bool GetBoolValue(int column);
virtual double GetDoubleValue(int column);
virtual const char * GetStringValue(int column);
virtual const char * GetStringValueWithNull(int column);
virtual size_t GetDataSize(int column);
virtual const byte * GetDataValue(int column);
};
After executing a SQL query, the result of that query will given to the UDatabase user in form of an IDataSet instance. By using that instance, the data of the
query result can be accessed. After prpcessomg all data, the application must delete the IDataSet instance to let the IDatabase process the next SQL query from
the internal queue. So it is alos not allowes to store the IDataSet for some later use, because it will block the whole database system. An IDataSet with instance
will be passes to UDataset::DatabaseExecSQLResult() and should be processed before the function returns.
Public functions
GetColumnCount
Return value
Returns the number of columns available in the result set.
Eot
Return value
True, of the end of the result table had been reached, else false.
Remarks
If Eot returnes true, the IDataSet intsance must be deleted to let IDataset continue processing the next SQL query inside the internal queue.
Next
Set the internal row pointer to the next row to get the data out of it. This should be used in a loop to process all data from the query.
Please use it with care, because if processing the data takes a lot of time, it will block the whole asynchronous system for your application. Before
accessing data, IDataSet::Eot() must be called to make sure that there is data available.
Remarks
Only call Next() if the query for that result had been executed with the DB_EXEC_FETCH_ALL flag or use IDataset::FetchNextRow() instead.
FetchNextRow
Fetches the next row from the server. This is the asynchronous way of processing each row. After calling this function, UDatabase::DatabaseExecSQLResult() can be leaved
and will be called again from IDatabase, when the next row had ben read. You must check the return of IDataSet::Eot() before accessing the row data to make sure, that
there is data available.
Callbacks
UDataset::DatasetExecSQLResult() will be called
IsSingleRowMode
Can be used to check, if the data can be process with IDataSet::Next() or IDataSet::FetchNextRow(). Single-row-mode means, that the result will be fetched line by line
from the server (which is the default if the flag DB_EXEC_FETCH_ALL was not given to IDatabase::ExecSQL()).
Return value
True, if the dataset uses single-row-mode and the rows must be read calling IDataSet::FetchNextRow(). Fals, if all the rows had been received from the database server and
IDataSet::Next() must be used to process the rows.
GetAffectedRows
Return value
Returns the number of rows affected by the query (which actually is then number of rows IDataSet provides).
GetColumnName
Parameters
int column | The index of the column to get the name for. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
Returns the name of the culumn.
GetColumnID
Parameters
const char * columnName | The Name of the column to get the index for. |
Return value
The index of the column or -1, if the column name canot be found inside the list of columns.
ColumnIsNull (overloaded)
Parameters
int column | The index of the column to check. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
True, if the value of the column is NULL, else false.
ColumnIsNull (overloaded)
Parameters
const char * columnName | The name of the column to check. |
Return value
True, if the value of the column is NULL, else false.
ColumnIsString (overloaded)
Parameters
int column | The index of the column to check. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
True, if the value of the column is a string type, else false.
GetIntValue (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as integer.
GetIntValue (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as integer.
GetUIntValue (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as integer.
GetUIntValue (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as dword.
GetLong64Value (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as long64.
GetLong64Value (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as long64.
GetULong64Value (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as ulong64.
GetULong64Value (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as ulong64.
GetBoolValue (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as bool.
GetBoolValue (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as bool.
GetDoubleValue (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as double.
GetDoubleValue (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as doubel.
GetStringValue (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as string. If the column is NULL, an empty string will be returned.
GetStringValue (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as string. If the column is NULL, an empty string will be returned.
GetStringValueWithNull (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as string. If the column is NULL, nullptr will be returned.
GetStringValueWithNull (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The data value of the column as string. If the column is NULL, nullptr will be returned.
GetDataSize (overloaded)
Parameters
int column | The index of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
The number of binary data bytes hold by the column.
Remarks
To get the binary data itself, call IDataSet::GetDataValue().
GetDataSize (overloaded)
Parameters
const char * columnName | The name of the column get the value from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
The number of binary data bytes hold by the column.
Remarks
To get the binary data itself, call IDataSet::GetDataValue().
GetDataValue (overloaded)
Parameters
int column | The index of the column get the data from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Return value
Returns the binary data of the column.
Remarks
Got get the number of bytes hold by the column, call IDataSet::GetDataSize().
GetDataValue (overloaded)
Parameters
const char * columnName | The name of the column get the data from. Must be between 0 and IDataSet::GetColumnCount() - 1. |
Returns the binary data of the column.
Remarks
Got get the number of bytes hold by the column, call IDataSet::GetDataSize().
UDatabase
class UDatabase {
public:
virtual void DatabaseConnectComplete(IDatabase * const database) {};
virtual void DatabaseShutdown(IDatabase * const database, db_error_t reason) {};
virtual void DatabaseError(IDatabase * const database, db_error_t error) = 0;
virtual void DatabaseExecSQLResult(IDatabase * const database, class IDataSet * dataset) {};
virtual void DatabaseInsertSQLResult(IDatabase * const database, ulong64 id) {};
virtual void DatabaseBeginTransactionResult(IDatabase * const database) {};
virtual void DatabaseEndTransactionResult(IDatabase * const database) {};
virtual void DatabasePrepareStatementResult(IDatabase * const database, class IPreparedStatement * statement) {};
};
Each database request will lead to a result, wich will be redirected to an UDatabase instance. An app must provide at least on UDatabase instance to pass to IDatabase, but
also can have multiple UDatabase objects to pass to database related calls. That user passed to a function (like IDatabase::ExecSQL()) will receive the result of the SQL
query to execute. If nullptr will be passed as user to that functions, the default user (given to IDatabaseProvider::CreateDatabase()) will be used for callbacks.
Public functions
DatabaseConnectComplete
Will be called when the connection to the database server had been established.
Parameters
IDatabase * const database | The calling database instance. |
DatabaseShutdown
Will be called after the connection had been closed or if an error occurred when tried to establish the connection. The reason provides details
about what happend.
Parameters
IDatabase * const database | The calling database instance. |
db_error_t reason | The reason why the connection had been closed. |
Remarks
This callback will be a good place to delete the IDatabase instance. However, this should only be done from the same UDatabase instance that
was given to IDatabaseProvider::CreateDatabase().
DatabaseError
Will be called when an error occures while executing a command. The user whis query raised an error will receive that callback. In that case, all
pending SQL queries inside the internal queue of IDatabase will be discared and an active transaction will be be rolled back and closed (befor this callback raises).
If a critical error occures, all queries inside the queue will be discared and all users will receive UDatabase::DatabaseError(), before the main user receives
a UDatabase::DatabaseShutdown().
Parameters
IDatabase * const database | The calling database instance. |
db_error_t error | The rror that occured. |
DatabaseExecSQLResult
Will be called after executing an SQL query, providing the result of that call. The given dataset should be handled here and must be deleted,
if no longer needed to let IDatabase process the next query of the internal queue. If the dataset is in sinngle row mode (because of fetching each
row in an asynchronous way), DatabaseExecSQLResult() will be recalled after receiving the next row. For this, IDataSet::FetchNextRow() must be used.
Parameters
IDatabase * const database | The calling database instance. |
IDataset * dataset | The IDataset instance providing the result of the query. |
Remarks
Don't delete the IDataset after calling IDataset::FetchNextRow(). Also, don't forget to delete the IDataSAet instance if you no longer
need it, of the database system will block forever.
DatabaseInsertSQLResult
Will be called after the execution of IDatabase::InsertSQL(), providing the id of the newly insert row.
Parameters
IDatabase * const database | The calling database instance. |
ulong64 id | The ID of the new insert row. |
Remarks
To use IDatabse::InsertSQL() and this callback, the table to insert a row must provide a field named ID. That field must
hold a 64bit value and must be defined to autoincrement. More information can be found in the PostgreSQL or MySQL documentation.
DatabaseBeginTransactionResult
Will be called after a transaction had been started because of a previous call to IDatabase::BeginTransaction(). So this callback will be the right
place to start posting SQL requests that will be part of the transaction.
Parameters
IDatabase * const database | The calling database instance. |
Remarks
After a transaction had been started, all IDatabase::ExecSQL() or IDatabase::InsertSQL() calls will be put to the end of the queue and ignroed, if
they are not for the same user as the one who started the transaction. To use multiple users for results of transaction queries, use the
DatabaseSwitch helper class.
DatabaseEndTransactionResult
Will be called after a transaction had been closed because of a previous call to IDatabase::EndTransaction().
Parameters
IDatabase * const database | The calling database instance. |
DatabasePrepareStatementResult
When creating a prepared statement by calling IDatabase::PrepareStatement(), the result will be passed to UDatabase::DatabasePrepareStatementResult() by the
given IPreparedStatement instance. To execute that instance, it must be given to IDatabase::ExecSQL() or IDatabase::InsertSQL(). It also must be deleted by
the app, if no longer used.
Parameters
IDatabase * const database | The calling database instance. |
IPreparedStatement * statement | The instance to use for executing the prepared statement. |
Data types
Defines / Statics
#define DB_EXEC_FETCH_ALL 0x01
DB_EXEC_FETCH_ALL |
Can be passed to IDatabase::ExecSQL() to request all result rows. In that case, the results will be completely loaded into the apps
memory. To access it, the data must be processed in a loop by calling IDataSet::Next().
|
db_error_t
typedef enum {
DB_OK = 0,
DB_SHUTDOWN_NORMAL = DB_OK,
DB_ERR_CONNECT_FAILED,
DB_ERR_SQL_COMMAND_FAILED,
DB_ERR_SHUTDOWN_STATE,
DB_ERR_SERVER_ERROR
} db_error_t;
This enum is used determine the reason why a database connection had been closed. The values are:
DB_OK | Everything is fine, the shutdown is the result of a previous IDatabase::Shutdown() call. |
DB_SHUTDOWN_NORMAL | Same as DB_OK. Recommendet to use for better readable code. |
DB_ERR_CONNECT_FAILED | The conenction to the database failed. Enable logging to get details. |
DB_ERR_SQL_COMMAND_FAILED | An SQL command send failed to execute. Enable logging to see what was wrong with that query. |
DB_ERR_SHUTDOWN_STATE | The app tried to execute a database command, while the shutdown of the database already is in process. |
DB_ERR_SERVER_ERROR | An error occured on the database server side. Enable logging to see more details. |
Code Example
app::app(IIoMux * iomux)
: iomux(iomux)
{
IDatabaseProvider * databaseProvider = CreatePostgreSQLDatabaseProvider();
this->database = databaseProvider->CreateDatabase(iomux, this, this);
delete databaseProvider;
this->database->Connect("127.0.0.1", "test_db", "user", "12345");
}
void app::DatabaseConnectComplete(IDatabase * const database)
{
this->database->ExecSQL(this, 0, "select * from test_table where name=%s and id=%u", "Noname", 42);
}
void app::DatabaseExecSQLResult(IDatabase * const database, class IDataSet * dataset)
{
if (dataset->Eot()) {
delete dataset;
this->database->Shutdown();
}
else {
printf("%u | %s\n", dataset->GetIntValue("id"), dataset->GetStringValue("name"));
dataset->FetchNextRow();
}
}
void app::DatabaseShutdown(IDatabase * const database, db_error_t reason)
{
if (reason != DB_SHUTDOWN_NORMAL) {
debug->printf("An error occured, look at the log of the app\n");
}
delete this->database;
this->database = nullptr;
}