1 /**
2  * DDBC - D DataBase Connector - abstraction layer for RDBMS access, with interface similar to JDBC. 
3  * 
4  * Source file ddbc/core.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  * Limitations of current version: readonly unidirectional resultset, completely fetched into memory.
15  * 
16  * Its primary objects are:
17  * $(UL
18  *    $(LI Driver: $(UL $(LI Implements interface to particular RDBMS, used to create connections)))
19  *    $(LI Connection: $(UL $(LI Connection to the server, and querying and setting of server parameters.)))
20  *    $(LI Statement: Handling of general SQL requests/queries/commands, with principal methods:
21  *       $(UL $(LI executeUpdate() - run query which doesn't return result set.)
22  *            $(LI executeQuery() - execute query which returns ResultSet interface to access rows of result.)
23  *        )
24  *    )
25  *    $(LI PreparedStatement: Handling of general SQL requests/queries/commands which having additional parameters, with principal methods:
26  *       $(UL $(LI executeUpdate() - run query which doesn't return result set.)
27  *            $(LI executeQuery() - execute query which returns ResultSet interface to access rows of result.)
28  *            $(LI setXXX() - setter methods to bind parameters.)
29  *        )
30  *    )
31  *    $(LI ResultSet: $(UL $(LI Get result of query row by row, accessing individual fields.)))
32  * )
33  *
34  * You can find usage examples in unittest{} sections.
35  *
36  * Copyright: Copyright 2013
37  * License:   $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
38  * Author:   Vadim Lopatin
39  */
40 module dstruct.ddbc.core;
41 
42 import std.exception;
43 import std.variant;
44 import std.datetime;
45 
46 class SQLException : Exception {
47     protected string _stateString;
48     this(string msg, string stateString, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); _stateString = stateString; }
49     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
50     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, causedBy, f, l); }
51     this(string msg, Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, causedBy, f, l); }
52     this(string msg, string stateString, Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, causedBy, f, l); _stateString = stateString; }
53 }
54 
55 class SQLWarning {
56     // stub
57 }
58 
59 /// JDBC java.sql.Types from http://docs.oracle.com/javase/6/docs/api/java/sql/Types.html
60 enum SqlType {
61 	//sometimes referred to as a type code, that identifies the generic SQL type ARRAY.
62 	//ARRAY,
63 	///sometimes referred to as a type code, that identifies the generic SQL type BIGINT.
64 	BIGINT,
65 	///sometimes referred to as a type code, that identifies the generic SQL type BINARY.
66 	//BINARY,
67 	//sometimes referred to as a type code, that identifies the generic SQL type BIT.
68 	BIT,
69 	///sometimes referred to as a type code, that identifies the generic SQL type BLOB.
70 	BLOB,
71 	///somtimes referred to as a type code, that identifies the generic SQL type BOOLEAN.
72 	BOOLEAN,
73 	///sometimes referred to as a type code, that identifies the generic SQL type CHAR.
74 	CHAR,
75 	///sometimes referred to as a type code, that identifies the generic SQL type CLOB.
76 	CLOB,
77 	//somtimes referred to as a type code, that identifies the generic SQL type DATALINK.
78 	//DATALINK,
79 	///sometimes referred to as a type code, that identifies the generic SQL type DATE.
80 	DATE,
81 	///sometimes referred to as a type code, that identifies the generic SQL type DATETIME.
82 	DATETIME,
83 	///sometimes referred to as a type code, that identifies the generic SQL type DECIMAL.
84 	DECIMAL,
85 	//sometimes referred to as a type code, that identifies the generic SQL type DISTINCT.
86 	//DISTINCT,
87 	///sometimes referred to as a type code, that identifies the generic SQL type DOUBLE.
88 	DOUBLE,
89 	///sometimes referred to as a type code, that identifies the generic SQL type FLOAT.
90 	FLOAT,
91 	///sometimes referred to as a type code, that identifies the generic SQL type INTEGER.
92 	INTEGER,
93 	//sometimes referred to as a type code, that identifies the generic SQL type JAVA_OBJECT.
94 	//JAVA_OBJECT,
95 	///sometimes referred to as a type code, that identifies the generic SQL type LONGNVARCHAR.
96 	LONGNVARCHAR,
97 	///sometimes referred to as a type code, that identifies the generic SQL type LONGVARBINARY.
98 	LONGVARBINARY,
99 	///sometimes referred to as a type code, that identifies the generic SQL type LONGVARCHAR.
100 	LONGVARCHAR,
101 	///sometimes referred to as a type code, that identifies the generic SQL type NCHAR
102 	NCHAR,
103 	///sometimes referred to as a type code, that identifies the generic SQL type NCLOB.
104 	NCLOB,
105 	///The constant in the Java programming language that identifies the generic SQL value NULL.
106 	NULL,
107 	///sometimes referred to as a type code, that identifies the generic SQL type NUMERIC.
108 	NUMERIC,
109 	///sometimes referred to as a type code, that identifies the generic SQL type NVARCHAR.
110 	NVARCHAR,
111 	///indicates that the SQL type is database-specific and gets mapped to a object that can be accessed via the methods getObject and setObject.
112 	OTHER,
113 	//sometimes referred to as a type code, that identifies the generic SQL type REAL.
114 	//REAL,
115 	//sometimes referred to as a type code, that identifies the generic SQL type REF.
116 	//REF,
117 	//sometimes referred to as a type code, that identifies the generic SQL type ROWID
118 	//ROWID,
119 	///sometimes referred to as a type code, that identifies the generic SQL type SMALLINT.
120 	SMALLINT,
121 	//sometimes referred to as a type code, that identifies the generic SQL type XML.
122 	//SQLXML,
123 	//sometimes referred to as a type code, that identifies the generic SQL type STRUCT.
124 	//STRUCT,
125 	///sometimes referred to as a type code, that identifies the generic SQL type TIME.
126 	TIME,
127 	//sometimes referred to as a type code, that identifies the generic SQL type TIMESTAMP.
128 	//TIMESTAMP,
129 	///sometimes referred to as a type code, that identifies the generic SQL type TINYINT.
130 	TINYINT,
131 	///sometimes referred to as a type code, that identifies the generic SQL type VARBINARY.
132 	VARBINARY,
133 	///sometimes referred to as a type code, that identifies the generic SQL type VARCHAR.
134 	VARCHAR,
135 }
136 
137 interface Connection {
138 	/// Releases this Connection object's database and JDBC resources immediately instead of waiting for them to be automatically released.
139 	void close();
140 	/// Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object.
141 	void commit();
142 	/// Retrieves this Connection object's current catalog name.
143 	string getCatalog();
144 	/// Sets the given catalog name in order to select a subspace of this Connection object's database in which to work.
145 	void setCatalog(string catalog);
146 	/// Retrieves whether this Connection object has been closed.
147 	bool isClosed();
148 	/// Undoes all changes made in the current transaction and releases any database locks currently held by this Connection object.
149 	void rollback();
150 	/// Retrieves the current auto-commit mode for this Connection object.
151 	bool getAutoCommit();
152 	/// Sets this connection's auto-commit mode to the given state.
153 	void setAutoCommit(bool autoCommit);
154 	// statements
155 	/// Creates a Statement object for sending SQL statements to the database.
156 	Statement createStatement();
157 	/// Creates a PreparedStatement object for sending parameterized SQL statements to the database.
158 	PreparedStatement prepareStatement(string query);
159 }
160 
161 interface ResultSetMetaData {
162 	//Returns the number of columns in this ResultSet object.
163 	int getColumnCount();
164 
165 	// Gets the designated column's table's catalog name.
166 	string getCatalogName(int column);
167 	// 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.
168 	//string getColumnClassName(int column);
169 	// Indicates the designated column's normal maximum width in characters.
170 	int getColumnDisplaySize(int column);
171 	// Gets the designated column's suggested title for use in printouts and displays.
172 	string getColumnLabel(int column);
173 	// Get the designated column's name.
174 	string getColumnName(int column);
175 	// Retrieves the designated column's SQL type.
176 	int getColumnType(int column);
177 	// Retrieves the designated column's database-specific type name.
178 	string getColumnTypeName(int column);
179 	// Get the designated column's number of decimal digits.
180 	int getPrecision(int column);
181 	// Gets the designated column's number of digits to right of the decimal point.
182 	int getScale(int column);
183 	// Get the designated column's table's schema.
184 	string getSchemaName(int column);
185 	// Gets the designated column's table name.
186 	string getTableName(int column);
187 	// Indicates whether the designated column is automatically numbered, thus read-only.
188 	bool isAutoIncrement(int column);
189 	// Indicates whether a column's case matters.
190 	bool isCaseSensitive(int column);
191 	// Indicates whether the designated column is a cash value.
192 	bool isCurrency(int column);
193 	// Indicates whether a write on the designated column will definitely succeed.
194 	bool isDefinitelyWritable(int column);
195 	// Indicates the nullability of values in the designated column.
196 	int isNullable(int column);
197 	// Indicates whether the designated column is definitely not writable.
198 	bool isReadOnly(int column);
199 	// Indicates whether the designated column can be used in a where clause.
200 	bool isSearchable(int column);
201 	// Indicates whether values in the designated column are signed numbers.
202 	bool isSigned(int column);
203 	// Indicates whether it is possible for a write on the designated column to succeed.
204 	bool isWritable(int column);
205 }
206 
207 interface ParameterMetaData {
208 	// Retrieves the fully-qualified name of the Java class whose instances should be passed to the method PreparedStatement.setObject.
209 	//String getParameterClassName(int param);
210 	/// Retrieves the number of parameters in the PreparedStatement object for which this ParameterMetaData object contains information.
211 	int getParameterCount();
212 	/// Retrieves the designated parameter's mode.
213 	int getParameterMode(int param);
214 	/// Retrieves the designated parameter's SQL type.
215 	int getParameterType(int param);
216 	/// Retrieves the designated parameter's database-specific type name.
217 	string getParameterTypeName(int param);
218 	/// Retrieves the designated parameter's number of decimal digits.
219 	int getPrecision(int param);
220 	/// Retrieves the designated parameter's number of digits to right of the decimal point.
221 	int getScale(int param);
222 	/// Retrieves whether null values are allowed in the designated parameter.
223 	int isNullable(int param);
224 	/// Retrieves whether values for the designated parameter can be signed numbers.
225 	bool isSigned(int param);
226 }
227 
228 interface DataSetReader {
229 	bool getBoolean(int columnIndex);
230 	ubyte getUbyte(int columnIndex);
231 	ubyte[] getUbytes(int columnIndex);
232 	byte[] getBytes(int columnIndex);
233 	byte getByte(int columnIndex);
234 	short getShort(int columnIndex);
235 	ushort getUshort(int columnIndex);
236 	int getInt(int columnIndex);
237 	uint getUint(int columnIndex);
238 	long getLong(int columnIndex);
239 	ulong getUlong(int columnIndex);
240 	double getDouble(int columnIndex);
241 	float getFloat(int columnIndex);
242 	string getString(int columnIndex);
243 	SysTime getSysTime(int columnIndex);
244 	DateTime getDateTime(int columnIndex);
245 	Date getDate(int columnIndex);
246 	TimeOfDay getTime(int columnIndex);
247 	Variant getVariant(int columnIndex);
248 	bool isNull(int columnIndex);
249 	bool wasNull();
250 }
251 
252 interface DataSetWriter {
253 	void setFloat(int parameterIndex, float x);
254 	void setDouble(int parameterIndex, double x);
255 	void setBoolean(int parameterIndex, bool x);
256 	void setLong(int parameterIndex, long x);
257 	void setInt(int parameterIndex, int x);
258 	void setShort(int parameterIndex, short x);
259 	void setByte(int parameterIndex, byte x);
260 	void setBytes(int parameterIndex, byte[] x);
261 	void setUlong(int parameterIndex, ulong x);
262 	void setUint(int parameterIndex, uint x);
263 	void setUshort(int parameterIndex, ushort x);
264 	void setUbyte(int parameterIndex, ubyte x);
265 	void setUbytes(int parameterIndex, ubyte[] x);
266 	void setString(int parameterIndex, string x);
267 	void setSysTime(int parameterIndex, SysTime x);
268 	void setDateTime(int parameterIndex, DateTime x);
269 	void setDate(int parameterIndex, Date x);
270 	void setTime(int parameterIndex, TimeOfDay x);
271 	void setVariant(int columnIndex, Variant x);
272 
273 	void setNull(int parameterIndex);
274 	void setNull(int parameterIndex, int sqlType);
275 }
276 
277 interface ResultSet : DataSetReader {
278 	void close();
279 	bool first();
280 	bool isFirst();
281 	bool isLast();
282 	bool next();
283 
284 	//Retrieves the number, types and properties of this ResultSet object's columns
285 	ResultSetMetaData getMetaData();
286 	//Retrieves the Statement object that produced this ResultSet object.
287 	Statement getStatement();
288 	//Retrieves the current row number
289 	int getRow();
290 	//Retrieves the fetch size for this ResultSet object.
291 	deprecated("Marked for removal as cCannot be used by all supported drivers. See Github issue #85")
292 	ulong getFetchSize();
293 
294 	// from DataSetReader
295 	bool getBoolean(int columnIndex);
296 	ubyte getUbyte(int columnIndex);
297 	ubyte[] getUbytes(int columnIndex);
298 	byte[] getBytes(int columnIndex);
299 	byte getByte(int columnIndex);
300 	short getShort(int columnIndex);
301 	ushort getUshort(int columnIndex);
302 	int getInt(int columnIndex);
303 	uint getUint(int columnIndex);
304 	long getLong(int columnIndex);
305 	ulong getUlong(int columnIndex);
306 	double getDouble(int columnIndex);
307 	float getFloat(int columnIndex);
308 	string getString(int columnIndex);
309     Variant getVariant(int columnIndex);
310 	SysTime getSysTime(int columnIndex);
311 	DateTime getDateTime(int columnIndex);
312 	Date getDate(int columnIndex);
313 	TimeOfDay getTime(int columnIndex);
314 
315     bool isNull(int columnIndex);
316 	bool wasNull();
317 
318 	// additional methods
319 	int findColumn(string columnName);
320 	bool getBoolean(string columnName);
321 	ubyte getUbyte(string columnName);
322 	ubyte[] getUbytes(string columnName);
323 	byte[] getBytes(string columnName);
324 	byte getByte(string columnName);
325 	short getShort(string columnName);
326 	ushort getUshort(string columnName);
327 	int getInt(string columnName);
328 	uint getUint(string columnName);
329 	long getLong(string columnName);
330 	ulong getUlong(string columnName);
331 	double getDouble(string columnName);
332 	float getFloat(string columnName);
333     string getString(string columnName);
334 	SysTime getSysTime(string columnName);
335 	DateTime getDateTime(string columnName);
336 	Date getDate(string columnName);
337 	TimeOfDay getTime(string columnName);
338 	Variant getVariant(string columnName);
339 
340     /// to iterate through all rows in result set
341     int opApply(int delegate(DataSetReader) dg);
342 
343 }
344 
345 interface Statement {
346 	ResultSet executeQuery(string query);
347 	int executeUpdate(string query);
348 	int executeUpdate(string query, out Variant insertId);
349 	void close();
350 }
351 
352 /// An object that represents a precompiled SQL statement. 
353 interface PreparedStatement : Statement, DataSetWriter {
354 	/// Executes the SQL statement in this PreparedStatement object, which must be an SQL INSERT, UPDATE or DELETE statement; or an SQL statement that returns nothing, such as a DDL statement.
355 	int executeUpdate();
356 	/// Executes the SQL statement in this PreparedStatement object, which must be an SQL INSERT, UPDATE or DELETE statement; or an SQL statement that returns nothing, such as a DDL statement.
357 	int executeUpdate(out Variant insertId);
358 	/// Executes the SQL query in this PreparedStatement object and returns the ResultSet object generated by the query.
359 	ResultSet executeQuery();
360 
361 	/// Retrieves a ResultSetMetaData object that contains information about the columns of the ResultSet object that will be returned when this PreparedStatement object is executed.
362 	ResultSetMetaData getMetaData();
363 	/// Retrieves the number, types and properties of this PreparedStatement object's parameters.
364 	ParameterMetaData getParameterMetaData();
365 	/// Clears the current parameter values immediately.
366 	void clearParameters();
367 
368 	// from DataSetWriter
369 	void setFloat(int parameterIndex, float x);
370 	void setDouble(int parameterIndex, double x);
371 	void setBoolean(int parameterIndex, bool x);
372 	void setLong(int parameterIndex, long x);
373 	void setInt(int parameterIndex, int x);
374 	void setShort(int parameterIndex, short x);
375 	void setByte(int parameterIndex, byte x);
376 	void setBytes(int parameterIndex, byte[] x);
377 	void setUlong(int parameterIndex, ulong x);
378 	void setUint(int parameterIndex, uint x);
379 	void setUshort(int parameterIndex, ushort x);
380 	void setUbyte(int parameterIndex, ubyte x);
381 	void setUbytes(int parameterIndex, ubyte[] x);
382 	void setString(int parameterIndex, string x);
383 	void setSysTime(int parameterIndex, SysTime x);
384 	void setDateTime(int parameterIndex, DateTime x);
385 	void setDate(int parameterIndex, Date x);
386 	void setTime(int parameterIndex, TimeOfDay x);
387 	void setVariant(int parameterIndex, Variant x);
388 
389 	void setNull(int parameterIndex);
390 	void setNull(int parameterIndex, int sqlType);
391 }
392 
393 interface Driver {
394 	Connection connect(string url, string[string] params);
395 }
396 
397 interface DataSource {
398 	Connection getConnection();
399 }
400 
401 /// Helper function to make url in format required for DSN connections to Microsoft SQL Server
402 string makeDDBCUrl(string driverName, string[string] params) {
403 	enforce(driverName == "odbc", "only ODBC can have Url created this way");
404 	import std.array : byPair;
405     import std.algorithm.iteration : map, joiner;
406 	import std.conv : to;
407 	return "odbc://?" ~ to!string(joiner(params.byPair.map!(p => p.key ~ "=" ~ p.value), ","));
408 }
409 
410 /// Helper function to make url in form driverName://host:port/dbname?param1=value1,param2=value2
411 string makeDDBCUrl(string driverName, string host = null, int port = 0, 
412 							string dbName = null, string[string] params = null) {
413 	import std.algorithm.searching : canFind;
414 	enforce(canFind(["sqlite", "postgresql", "mysql", "odbc"], driverName), "driver must be one of sqlite|postgresql|mysql|odbc");
415     import std.conv : to;
416     char[] res;
417     res.assumeSafeAppend;
418 	res ~= "ddbc:";
419     res ~= driverName;
420 
421 	if(driverName is "sqlite") {
422 		// if it's SQLite the host arg should be a filename or ":memory:"
423 		res ~= ":"~host;
424 	} else {
425 		res ~= "://" ~ host ~ ":" ~ to!string(port);
426 
427 		if (dbName !is null) {
428 			res ~= "/" ~ dbName;
429 		}
430 	}
431 
432 	if(params !is null) {
433 		import std.array : byPair;
434     	import std.algorithm.iteration : map, joiner;
435 		res ~= "?" ~ to!string(joiner(params.byPair.map!(p => p.key ~ "=" ~ p.value), ","));
436 	}
437 
438     return res.dup;
439 }
440 
441 private unittest {
442 	assertThrown!Exception(makeDDBCUrl("bogus", ""));
443 }
444 
445 private unittest {
446 	string url = makeDDBCUrl("sqlite", ":memory:");
447 	assert(url == "ddbc:sqlite::memory:", "SQLite URL is not correct: "~url);
448 }
449 
450 private unittest {
451 	string url = makeDDBCUrl("sqlite", "ddbc-test.sqlite");
452 	assert(url == "ddbc:sqlite:ddbc-test.sqlite", "SQLite URL is not correct: "~url);
453 }
454 
455 private unittest {
456 	string url = makeDDBCUrl("postgresql", "127.0.0.1", 5432, "mydb");
457 	assert(url == "ddbc:postgresql://127.0.0.1:5432/mydb", "Postgres URL is not correct: "~url);
458 }
459 
460 private unittest {
461 	string url = makeDDBCUrl("mysql", "127.0.0.1", 3306, "mydb");
462 	assert(url == "ddbc:mysql://127.0.0.1:3306/mydb", "MySQL URL is not correct: "~url);
463 }
464 
465 private unittest {
466 	string[string] params;
467 	params["user"] = "sa";
468 	params["password"] = "p@ss";
469 	params["driver"] = "FreeTDS";
470 
471 	string url = makeDDBCUrl("odbc", "localhost", 1433, null, params);
472 	// todo: check with this URL structure is even correct
473 	assert(url == "ddbc:odbc://localhost:1433?user=sa,password=p@ss,driver=FreeTDS", "ODBC URL is not correct: "~url);
474 }
475 
476 private unittest {
477 	string[string] params;
478 	params["user"] = "sa";
479 	params["password"] = "p@ss";
480 	params["driver"] = "msodbcsql17";
481 
482 	string url = makeDDBCUrl("odbc", "localhost", 1433, null, params);
483 	// todo: check with this URL structure is even correct
484 	assert(url == "ddbc:odbc://localhost:1433?user=sa,password=p@ss,driver=msodbcsql17", "ODBC URL is not correct: "~url);
485 }
486 
487 private unittest {
488 	//immutable string[string] params = ["dsn","myDSN"];
489 	string[string] params;
490 	params["dsn"] = "myDSN";
491 
492 	string url = makeDDBCUrl("odbc", params);
493 	assert(url == "odbc://?dsn=myDSN", "ODBC URL is not correct: "~url);
494 }