ODBC Class

So I am trying to write my own ODBC class to make my connections and queries from ODBC more object oriented and less procedural. I know my approaches may be completely off, so please bare with me. It doesn't work quite right, particularly when using the ODBC_Database::Fetch() method. Connect() and Execute() seem to work fine. Any advice would be much appreciated.

Thanks!

PS - I am learning this stuff on my own (particularly from this website) so if my technique is horrible, feel free to give me some pointers - no pun intended.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#ifndef __easyodbc_h__
#define __easyodbc_h__

#ifndef __stdio_h__
#include <stdio.h>
#endif

#ifndef __stdlib_h__
#include <stdlib.h>
#endif

#ifndef __windows_h__
#include <windows.h>
#endif

#ifndef __sql_h__
#include <sql.h>
#endif

#ifndef __sqltypes_h__
#include <sqltypes.h>
#endif

#ifndef __sqlext_h__
#include <sqlext.h>
#endif

#ifndef __string__
#include <string>
#endif

#ifndef __vector__
#include <vector>
#endif

#pragma comment( lib, "odbc32.lib" )

class ODBC_Database;
class ODBC_Recordset;

class ODBC_Database
{
protected:
	//members
	HWND desktopHandle;
	SQLHANDLE hEnv, hConn, hStmt;
	SQLRETURN retcode;
	SQLCHAR OutConnStr[255];
	SQLSMALLINT OutConnStrLen;
	ODBC_Recordset *recordset;
	short errnum;
	std::string errmsg;

public:
	//constructor
	ODBC_Database( ODBC_Recordset *rs );

	//methods
	bool Connect( std::string ConnStr , bool PromptOnFail );
	bool Execute( std::string SqlCmd );
	bool Fetch();
	bool Close();
	short ErrNum() { return errnum; }
	std::string ErrMsg() { return errmsg.c_str(); }

	friend class ODBC_Recordset;
};

class ODBC_Recordset
{
protected:
	//members
	ODBC_Database *database;
	SQLINTEGER numRows;
	SQLSMALLINT numCols;
	std::vector<std::string> values;

public:
	//constructor
	ODBC_Recordset( ODBC_Database* db );
	std::string GetValue( SQLSMALLINT index );
	std::string operator [] ( SQLSMALLINT index );

	friend class ODBC_Database;
};

ODBC_Database::ODBC_Database( ODBC_Recordset *rs )
{
	//get desktop handle
	desktopHandle = GetDesktopWindow();

	//create recordset
	recordset = new ODBC_Recordset( this );

	rs = recordset;
	
	//set initial error values
	errnum = 0;
	errmsg = "";

}

bool ODBC_Database::Connect(
	std::string ConnStr = "",
	bool PromptOnFail = true )
{
	//allocate handle for environment
	retcode = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv );
	if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
	{
		errnum = 1;
		errmsg = "Unable to allocate handle for environment.";
		return false;
	}

	//set odbc version
	retcode = SQLSetEnvAttr( hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0 );
	if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
	{
		errnum = 2;
		errmsg = "Unable to set ODBC version.";
		return false;
	}

	//allocate handle for connection
	retcode = SQLAllocHandle( SQL_HANDLE_DBC, hEnv, &hConn );
	if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
	{
		errnum = 3;
		errmsg = "Unable to allocate handle for connection.";
		return false;
	}

	//attempt to connect with supplied info
	retcode = SQLDriverConnect(
		hConn,
		desktopHandle,
		(SQLCHAR*)ConnStr.c_str(),
		ConnStr.size(),
		OutConnStr,
		255,
		&OutConnStrLen,
		SQL_DRIVER_NOPROMPT );

	if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
	{
		if( !PromptOnFail )
		{
			errnum = 4;
			errmsg = "Unable to connect to data source with the supplied information.";
			return false;
		}

		//if failed, provider user with prompt
		retcode = SQLDriverConnect(
			hConn,
			desktopHandle,
			(SQLCHAR*)ConnStr.c_str(),
			ConnStr.size(),
			OutConnStr,
			255,
			&OutConnStrLen,
			SQL_DRIVER_PROMPT );

			if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
			{
				errnum = 5;
				errmsg = "Unable to connect to data source with the supplied information.";
				return false;
			}
	}

	//if everything succeeded
	return true;
}

bool ODBC_Database::Execute( std::string SqlCmd )
{
	//allocate handle for statement
	retcode = SQLAllocHandle( SQL_HANDLE_STMT, hConn, &hStmt );
	if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
	{
		errnum = 6;
		errmsg = "Unable to allocate handle for statement.";
		return false;
	}

	//execute SQL command
	retcode = SQLExecDirect( hStmt, (SQLCHAR*)SqlCmd.c_str(), SqlCmd.size() );
	if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
	{
		errnum = 7;
		errmsg = "Unable to execute SQL command.  Please check for syntax errors.";
		return false;
	}

	//read result data
	retcode =  SQLFetch( hStmt );
	if( retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
	{
		errnum = 8;
		errmsg = "Unable to read result data.";
		return false;
	}
	
	//get row and column counts
	retcode = SQLRowCount( hStmt, &(recordset->numRows) );
	retcode = SQLNumResultCols( hStmt, &(recordset->numCols) );
	
	//if everything succeeded
	return true;
}

bool ODBC_Database::Fetch()
{
	char buf[256];
	SQLINTEGER numBytes;
	
	recordset->values.clear();
	
	//iterate through columns
	for( int c = 1; c <= recordset->numCols; c++ )
	{
		retcode = SQLGetData( hStmt, c, SQL_C_CHAR, buf, 255, &numBytes );
		recordset->values.push_back( buf );
	}
	
	retcode = SQLFetch( hStmt );
	
	return ( retcode == SQL_SUCCESS );
}

bool ODBC_Database::Close()
{
	delete recordset;
	SQLFreeHandle( SQL_HANDLE_STMT, hStmt );
	SQLFreeHandle( SQL_HANDLE_DBC, hConn );
	SQLFreeHandle( SQL_HANDLE_ENV, hEnv );
	return true;
}

ODBC_Recordset::ODBC_Recordset( ODBC_Database *db )
{
	database = db;
	numRows = 0;
	numCols = 0;
	values.push_back("");
}

std::string ODBC_Recordset::GetValue( SQLSMALLINT index )
{
	return values.at( index );
}

std::string ODBC_Recordset::operator [](SQLSMALLINT index)
{
	return values.at( index );
}

#endif 
So I figured out that the issue was with passing the ODBC_Recordset* to the ODBC_Database constructor. I did it differently for now, but why does that not work? Can I not modify a pointer address within a constructor? Here is what the code was originally...

1
2
3
4
5
6
7
8
9
int main()
{
  ODBC_Recordset* rs = 0;  //if I don't do this, I get an error that the pointer was not initialized
  ODBC_Database* MyDB = new ODBC_Database( rs );
  /* checking with the compiler, rs still points to 0x00000000...why?
      this causes the ODBC_Database::Fetch() method to fail... */
  return 0;
}


I am probably missing something obvious, but for now I'm stumped.
I'm sure someone knows the answer...why can I not do this? Here is the ODBC_Database constructor -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ODBC_Database::ODBC_Database( ODBC_Recordset *rs )
{
	//get desktop handle
	desktopHandle = GetDesktopWindow();

	//create recordset
	recordset = new ODBC_Recordset( this );

	rs = recordset;
	
	//set initial error values
	errnum = 0;
	errmsg = "";

}

Topic archived. No new replies allowed.