1 /**
2  * DDBC - D DataBase Connector - abstraction layer for RDBMS access, with interface similar to JDBC. 
3  * 
4  * Source file ddbc/common.d.
5  *
6  * DDBC library attempts to provide implementation independent interface to different databases.
7  * 
8  * Set of supported RDBMSs can be extended by writing Drivers for particular DBs.
9  * Currently it only includes MySQL Driver which uses patched version of MYSQLN (native D implementation of MySQL connector, written by Steve Teale)
10  * 
11  * JDBC documentation can be found here:
12  * $(LINK http://docs.oracle.com/javase/1.5.0/docs/api/java/sql/package-summary.html)$(BR)
13  *
14  * This module contains some useful base class implementations for writing Driver for particular RDBMS.
15  * As well it contains useful class - ConnectionPoolDataSourceImpl - which can be used as connection pool.
16  *
17  * You can find usage examples in unittest{} sections.
18  *
19  * Copyright: Copyright 2013
20  * License:   $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
21  * Author:   Vadim Lopatin
22  */
23 module ddbc.common;
24 import ddbc.core;
25 import std.algorithm;
26 import std.exception;
27 import std.stdio;
28 import std.conv;
29 import std.variant;
30 
31 class DataSourceImpl : DataSource {
32 	Driver driver;
33 	string url;
34 	string[string] params;
35 	this(Driver driver, string url, string[string]params) {
36 		this.driver = driver;
37 		this.url = url;
38 		this.params = params;
39 	}
40 	override Connection getConnection() {
41 		return driver.connect(url, params);
42 	}
43 }
44 
45 interface ConnectionCloseHandler {
46 	void onConnectionClosed(Connection connection);
47 }
48 
49 class ConnectionWrapper : Connection {
50 	private ConnectionCloseHandler pool;
51 	private Connection base;
52 	private bool closed;
53 
54 	this(ConnectionCloseHandler pool, Connection base) {
55 		this.pool = pool;
56 		this.base = base;
57 	}
58 	override void close() { 
59 		assert(!closed, "Connection is already closed");
60 		closed = true; 
61 		pool.onConnectionClosed(base); 
62 	}
63 	override PreparedStatement prepareStatement(string query) { return base.prepareStatement(query); }
64 	override void commit() { base.commit(); }
65 	override Statement createStatement() { return base.createStatement(); }
66 	override string getCatalog() { return base.getCatalog(); }
67 	override bool isClosed() { return closed; }
68 	override void rollback() { base.rollback(); }
69 	override bool getAutoCommit() { return base.getAutoCommit(); }
70 	override void setAutoCommit(bool autoCommit) { base.setAutoCommit(autoCommit); }
71 	override void setCatalog(string catalog) { base.setCatalog(catalog); }
72 }
73 // some bug in std.algorithm.remove? length is not decreased... - under linux x64 dmd
74 static void myRemove(T)(ref T[] array, size_t index) {
75     for (auto i = index; i < array.length - 1; i++) {
76         array[i] = array[i + 1];
77     }
78     array[array.length - 1] = T.init;
79     array.length--;
80 }
81 
82 // TODO: implement limits
83 // TODO: thread safety
84 class ConnectionPoolDataSourceImpl : DataSourceImpl, ConnectionCloseHandler {
85 private:
86 	int maxPoolSize;
87 	int timeToLive;
88 	int waitTimeOut;
89 
90 	Connection [] activeConnections;
91 	Connection [] freeConnections;
92 
93 public:
94 
95 	this(Driver driver, string url, string[string]params, int maxPoolSize = 1, int timeToLive = 600, int waitTimeOut = 30) {
96 		super(driver, url, params);
97 		this.maxPoolSize = maxPoolSize;
98 		this.timeToLive = timeToLive;
99 		this.waitTimeOut = waitTimeOut;
100 	}
101 
102 	override Connection getConnection() {
103 		Connection conn = null;
104         //writeln("getConnection(): freeConnections.length = " ~ to!string(freeConnections.length));
105         if (freeConnections.length > 0) {
106             //writeln("getConnection(): returning free connection");
107             conn = freeConnections[freeConnections.length - 1]; // $ - 1
108             auto oldSize = freeConnections.length;
109             myRemove(freeConnections, freeConnections.length - 1);
110             //freeConnections.length = oldSize - 1; // some bug in remove? length is not decreased...
111             auto newSize = freeConnections.length;
112             assert(newSize == oldSize - 1);
113         } else {
114             //writeln("getConnection(): creating new connection");
115             try {
116                 conn = super.getConnection();
117             } catch (Throwable e) {
118                 //writeln("exception while creating connection " ~ e.msg);
119                 throw e;
120             }
121             //writeln("getConnection(): connection created");
122         }
123         auto oldSize = activeConnections.length;
124         activeConnections ~= conn;
125         auto newSize = activeConnections.length;
126         assert(oldSize == newSize - 1);
127         auto wrapper = new ConnectionWrapper(this, conn);
128 		return wrapper;
129 	}
130 
131 	void removeUsed(Connection connection) {
132         //writeln("removeUsed");
133         //writeln("removeUsed - activeConnections.length=" ~ to!string(activeConnections.length));
134 		foreach (i, item; activeConnections) {
135 			if (item == connection) {
136                 auto oldSize = activeConnections.length;
137 				//std.algorithm.remove(activeConnections, i);
138                 myRemove(activeConnections, i);
139                 //activeConnections.length = oldSize - 1;
140                 auto newSize = activeConnections.length;
141                 assert(oldSize == newSize + 1);
142                 return;
143 			}
144 		}
145 		throw new SQLException("Connection being closed is not found in pool");
146 	}
147 
148 	override void onConnectionClosed(Connection connection) {
149         //writeln("onConnectionClosed");
150         assert(connection !is null);
151         //writeln("calling removeUsed");
152         removeUsed(connection);
153         //writeln("adding to free list");
154         auto oldSize = freeConnections.length;
155         freeConnections ~= connection;
156         auto newSize = freeConnections.length;
157         assert(newSize == oldSize + 1);
158     }
159 }
160 
161 // Helper implementation of ResultSet - throws Method not implemented for most of methods.
162 class ResultSetImpl : ddbc.core.ResultSet {
163 public:
164     override int opApply(int delegate(DataSetReader) dg) { 
165         int result = 0;
166         if (!first())
167             return 0;
168         do { 
169             result = dg(cast(DataSetReader)this); 
170             if (result) break; 
171         } while (next());
172         return result; 
173     }
174     override void close() {
175 		throw new SQLException("Method not implemented");
176 	}
177 	override bool first() {
178 		throw new SQLException("Method not implemented");
179 	}
180 	override bool isFirst() {
181 		throw new SQLException("Method not implemented");
182 	}
183 	override bool isLast() {
184 		throw new SQLException("Method not implemented");
185 	}
186 	override bool next() {
187 		throw new SQLException("Method not implemented");
188 	}
189 	
190 	override int findColumn(string columnName) {
191 		throw new SQLException("Method not implemented");
192 	}
193 	override bool getBoolean(int columnIndex) {
194 		throw new SQLException("Method not implemented");
195 	}
196 	override bool getBoolean(string columnName) {
197 		return getBoolean(findColumn(columnName));
198 	}
199 	override ubyte getUbyte(int columnIndex) {
200 		throw new SQLException("Method not implemented");
201 	}
202 	override ubyte getUbyte(string columnName) {
203 		return getUbyte(findColumn(columnName));
204 	}
205 	override byte getByte(int columnIndex) {
206 		throw new SQLException("Method not implemented");
207 	}
208 	override byte getByte(string columnName) {
209 		return getByte(findColumn(columnName));
210 	}
211 	override byte[] getBytes(int columnIndex) {
212 		throw new SQLException("Method not implemented");
213 	}
214 	override byte[] getBytes(string columnName) {
215 		return getBytes(findColumn(columnName));
216 	}
217 	override ubyte[] getUbytes(int columnIndex) {
218 		throw new SQLException("Method not implemented");
219 	}
220 	override ubyte[] getUbytes(string columnName) {
221 		return getUbytes(findColumn(columnName));
222 	}
223 	override short getShort(int columnIndex) {
224 		throw new SQLException("Method not implemented");
225 	}
226 	override short getShort(string columnName) {
227 		return getShort(findColumn(columnName));
228 	}
229 	override ushort getUshort(int columnIndex) {
230 		throw new SQLException("Method not implemented");
231 	}
232 	override ushort getUshort(string columnName) {
233 		return getUshort(findColumn(columnName));
234 	}
235 	override int getInt(int columnIndex) {
236 		throw new SQLException("Method not implemented");
237 	}
238 	override int getInt(string columnName) {
239 		return getInt(findColumn(columnName));
240 	}
241 	override uint getUint(int columnIndex) {
242 		throw new SQLException("Method not implemented");
243 	}
244 	override uint getUint(string columnName) {
245 		return getUint(findColumn(columnName));
246 	}
247 	override long getLong(int columnIndex) {
248 		throw new SQLException("Method not implemented");
249 	}
250 	override long getLong(string columnName) {
251 		return getLong(findColumn(columnName));
252 	}
253 	override ulong getUlong(int columnIndex) {
254 		throw new SQLException("Method not implemented");
255 	}
256 	override ulong getUlong(string columnName) {
257 		return getUlong(findColumn(columnName));
258 	}
259 	override double getDouble(int columnIndex) {
260 		throw new SQLException("Method not implemented");
261 	}
262 	override double getDouble(string columnName) {
263 		return getDouble(findColumn(columnName));
264 	}
265 	override float getFloat(int columnIndex) {
266 		throw new SQLException("Method not implemented");
267 	}
268 	override float getFloat(string columnName) {
269 		return getFloat(findColumn(columnName));
270 	}
271 	override string getString(int columnIndex) {
272 		throw new SQLException("Method not implemented");
273 	}
274 	override string getString(string columnName) {
275 		return getString(findColumn(columnName));
276 	}
277     override Variant getVariant(int columnIndex) {
278         throw new SQLException("Method not implemented");
279     }
280     override Variant getVariant(string columnName) {
281         return getVariant(findColumn(columnName));
282     }
283 
284 	override bool wasNull() {
285 		throw new SQLException("Method not implemented");
286 	}
287 
288 	override bool isNull(int columnIndex) {
289 		throw new SQLException("Method not implemented");
290 	}
291 
292 	//Retrieves the number, types and properties of this ResultSet object's columns
293 	override ResultSetMetaData getMetaData() {
294 		throw new SQLException("Method not implemented");
295 	}
296 	//Retrieves the Statement object that produced this ResultSet object.
297 	override Statement getStatement() {
298 		throw new SQLException("Method not implemented");
299 	}
300 	//Retrieves the current row number
301 	override int getRow() {
302 		throw new SQLException("Method not implemented");
303 	}
304 	//Retrieves the fetch size for this ResultSet object.
305 	override int getFetchSize() {
306 		throw new SQLException("Method not implemented");
307 	}
308 	override std.datetime.DateTime getDateTime(int columnIndex) {
309 		throw new SQLException("Method not implemented");
310 	}
311 	override std.datetime.Date getDate(int columnIndex) {
312 		throw new SQLException("Method not implemented");
313 	}
314 	override std.datetime.TimeOfDay getTime(int columnIndex) {
315 		throw new SQLException("Method not implemented");
316 	}
317 }
318 
319 class ColumnMetadataItem {
320 	string 	catalogName;
321 	int	    displaySize;
322 	string 	label;
323 	string  name;
324 	int 	type;
325 	string 	typeName;
326 	int     precision;
327 	int     scale;
328 	string  schemaName;
329 	string  tableName;
330 	bool 	isAutoIncrement;
331 	bool 	isCaseSensitive;
332 	bool 	isCurrency;
333 	bool 	isDefinitelyWritable;
334 	int 	isNullable;
335 	bool 	isReadOnly;
336 	bool 	isSearchable;
337 	bool 	isSigned;
338 	bool 	isWritable;
339 }
340 
341 class ParameterMetaDataItem {
342 	/// Retrieves the designated parameter's mode.
343 	int mode;
344 	/// Retrieves the designated parameter's SQL type.
345 	int type;
346 	/// Retrieves the designated parameter's database-specific type name.
347 	string typeName;
348 	/// Retrieves the designated parameter's number of decimal digits.
349 	int precision;
350 	/// Retrieves the designated parameter's number of digits to right of the decimal point.
351 	int scale;
352 	/// Retrieves whether null values are allowed in the designated parameter.
353 	int isNullable;
354 	/// Retrieves whether values for the designated parameter can be signed numbers.
355 	bool isSigned;
356 }
357 
358 class ParameterMetaDataImpl : ParameterMetaData {
359 	ParameterMetaDataItem [] cols;
360 	this(ParameterMetaDataItem [] cols) {
361 		this.cols = cols;
362 	}
363 	ref ParameterMetaDataItem col(int column) {
364 		enforceEx!SQLException(column >=1 && column <= cols.length, "Parameter index out of range");
365 		return cols[column - 1];
366 	}
367 	// Retrieves the fully-qualified name of the Java class whose instances should be passed to the method PreparedStatement.setObject.
368 	//String getParameterClassName(int param);
369 	/// Retrieves the number of parameters in the PreparedStatement object for which this ParameterMetaData object contains information.
370 	int getParameterCount() {
371 		return cast(int)cols.length;
372 	}
373 	/// Retrieves the designated parameter's mode.
374 	int getParameterMode(int param) { return col(param).mode; }
375 	/// Retrieves the designated parameter's SQL type.
376 	int getParameterType(int param) { return col(param).type; }
377 	/// Retrieves the designated parameter's database-specific type name.
378 	string getParameterTypeName(int param) { return col(param).typeName; }
379 	/// Retrieves the designated parameter's number of decimal digits.
380 	int getPrecision(int param) { return col(param).precision; }
381 	/// Retrieves the designated parameter's number of digits to right of the decimal point.
382 	int getScale(int param) { return col(param).scale; }
383 	/// Retrieves whether null values are allowed in the designated parameter.
384 	int isNullable(int param) { return col(param).isNullable; }
385 	/// Retrieves whether values for the designated parameter can be signed numbers.
386 	bool isSigned(int param) { return col(param).isSigned; }
387 }
388 
389 class ResultSetMetaDataImpl : ResultSetMetaData {
390 	ColumnMetadataItem [] cols;
391 	this(ColumnMetadataItem [] cols) {
392 		this.cols = cols;
393 	}
394 	ref ColumnMetadataItem col(int column) {
395 		enforceEx!SQLException(column >=1 && column <= cols.length, "Column index out of range");
396 		return cols[column - 1];
397 	}
398 	//Returns the number of columns in this ResultSet object.
399 	override int getColumnCount() { return cast(int)cols.length; }
400 	// Gets the designated column's table's catalog name.
401 	override string getCatalogName(int column) { return col(column).catalogName; }
402 	// Returns the fully-qualified name of the Java class whose instances are manufactured if the method ResultSet.getObject is called to retrieve a value from the column.
403 	//override string getColumnClassName(int column) { return col(column).catalogName; }
404 	// Indicates the designated column's normal maximum width in characters.
405 	override int getColumnDisplaySize(int column) { return col(column).displaySize; }
406 	// Gets the designated column's suggested title for use in printouts and displays.
407 	override string getColumnLabel(int column) { return col(column).label; }
408 	// Get the designated column's name.
409 	override string getColumnName(int column) { return col(column).name; }
410 	// Retrieves the designated column's SQL type.
411 	override int getColumnType(int column) { return col(column).type; }
412 	// Retrieves the designated column's database-specific type name.
413 	override string getColumnTypeName(int column) { return col(column).typeName; }
414 	// Get the designated column's number of decimal digits.
415 	override int getPrecision(int column) { return col(column).precision; }
416 	// Gets the designated column's number of digits to right of the decimal point.
417 	override int getScale(int column) { return col(column).scale; }
418 	// Get the designated column's table's schema.
419 	override string getSchemaName(int column) { return col(column).schemaName; }
420 	// Gets the designated column's table name.
421 	override string getTableName(int column) { return col(column).tableName; }
422 	// Indicates whether the designated column is automatically numbered, thus read-only.
423 	override bool isAutoIncrement(int column) { return col(column).isAutoIncrement; }
424 	// Indicates whether a column's case matters.
425 	override bool isCaseSensitive(int column) { return col(column).isCaseSensitive; }
426 	// Indicates whether the designated column is a cash value.
427 	override bool isCurrency(int column) { return col(column).isCurrency; }
428 	// Indicates whether a write on the designated column will definitely succeed.
429 	override bool isDefinitelyWritable(int column) { return col(column).isDefinitelyWritable; }
430 	// Indicates the nullability of values in the designated column.
431 	override int isNullable(int column) { return col(column).isNullable; }
432 	// Indicates whether the designated column is definitely not writable.
433 	override bool isReadOnly(int column) { return col(column).isReadOnly; }
434 	// Indicates whether the designated column can be used in a where clause.
435 	override bool isSearchable(int column) { return col(column).isSearchable; }
436 	// Indicates whether values in the designated column are signed numbers.
437 	override bool isSigned(int column) { return col(column).isSigned; }
438 	// Indicates whether it is possible for a write on the designated column to succeed.
439 	override bool isWritable(int column) { return col(column).isWritable; }
440 }
441 
442 version (unittest) {
443     void unitTestExecuteBatch(Connection conn, string[] queries) {
444         Statement stmt = conn.createStatement();
445         foreach(query; queries) {
446 			//writeln("query:" ~ query);
447             stmt.executeUpdate(query);
448         }
449     }
450 }