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 }