1 #include "dbixx.h"
2 #include <stdio.h>
3 #include <limits>
4 #include <iomanip>
5 #include <sstream>
6 
7 namespace dbixx {
8 
9 using namespace std;
10 
11 
12 class loader {
13 public:
loader()14 	loader() { dbi_initialize(NULL); };
~loader()15 	~loader() { dbi_shutdown(); };
16 };
17 
18 static loader backend_loader;
19 
session()20 session::session()
21 {
22 	conn=NULL;
23 }
24 
connect(std::string const & connection_string)25 void session::connect(std::string const &connection_string)
26 {
27 	size_t p = connection_string.find(':');
28 	if( p == std::string::npos )
29 		throw dbixx_error("Invalid connection string - no driver given");
30 	driver(connection_string.substr(0,p));
31 	p++;
32 	while(p<connection_string.size()) {
33 		size_t n=connection_string.find('=',p);
34 		if(n==std::string::npos)
35 			throw dbixx_error("Invalid connection string - invalid property");
36 		std::string key = connection_string.substr(p,n-p);
37 		p=n+1;
38 		std::string value;
39 		bool is_string = true;
40 		if(p>=connection_string.size()) {
41 			/// Nothing - empty property
42 		}
43 		else if(connection_string[p]=='\'') {
44 			p++;
45 			while(true) {
46 				if(p>=connection_string.size()) {
47 					throw dbixx_error("Invalid connection string unterminated string");
48 				}
49 				if(connection_string[p]=='\'') {
50 					if(p+1 < connection_string.size() && connection_string[p+1]=='\'') {
51 						value+='\'';
52 						p+=2;
53 					}
54 					else {
55 						p++;
56 						break;
57 					}
58 				}
59 				else {
60 					value+=connection_string[p];
61 					p++;
62 				}
63 			}
64 		}
65 		else {
66 			size_t n=connection_string.find(';',p);
67 			if(n==std::string::npos) {
68 				value=connection_string.substr(p);
69 				p=connection_string.size();
70 			}
71 			else {
72 				value=connection_string.substr(p,n-p);
73 				p=n;
74 			}
75 			if(!value.empty()) {
76 				size_t pos = 0;
77 				if(value[0]=='-') {
78 					pos = 1;
79 				}
80 				bool digit_detected = false;
81 				bool only_digits = true;
82 				while(pos < value.size() && only_digits) {
83 					if('0'<=value[pos] && value[pos]<='9')
84 						digit_detected = true;
85 					else
86 						only_digits = false;
87 					pos++;
88 				}
89 				if(only_digits && digit_detected) {
90 					is_string = false;
91 				}
92 			}
93 		}
94 		if(is_string) {
95 			param(key,value);
96 		}
97 		else {
98 			param(key,atoi(value.c_str()));
99 		}
100 		if(p < connection_string.size() && connection_string[p]==';')
101 			++p;
102 
103 	}
104 	connect();
105 }
106 
connect()107 void session::connect()
108 {
109 	check_open();
110 	map<string,string>::const_iterator sp;
111 	for(sp=string_params.begin();sp!=string_params.end();sp++){
112 		if(dbi_conn_set_option(conn,sp->first.c_str(),sp->second.c_str())) {
113 			error();
114 		}
115 	}
116 
117 	map<string,int>::const_iterator ip;
118 	for(ip=numeric_params.begin();ip!=numeric_params.end();ip++){
119 		if(dbi_conn_set_option_numeric(conn,ip->first.c_str(),ip->second)) {
120 			error();
121 		}
122 	}
123 
124 	if(dbi_conn_connect(conn)<0) {
125 		error();
126 	}
127 }
128 
reconnect()129 void session::reconnect()
130 {
131 	close();
132 	driver(this->backend);
133 	connect();
134 }
135 
session(string const & backend_or_conn_str)136 session::session(string const &backend_or_conn_str)
137 {
138 	conn=NULL;
139 
140 	if(backend_or_conn_str.find(':')==std::string::npos)
141 		driver(backend_or_conn_str);
142 	else
143 		connect(backend_or_conn_str);
144 }
145 
close()146 void session::close()
147 {
148 	if(conn) {
149 		dbi_conn_close(conn);
150 		conn=NULL;
151 	}
152 }
153 
~session()154 session::~session()
155 {
156 	close();
157 }
158 
driver(string const & backend)159 void session::driver(string const &backend)
160 {
161 	close();
162 	this->backend=backend;
163 	conn=dbi_conn_new(backend.c_str());
164 	if(!conn) {
165 		throw dbixx_error("Failed to load backend");
166 	}
167 }
168 
driver()169 std::string session::driver()
170 {
171 	return backend;
172 }
173 
check_open(void)174 void session::check_open(void)
175 {
176 	if(!conn) throw dbixx_error("Backend is not open");
177 }
178 
rowid(char const * name)179 unsigned long long session::rowid(char const *name)
180 {
181 	check_open();
182 	return dbi_conn_sequence_last(conn,name);
183 }
184 
error()185 void session::error()
186 {
187 	char const *e;
188 	dbi_conn_error(conn,&e);
189 	throw dbixx_error(e,escaped_query);
190 }
191 
param(string const & par,string const & val)192 void session::param(string const &par,string const &val)
193 {
194 	string_params[par]=val;
195 }
196 
param(string const & par,int val)197 void session::param(string const &par,int val)
198 {
199 	numeric_params[par]=val;
200 }
201 
escape()202 void session::escape()
203 {
204 	while(pos_read<query_in.size()) {
205 		if(query_in[pos_read]=='\'') {
206 			escaped_query+='\'';
207 			pos_read++;
208 			while(query_in[pos_read]!='\'' && pos_read!=query_in.size()){
209 				escaped_query+=query_in[pos_read];
210 				pos_read++;
211 			}
212 			if(pos_read==query_in.size())
213 				throw dbixx_error("Unexpected end of query after \"'\"");
214 			escaped_query+='\'';
215 			pos_read++;
216 			continue;
217 		}
218 		if(query_in[pos_read]=='?') {
219 			ready_for_input=true;
220 			pos_read++;
221 			break;
222 		}
223 		escaped_query+=query_in[pos_read];
224 		pos_read++;
225 	}
226 	if(ready_for_input)
227 		return;
228 	if(pos_read==query_in.size()) {
229 		complete=true;
230 		return;
231 	}
232 	throw dbixx_error("Internal dbixx error");
233 }
234 
check_input()235 void session::check_input()
236 {
237 	if(!ready_for_input) {
238 		throw dbixx_error("More parameters given then inputs in query");
239 	}
240 }
241 
242 template<typename T>
do_bind(T v,bool is_null)243 void session::do_bind(T v,bool is_null)
244 {
245 	check_input();
246 	// The representation of a number in decimal form
247 	// is more compact then in binary so it should be enough
248 	if(is_null) {
249 		escaped_query+="NULL";
250 	}
251 	else {
252 		std::ostringstream ss;
253 		ss.imbue(std::locale::classic());
254 
255 		if(!std::numeric_limits<T>::is_integer) {
256 			ss<<std::setprecision(std::numeric_limits<T>::digits10+1);
257 		}
258 
259 		ss << v;
260 		escaped_query+=ss.str();
261 	}
262 	ready_for_input=false;
263 	escape();
264 }
265 
bind(int v,bool isnull)266 void session::bind(int v,bool isnull) { do_bind(v,isnull); }
bind(unsigned v,bool isnull)267 void session::bind(unsigned v,bool isnull) { do_bind(v,isnull); }
bind(long v,bool isnull)268 void session::bind(long v,bool isnull) { do_bind(v,isnull); }
bind(unsigned long v,bool isnull)269 void session::bind(unsigned long v,bool isnull) { do_bind(v,isnull); }
bind(long long v,bool isnull)270 void session::bind(long long v,bool isnull) { do_bind(v,isnull); }
bind(unsigned long long v,bool isnull)271 void session::bind(unsigned long long v,bool isnull) { do_bind(v,isnull); }
bind(double v,bool isnull)272 void session::bind(double v,bool isnull) { do_bind(v,isnull); }
bind(long double v,bool isnull)273 void session::bind(long double v,bool isnull) { do_bind(v,isnull); }
274 
bind(std::tm const & v,bool isnull)275 void session::bind(std::tm const &v,bool isnull)
276 {
277 	check_input();
278 	// The representation of a number in decimal form
279 	// is more compact then in binary so it should be enough
280 	if(isnull) {
281 		escaped_query+="NULL";
282 	}
283 	else {
284 		std::ostringstream ss;
285 		ss.imbue(std::locale::classic());
286 		ss<<std::setfill('0');
287 		ss <<"'";
288 		ss << std::setw(4) << v.tm_year+1900 <<'-';
289 		ss << std::setw(2) << v.tm_mon+1 <<'-';
290 		ss << std::setw(2) << v.tm_mday <<' ';
291 		ss << std::setw(2) << v.tm_hour <<':';
292 		ss << std::setw(2) << v.tm_min  <<':';
293 		ss << std::setw(2) << v.tm_sec;
294 		ss <<"'";
295 
296 		escaped_query+=ss.str();
297 	}
298 	ready_for_input=false;
299 	escape();
300 }
301 
bind(null const & m,bool isnull)302 void session::bind(null const &m,bool isnull)
303 {
304 	check_input();
305 	escaped_query+="NULL";
306 	ready_for_input=false;
307 	escape();
308 }
309 
310 
bind(string const & s,bool isnull)311 void session::bind(string const &s,bool isnull)
312 {
313 	check_input();
314 	check_open();
315 	if(isnull) {
316 		escaped_query+="NULL";
317 	}
318 	else {
319 		if(s.size()!=0){
320 			char *new_str=NULL;
321 			size_t sz=dbi_conn_quote_string_copy(conn,s.c_str(),&new_str);
322 			if(sz==0) {
323 				error();
324 			}
325 			try {
326 				escaped_query+=new_str;
327 			}
328 			catch(...) {
329 				free(new_str);
330 				throw;
331 			};
332 			free(new_str);
333 		}
334 		else {
335 			escaped_query+="\'\'";
336 		}
337 	}
338 	ready_for_input=false;
339 	escape();
340 }
341 
342 
343 
query(std::string const & q)344 void session::query(std::string const &q)
345 {
346 	complete=false;
347 	ready_for_input=false;
348 	query_in=q;
349 	pos_read=0;
350 	escaped_query="";
351 	escaped_query.reserve(q.size()*3/2);
352 	pos_write=0;
353 	escape();
354 }
355 
exec()356 void session::exec()
357 {
358 	check_open();
359 	if(!complete)
360 		throw dbixx_error("Not all parameters are bind");
361 	dbi_result res=dbi_conn_query(conn,escaped_query.c_str());
362 	if(!res) error();
363 	if(dbi_result_get_numrows(res)!=0) {
364 		dbi_result_free(res);
365 		throw dbixx_error("exec() query may not return results");
366 	}
367 	affected_rows=dbi_result_get_numrows_affected(res);
368 	dbi_result_free(res);
369 }
370 
fetch(result & r)371 void session::fetch(result &r)
372 {
373 	check_open();
374 	if(!complete)
375 		throw dbixx_error("Not all parameters are bind");
376 	dbi_result res=dbi_conn_query(conn,escaped_query.c_str());
377 	if(!res) error();
378 	r.assign(res);
379 }
380 
single(row & r)381 bool session::single(row &r)
382 {
383 	check_open();
384 	if(!complete)
385 		throw dbixx_error("Not all parameters are bind");
386 	dbi_result res=dbi_conn_query(conn,escaped_query.c_str());
387 	if(!res) error();
388 	int n;
389 	if((n=dbi_result_get_numrows(res))!=0 && n!=1) {
390 		dbi_result_free(res);
391 		throw dbixx_error("signle() must return 1 or 0 rows");
392 	}
393 	if(n==1) {
394 		r.assign(res);
395 		return true;
396 	}
397 	else {
398 		r.reset();
399 	}
400 	return false;
401 }
402 
~transaction()403 transaction::~transaction()
404 {
405 	if(!commited){
406 		try {
407 			sql<<"ROLLBACK",exec();
408 		}
409 		catch(...){}
410 	}
411 }
412 
begin()413 void transaction::begin()
414 {
415 	sql<<"BEGIN",exec();
416 }
417 
commit()418 void transaction::commit()
419 {
420 	sql<<"COMMIT",exec();
421 	commited=true;
422 }
423 
rollback()424 void transaction::rollback()
425 {
426 	sql<<"ROLLBACK",exec();
427 	commited=true;
428 }
429 
430 } // END OF NAMESPACE DBIXX
431