dtl


BoundIO and BoundIOs

Category: utilities Component type: type

Description

A BoundIO is an object used to hold an association between a field in a query and a field in a user defined data object. The BoundIO class is held in a private member of the DBView template called the BoundIOs object which is an STL map<string, BoundIO> that holds a set of BoundIO objects that are mapped by SQL field name. As an end user, the four places you will encouter a BoundIO are when writing BCA , BPA, InsVal or SelVal functions.

Definition

Defined in the BoundIO.h header file.

Refinement of

None.

Associated types

NullField - a tag struct which indicates we want to set a field to null.

Example 1:

// Functor to bind SQL columns to a data object
class Example
{
  public:                                       // tablename.columnname:
	int exampleInt;                         // DB_EXAMPLE.INT_VALUE
	string exampleStr;                      // DB_EXAMPLE.STRING_VALUE
	double exampleDouble;                   // DB_EXAMPLE.DOUBLE_VALUE
	long exampleLong;                       // DB_EXAMPLE.EXAMPLE_LONG
	TIMESTAMP_STRUCT exampleDate;           // DB_EXAMPLE.EXAMPLE_DATE
};
class BCAExampleObj
{
public:
	void operator()(BoundIOs &boundIOs, Example &rowbuf)
    	{
	   boundIOs["INT_VALUE"] 	>> rowbuf.exampleInt;
	   boundIOs["STRING_VALUE"]	>> rowbuf.exampleStr;
	   boundIOs["DOUBLE_VALUE"]	>> rowbuf.exampleDouble;
	   boundIOs["EXAMPLE_LONG"]	>> rowbuf.exampleLong;
	   boundIOs["EXAMPLE_DATE"]	>> rowbuf.exampleDate;
	}
};

 

Example 2:

//BPA Functor to bind SQL parameters to a data object

// "Example" class to hold rows from our database table
class Example
{
  public:                                // tablename.columnname:
	int exampleInt;                 // DB_EXAMPLE.INT_VALUE
	string exampleStr;              // DB_EXAMPLE.STRING_VALUE
	double exampleDouble;           // DB_EXAMPLE.DOUBLE_VALUE
	long exampleLong;               // DB_EXAMPLE.EXAMPLE_LONG
	TIMESTAMP_STRUCT exampleDate;   // DB_EXAMPLE.EXAMPLE_DATE

	Example(int exInt, const string &exStr, double exDouble, long exLong,
		const TIMESTAMP_STRUCT &exDate) :
	   exampleInt(exInt), exampleStr(exStr), exampleDouble(exDouble), exampleLong(exLong),
	   exampleDate(exDate)
	{ }

};

// Create an association between table columns and fields in our object
class BCAExampleObj
{
public:
	void operator()(BoundIOs &cols, Example &rowbuf)
    	{
	   cols["INT_VALUE"] >> rowbuf.exampleInt;
	   cols["STRING_VALUE"] >> rowbuf.exampleStr;
	   cols["DOUBLE_VALUE"] >> rowbuf.exampleDouble;
	   cols["EXAMPLE_LONG"] >> rowbuf.exampleLong;
	   cols["EXAMPLE_DATE"] >> rowbuf.exampleDate;
	}
}

class ExampleParamObj
{
    public:
       	int lowIntValue;
	int highIntValue;
	string strValue;
	TIMESTAMP_STRUCT dateValue;
};

class BPAParamObj
{
public:
	void operator()(BoundIOs &boundIOs, ExampleParamObj &paramObj)
	{
	  boundIOs[0] << paramObj.lowIntValue;
	  boundIOs[1] << paramObj.highIntValue;
	  boundIOs[2] << paramObj.strValue;
	  boundIOs[3] << paramObj.dateValue;
	}

};

// read some Example objects from the database and return a vector of
// the results, use BPA to set join parameters
vector<Example> ReadData()
{
	vector<Example> results;

	// construct view
	
	DBView<Example, ExampleParamObj>
		view("DB_EXAMPLE", BCAExampleObj(),
		"WHERE INT_VALUE BETWEEN (?) AND (?) AND "
		"STRING_VALUE = (?) OR EXAMPLE_DATE < (?) ORDER BY EXAMPLE_LONG",
		BPAParamObj());

	// loop through query results and add them to our vector
	// in this loop, read_it.GetLastCount() records read from DB

	DBView<Example, ExampleParamObj>::select_iterator read_it = view.begin();

	// set parameter values for the WHERE clause in our SQL query
	read_it.Params().lowIntValue = 2;
	read_it.Params().highIntValue = 8;
	read_it.Params().strValue = "Example";
	
	TIMESTAMP_STRUCT paramDate = {2000, 1, 1, 0, 0, 0, 0};
	read_it.Params().dateValue = paramDate;

	for ( ; read_it != view.end();  read_it++)
	{
		cout << "Reading element #" << read_it.GetLastCount() << endl;
		results.push_back(*read_it);

		cout << "read_it->exampleInt = " << read_it->exampleInt << endl;
		cout << "read_it->exampleStr = " << read_it->exampleStr << endl;
		
	}
	
	return results;
}

 

Example 3:

//Default SelVal function to make sure fields in a row selected from the database are valid

// Default select validation behavior ... data is valid if and only if
// there are no columns which are null.
// If there are other checks you wish to make, put them in
// your own SelVal functor.
// You can also specialize this template if you wish to have different default behavior
// for your data class.
template<class DataObj> class DefaultSelValidate {
public:
bool operator()(BoundIOs &boundIOs, DataObj &rowbuf)
{
	for (BoundIOs::iterator b_it = boundIOs.begin();
				b_it != boundIOs.end(); b_it++)
	{
		BoundIO &boundIO = (*b_it).second;
		if (boundIO.IsColumn() && boundIO.IsNull())
			return false;  // found null column ... data is invalid
	}

	return true;	// no nulls found ... data is OK
}
};

Public base classes: BoundIO

None.

Public base classes: BoundIOs

map<string, BoundIO>

Notation:

X A type that is a model of BoundIO
a Object of type X

Expression semantics: BoundIO

Name Expression Precondition Semantics Postcondition
Default constructor
X a()
  Construct an empty BoundIO  
Copy constructor
X a(constX &b)
  Copy construct a BoundIO.  
Assignment operator
X& operator=(const X&b)
  Assignment copy  
Binding operator for INPUT/OUTPUT template<class T> BoundIO operator==(T &memberToBind)   INPUT/OUTPUT parameter type. Create an association between SQL column names / parameter numbers and object field names. This is done by inserting elements into the BoundIOs map. The syntax for inserting elements into the BoundIOs map is to call BoundIOs["SQL FIELD NAME" / SQL Parameter Number] == RowObject.FieldName. This invokes the == operator for the BoundIO class. We use the == syntax here as a mnemonic to remind end users that this is a two way association. Once the association is created, values can either be read from the database to the field, or written from the field to the database. (For a list of supported C++ types see [1]). Internally, this syntax does two things. First, the operator==() analyzes the RowObject.FieldName parameter to determine what type of field we are binding to and the memory address of the given field name (there are some restrictions! see [2]). Based on this information, operator==(), determines the SQL_TYPE, the SQL_C_TYPE, the data size, and the address to pass to SQLBindCol() or SQLBindParam(). All of this information gets stored in what is known as a BoundIO object. Finally, the resulting BoundIO object is inserted into the BoundIOs map using the BoundIOs::operator[]() described below.  
Binding operator to bind an input parameter template<class T> BoundIO operator << (T &memberToBind)   INPUT parameter type. The class member supplies data strictly as an input parameter to a SQL query. (See BPA for details.).
Binding operator to bind an output parameter template<class T> BoundIO operator >> (T &memberToBind)   OUTPUT parameter type. The class member supplies data strictly as an output parameter to a SQL query. (See BPA for details.).
Column Indicator
bool IsColumn()
  Returns true if this BoundIO represents a SQL field.  
Parameter Indicator
bool IsParam()
  Returns true if this BoundIO represents a SQL parameter.  
NULL data Indicator
bool IsNull()
  Returns true if value held in this field by the current rowbuf object holds NULL data. Typically this is used by the SelVal function.  
Set to null void SetNull()   Sets the field to represent a null value.  
Make non-null void ClearNull()   Sets the field to represent a non-null value.  
Set the SQL type void SetSQLType(SDWORD newSqlType)   Override the SQL type used by SQLBindParam when binding this column. By default the SQL type that is used when binding this column as a parameter is as per the ETIMap::build() settings in bind_basics.cpp. This method can be used to override the default SQL binding for the given data type. The available list of SQL types are as per the list of SQL data types for ODBC. This value will be used as the ParameterType argument when a call is made to SQLBindParam if this column is used as a parameter.  
Set the SQL C type void SetCType(SDWORD newCType)   Override the SQL C type used by ODBC when binding this column. By default the SQL C type that is used when binding this column is as per the ETIMap::build() settings in bind_basics.cpp. This method can be used to override the default SQL C binding for the given data type. The available list of SQL C types are as per the list of SQL C data types for ODBC. This value will be used as the ValueType argument when a call is made to either SQLBindParam or SQLBindCol. As an example, the default SQL C type for a char data field is SQL_C_CHAR, but you might want to use SQL_C_TINYINT to hold an integer in a char field instead. Be careful! We do not check the value you set here! If you specify a C data type that contains more bytes then the size of the field ODBC will overflow that field in memory. Similarly, if you specify a C datatype that is too small for the field your field may be only partially initialized.  

Expression semantics: BoundIOs

As per STL map but with the following modifications:

Name Expression Precondition Semantics Postcondition
Parameter operator
BoundIO &operator[](unsigned int paramNum)
  This is used to insert/find a BoundIO in the BoundIOs map using a stringified version of the number as the key. If this operator does not find an existing BoundIO with the given key, it creates a new BoundIO object that is marked as a SQL parameter with the name given by the key string. This new BoundIO object is then inserted into the BoundIOs list. SQL parameters are represented in the SQL string passed to a DBView by a '(?)'.  
Column operator
BoundIO &operator[](const string &colName)
  This is used to insert/find a BoundIO in the BoundIOs map using a the given string as the key. If this operator does not find an existing BoundIO with the given key, it creates a new BoundIO object that is marked as a SQL field with the name given by the key string. This new BoundIO object is then inserted into the BoundIOs list.  

Notes

[1] Supported C++ types for binding to fields in a database:

Supported C++ data types and mappings (see ETI_Map::build() in bind_basics.cpp for details)

C++ type ODBC C Data Type ODBC SQL Data Type
short SQL_C_SSHORT SQL_INTEGER
unsigned short SQL_C_USHORT SQL_INTEGER
int SQL_C_SLONG SQL_INTEGER
unsigned int SQL_C_ULONG SQL_INTEGER
long SQL_C_SLONG SQL_INTEGER
unsigned long SQL_C_ULONG SQL_INTEGER
double SQL_C_DOUBLE SQL_DOUBLE
float SQL_C_FLOAT SQL_FLOAT
char SQL_C_CHAR SQL_CHAR
bool SQL_C_BIT SQL_INTEGER
ODBC TIMESTAMP_STRUCT SQL_C_TIMESTAMP SQL_TIMESTAMP
jtime_c SQL_C_TIMESTAMP SQL_TIMESTAMP
char [xxx] SQL_C_CHAR SQL_VARCHAR
wchar [xxx] SQL_C_WCHAR SQL_WVARCHAR
string <--> basic_string<char> SQL_C_CHAR SQL_VARCHAR
wstring<--> basic_string<wchar_t> SQL_C_WCHAR SQL_WVARCHAR
binary data or blob <--> basic_string<unsigned char> SQL_C_BINARY SQL_VARBINARY

[2] If you are only using this BCA/BPA with a DBView template then the field you are binding to can be either statically or dynamically allocated by the DataObj, as long as the object has a correct copy and assigment operator for copying the field. If you want to use an IndexedDBView with your BCA, then the field must be a statically allocated member of the DataObj. The reason for this is that IndexedDBView computes a relative fieldoffset via &(rowbuf.Field) - &rowbuf that it uses for comparison functions for automatic indexing and update routines in the IndexedDBView. This kind of offset logic is only valid if the field is not dynamically allocated via operator new() or malloc(). So, if you want to use IndexedDBView we recommend that your fields be statically allocated members. (In fact, there is a complicated way around this restriction if you allocate your fields as a contiguous block. You can check out DynamicRowBCA in DynaDBView.h and the data_ptr() function that we define for the variant_row class in variant_row.h for an example of how we worked around this to accomodate dynamically sized rows. The result is not for the faint of heart.) Here is an example of what we are talking about:

#define DYNAMIC_SIZE 100 
class BCADynamicObj;
class Dynamic {
	char **szDynamicString;

public:

	Dynamic() {
		szDynamicString = (char **)malloc(DYNAMIC_SIZE);
	}

	Dynamic(const Dynamic &other) {
		szDynamicString = (char **)malloc(DYNAMIC_SIZE);	
		memcpy(szDynamicString, other.szDynamicString, DYNAMIC_SIZE);
	}

	const Dynamic & operator=(const Dynamic &other) {
		if (this != &other) {
			if (szDynamicString == NULL)
				szDynamicString = (char **)malloc(DYNAMIC_SIZE);	
			memcpy(szDynamicString, other.szDynamicString, DYNAMIC_SIZE);
		}

	}
	

	~Dynamic() {
		if (szDynamicString != NULL)
			free(szDynamicString);
	}

	friend class BCADynamicObj;
};

// This BCA will work with DBView but not IndexedDBView because szDynamicString is dynamic
class BCADynamicObj
{
public:
	void operator()(BoundIOs &boundIOs, Dynamic &rowbuf)
    	{
	   boundIOs["STRING_VALUE"] == *(rowbuf.szDynamicString);
	}
};

See also

BPA, BCA, InsVal, SelVal, DBView, IndexedDBView


[DTL Home]

Copyright © 2002, Michael Gradman and Corwin Joy.

Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appears in all copies and that both that copyright notice and this permission notice appear in supporting documentation. Corwin Joy and Michael Gradman make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.

This site written using the ORB. [The ORB]

1