1 /++
2 Implementation - Prepared statements.
3 
4 WARNING:
5 This module is used to consolidate the common implementation of the safe and
6 unafe API. DO NOT directly import this module, please import one of
7 `mysql.prepared`, `mysql.safe.prepared`, or `mysql.unsafe.prepared`. This
8 module will be removed in a future version without deprecation.
9 
10 $(SAFE_MIGRATION)
11 +/
12 module mysql.impl.prepared;
13 
14 import std.exception;
15 import std.range;
16 import std.traits;
17 import std.typecons;
18 import std.variant;
19 
20 import mysql.exceptions;
21 import mysql.protocol.comms;
22 import mysql.protocol.constants;
23 import mysql.protocol.packets;
24 import mysql.types;
25 import mysql.impl.result;
26 import mysql.safe.commands : ColumnSpecialization, CSN;
27 
28 /++
29 A struct to represent specializations of prepared statement parameters.
30 
31 If you need to send large objects to the database it might be convenient to
32 send them in pieces. The `chunkSize` and `chunkDelegate` variables allow for this.
33 If both are provided then the corresponding column will be populated by calling the delegate repeatedly.
34 The source should fill the indicated slice with data and arrange for the delegate to
35 return the length of the data supplied (in bytes). If that is less than the `chunkSize`
36 then the chunk will be assumed to be the last one.
37 
38 Please use one of the aliases instead of the Impl struct, as this name likely will be removed without deprecation in a future release.
39 +/
40 struct ParameterSpecializationImpl(bool isSafe)
41 {
42 	import mysql.protocol.constants;
43 
44 	size_t pIndex;    //parameter number 0 - number of params-1
45 	SQLType type = SQLType.INFER_FROM_D_TYPE;
46 	uint chunkSize; /// In bytes
47 	static if(isSafe)
48 		uint delegate(ubyte[]) @safe chunkDelegate;
49 	else
50 		uint delegate(ubyte[]) @system chunkDelegate;
51 }
52 
53 /// ditto
54 alias SafeParameterSpecialization = ParameterSpecializationImpl!true;
55 /// ditto
56 alias UnsafeParameterSpecialization = ParameterSpecializationImpl!false;
57 /// ditto
58 alias SPSN = SafeParameterSpecialization;
59 /// ditto
60 alias UPSN = UnsafeParameterSpecialization;
61 
62 
63 /++
64 Encapsulation of a prepared statement.
65 
66 Create this via the function `mysql.safe.connection.prepare`. Set your arguments (if any) via
67 the functions provided, and then run the statement by passing it to
68 `mysql.safe.commands.exec`/`mysql.safe.commands.query`/etc in place of the sql string parameter.
69 
70 Commands that are expected to return a result set - queries - have distinctive
71 methods that are enforced. That is it will be an error to call such a method
72 with an SQL command that does not produce a result set. So for commands like
73 SELECT, use the `mysql.safe.commands.query` functions. For other commands, like
74 INSERT/UPDATE/CREATE/etc, use `mysql.safe.commands.exec`.
75 +/
76 struct SafePrepared
77 {
78 	@safe:
79 private:
80 	const(char)[] _sql;
81 
82 package(mysql):
83 	ushort _numParams; /// Number of parameters this prepared statement takes
84 	PreparedStmtHeaders _headers;
85 	MySQLVal[] _inParams;
86 	SPSN[] _psa;
87 	CSN[] _columnSpecials;
88 	ulong _lastInsertID;
89 
90 	ExecQueryImplInfo getExecQueryImplInfo(uint statementId)
91 	{
92 		return ExecQueryImplInfo(true, null, statementId, _headers, _inParams, _psa);
93 	}
94 
95 public:
96 	/++
97 	Constructor. You probably want `mysql.safe.connection.prepare` instead of this.
98 
99 	Call `mysqln.safe.connection.prepare` instead of this, unless you are creating
100 	your own transport bypassing `mysql.impl.connection.Connection` entirely.
101 	The prepared statement must be registered on the server BEFORE this is
102 	called (which `mysqln.safe.connection.prepare` does).
103 
104 	Internally, the result of a successful outcome will be a statement handle - an ID -
105 	for the prepared statement, a count of the parameters required for
106 	execution of the statement, and a count of the columns that will be present
107 	in any result set that the command generates.
108 
109 	The server will then proceed to send prepared statement headers,
110 	including parameter descriptions, and result set field descriptions,
111 	followed by an EOF packet.
112 	+/
113 	this(const(char[]) sql, PreparedStmtHeaders headers, ushort numParams)
114 	{
115 		this._sql        = sql;
116 		this._headers    = headers;
117 		this._numParams  = numParams;
118 		_inParams.length = numParams;
119 		_psa.length      = numParams;
120 	}
121 
122 	/++
123 	Prepared statement parameter setter.
124 
125 	The value may, but doesn't have to be, wrapped in a MySQLVal. If so,
126 	null is handled correctly.
127 
128 	The value may, but doesn't have to be, a pointer to the desired value.
129 
130 	The value may, but doesn't have to be, wrapped in a Nullable!T. If so,
131 	null is handled correctly.
132 
133 	The value can be null.
134 
135 	Parameter specializations (ie, for chunked transfer) can be added if required.
136 	If you wish to use chunked transfer (via `psn`), note that you must supply
137 	a dummy value for `val` that's typed `ubyte[]`. For example: `cast(ubyte[])[]`.
138 
139 	Type_Mappings: $(TYPE_MAPPINGS)
140 
141 	Params: index = The zero based index
142 	+/
143 	void setArg(T)(size_t index, T val, SafeParameterSpecialization psn = SPSN.init)
144 		if(!isInstanceOf!(Nullable, T) && !is(T == Variant))
145 	{
146 		// Now in theory we should be able to check the parameter type here, since the
147 		// protocol is supposed to send us type information for the parameters, but this
148 		// capability seems to be broken. This assertion is supported by the fact that
149 		// the same information is not available via the MySQL C API either. It is up
150 		// to the programmer to ensure that appropriate type information is embodied
151 		// in the variant array, or provided explicitly. This sucks, but short of
152 		// having a client side SQL parser I don't see what can be done.
153 
154 		enforce!MYX(index < _numParams, "Parameter index out of range.");
155 
156 		_inParams[index] = val;
157 		psn.pIndex = index;
158 		_psa[index] = psn;
159 	}
160 
161 	///ditto
162 	void setArg(T)(size_t index, Nullable!T val, SafeParameterSpecialization psn = SPSN.init)
163 	{
164 		if(val.isNull)
165 			setArg(index, null, psn);
166 		else
167 			setArg(index, val.get(), psn);
168 	}
169 
170 	/++
171 	Bind a tuple of D variables to the parameters of a prepared statement.
172 
173 	You can use this method to bind a set of variables if you don't need any specialization,
174 	that is chunked transfer is not neccessary.
175 
176 	The tuple must match the required number of parameters, and it is the programmer's
177 	responsibility to ensure that they are of appropriate types.
178 
179 	Type_Mappings: $(TYPE_MAPPINGS)
180 	+/
181 	void setArgs(T...)(T args)
182 		if(T.length == 0 || (!is(T[0] == Variant[]) && !is(T[0] == MySQLVal[])))
183 	{
184 		enforce!MYX(args.length == _numParams, "Argument list supplied does not match the number of parameters.");
185 
186 		foreach (size_t i, arg; args)
187 			setArg(i, arg);
188 	}
189 
190 	/++
191 	Bind a MySQLVal[] as the parameters of a prepared statement.
192 
193 	You can use this method to bind a set of variables in MySQLVal form to
194 	the parameters of a prepared statement.
195 
196 	Parameter specializations (ie, for chunked transfer) can be added if required.
197 	If you wish to use chunked transfer (via `psn`), note that you must supply
198 	a dummy value for `val` that's typed `ubyte[]`. For example: `cast(ubyte[])[]`.
199 
200 	This method could be
201 	used to add records from a data entry form along the lines of
202 	------------
203 	auto stmt = conn.prepare("INSERT INTO `table42` VALUES(?, ?, ?)");
204 	DataRecord dr;    // Some data input facility
205 	ulong ra;
206 	do
207 	{
208 	    dr.get();
209 	    stmt.setArgs(dr("Name"), dr("City"), dr("Whatever"));
210 	    ulong rowsAffected = conn.exec(stmt);
211 	} while(!dr.done);
212 	------------
213 
214 	Type_Mappings: $(TYPE_MAPPINGS)
215 
216 	Params:
217 	args = External list of MySQLVal to be used as parameters
218 	psnList = Any required specializations
219 	+/
220 	void setArgs(MySQLVal[] args, SafeParameterSpecialization[] psnList=null)
221 	{
222 		enforce!MYX(args.length == _numParams, "Param count supplied does not match prepared statement");
223 		_inParams[] = args[];
224 		if (psnList !is null)
225 		{
226 			foreach (psn; psnList)
227 				_psa[psn.pIndex] = psn;
228 		}
229 	}
230 
231 	/++
232 	Prepared statement parameter getter.
233 
234 	Type_Mappings: $(TYPE_MAPPINGS)
235 
236 	Params: index = The zero based index
237 	Returns: The MySQLVal representing the argument.
238 	+/
239 	MySQLVal getArg(size_t index)
240 	{
241 		enforce!MYX(index < _numParams, "Parameter index out of range.");
242 		return _inParams[index];
243 	}
244 
245 	/++
246 	Sets a prepared statement parameter to NULL.
247 
248 	This is here mainly for legacy reasons. You can set a field to null
249 	simply by saying `prepared.setArg(index, null);`
250 
251 	Type_Mappings: $(TYPE_MAPPINGS)
252 
253 	Params: index = The zero based index
254 	+/
255 	deprecated("Please use setArg(index, null)")
256 	void setNullArg(size_t index)
257 	{
258 		setArg(index, null);
259 	}
260 
261 	/// Gets the SQL command for this prepared statement.
262 	const(char)[] sql() pure const
263 	{
264 		return _sql;
265 	}
266 
267 	/// Gets the number of arguments this prepared statement expects to be passed in.
268 	@property ushort numArgs() pure const nothrow
269 	{
270 		return _numParams;
271 	}
272 
273 	/// After a command that inserted a row into a table with an auto-increment
274 	/// ID column, this method allows you to retrieve the last insert ID generated
275 	/// from this prepared statement.
276 	@property ulong lastInsertID() pure const nothrow { return _lastInsertID; }
277 
278 	/// Gets the prepared header's field descriptions.
279 	@property FieldDescription[] preparedFieldDescriptions() pure { return _headers.fieldDescriptions; }
280 
281 	/// Gets the prepared header's param descriptions.
282 	@property ParamDescription[] preparedParamDescriptions() pure { return _headers.paramDescriptions; }
283 
284 	/// Get/set the column specializations.
285 	@property ColumnSpecialization[] columnSpecials() pure { return _columnSpecials; }
286 
287 	///ditto
288 	@property void columnSpecials(ColumnSpecialization[] csa) pure { _columnSpecials = csa; }
289 }
290 
291 /++
292 Unsafe wrapper for SafePrepared.
293 
294 This wrapper contains a SafePrepared, and forwards common functionality to that
295 type. It overrides the setting and fetching of arguments, converting them to
296 and from Variant for backwards compatibility.
297 
298 It also sets up UnsafeParameterSpecialization items for the parameters. Note
299 that these are simply cast to SafeParameterSpecialization. There are runtime
300 guards in place to ensure a SafeParameterSpecialization with an unsafe delegate
301 is not accessible as a safe delegate.
302 
303 $(SAFE_MIGRATION)
304 +/
305 struct UnsafePrepared
306 {
307 	private SafePrepared _safe;
308 
309 	private this(SafePrepared sp) @safe
310 	{
311 		_safe = sp;
312 	}
313 
314 	this(const(char[]) sql, PreparedStmtHeaders headers, ushort numParams) @safe
315 	{
316 		_safe = SafePrepared(sql, headers, numParams);
317 	}
318 
319 	/++
320 	Redefine all functions that deal with MySQLVal to deal with Variant instead. Please see SafePrepared for details on how the methods work.
321 
322 	$(SAFE_MIGRATION)
323 	+/
324 	void setArg(T)(size_t index, T val, UnsafeParameterSpecialization psn = UPSN.init) @system
325 		if(!is(T == Variant))
326 	{
327 		_safe.setArg(index, val, cast(SPSN)psn);
328 	}
329 
330 	/// ditto
331 	void setArg(size_t index, Variant val, UnsafeParameterSpecialization psn = UPSN.init) @system
332 	{
333 		_safe.setArg(index, _toVal(val), cast(SPSN)psn);
334 	}
335 
336 	/// ditto
337 	void setArgs(T...)(T args)
338 		if(T.length == 0 || (!is(T[0] == Variant[]) && !is(T[0] == MySQLVal[])))
339 	{
340 		_safe.setArgs(args);
341 	}
342 
343 	/// ditto
344 	void setArgs(Variant[] args, UnsafeParameterSpecialization[] psnList=null) @system
345 	{
346 		enforce!MYX(args.length == _safe._numParams, "Param count supplied does not match prepared statement");
347 		foreach(i, ref arg; args)
348 			_safe.setArg(i, _toVal(arg));
349 		if (psnList !is null)
350 		{
351 			foreach (psn; psnList)
352 				_safe._psa[psn.pIndex] = cast(SPSN)psn;
353 		}
354 	}
355 
356 	/// ditto
357 	Variant getArg(size_t index) @system
358 	{
359 		return _safe.getArg(index).asVariant;
360 	}
361 
362 	/++
363 	Allow conversion to a SafePrepared. UnsafePrepared with
364 	UnsafeParameterSpecialization items that have chunk delegates are not
365 	allowed to convert, because the delegates are possibly unsafe.
366 	+/
367 	ref SafePrepared safe() scope return @safe
368 	{
369 		// first, ensure there are no parameter specializations with delegates as
370 		// those are possibly unsafe.
371 		foreach(ref s; _safe._psa)
372 			enforce!MYX(s.chunkDelegate is null, "Cannot convert UnsafePrepared into SafePrepared with unsafe chunk delegates");
373 		return _safe;
374 	}
375 
376 	// this package method is to skip the ckeck for parameter specializations
377 	// with chunk delegates. It can only be used when using the safe prepared
378 	// statement for execution.
379 	package(mysql) ref SafePrepared safeForExec() return @system
380 	{
381 		return _safe;
382 	}
383 
384 	/// forward all the methods from the safe struct. See `SafePrepared` for
385 	/// details.
386 	deprecated("Please use setArg(index, null)")
387 	void setNullArg(size_t index) @safe
388 	{
389 		_safe.setArg(index, null);
390 	}
391 
392 	@safe pure @property:
393 
394 	/// ditto
395 	const(char)[] sql() const
396 	{
397 		return _safe.sql;
398 	}
399 
400 	/// ditto
401 	ushort numArgs() const nothrow
402 	{
403 		return _safe.numArgs;
404 	}
405 
406 	/// ditto
407 	ulong lastInsertID() const nothrow
408    	{
409 	   	return _safe.lastInsertID;
410    	}
411 	/// ditto
412 	FieldDescription[] preparedFieldDescriptions()
413 	{
414 	   	return _safe.preparedFieldDescriptions;
415    	}
416 
417 	/// ditto
418 	ParamDescription[] preparedParamDescriptions()
419 	{
420 	   	return _safe.preparedParamDescriptions;
421    	}
422 
423 	/// ditto
424 	ColumnSpecialization[] columnSpecials()
425 	{
426 	   	return _safe.columnSpecials;
427    	}
428 
429 	///ditto
430 	void columnSpecials(ColumnSpecialization[] csa)
431    	{
432 	   	_safe.columnSpecials(csa);
433    	}
434 
435 }
436 
437 /// Allow conversion to UnsafePrepared from SafePrepared.
438 UnsafePrepared unsafe(SafePrepared p) @safe
439 {
440 	return UnsafePrepared(p);
441 }
442 
443 /// Template constraint for `PreparedRegistrations`
444 private enum isPreparedRegistrationsPayload(Payload) =
445 	__traits(compiles, (){
446 			static assert(Payload.init.queuedForRelease == false);
447 			Payload p;
448 			p.queuedForRelease = true;
449 		});
450 
451 debug(MYSQLN_TESTS)
452 {
453 	// Test template constraint
454 	struct TestPreparedRegistrationsBad1 { }
455 	struct TestPreparedRegistrationsBad2 { bool foo = false; }
456 	struct TestPreparedRegistrationsBad3 { int queuedForRelease = 1; }
457 	struct TestPreparedRegistrationsBad4 { bool queuedForRelease = true; }
458 	struct TestPreparedRegistrationsGood1 { bool queuedForRelease = false; }
459 	struct TestPreparedRegistrationsGood2 { bool queuedForRelease = false; const(char)[] id; }
460 
461 	static assert(!isPreparedRegistrationsPayload!int);
462 	static assert(!isPreparedRegistrationsPayload!bool);
463 	static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad1);
464 	static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad2);
465 	static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad3);
466 	static assert(!isPreparedRegistrationsPayload!TestPreparedRegistrationsBad4);
467 	//static assert(isPreparedRegistrationsPayload!TestPreparedRegistrationsGood1);
468 	//static assert(isPreparedRegistrationsPayload!TestPreparedRegistrationsGood2);
469 }
470 
471 /++
472 Common functionality for recordkeeping of prepared statement registration
473 and queueing for unregister.
474 
475 Used by `Connection` and `MySQLPoolImpl`.
476 
477 Templated on payload type. The payload should be an aggregate that includes
478 the field: `bool queuedForRelease = false;`
479 
480 Allowing access to `directLookup` from other parts of mysql-native IS intentional.
481 `PreparedRegistrations` isn't intended as 100% encapsulation, it's mainly just
482 to factor out common functionality needed by both `Connection` and `MySQLPool`.
483 +/
484 package(mysql) struct PreparedRegistrations(Payload)
485 	if(	isPreparedRegistrationsPayload!Payload)
486 {
487 	@safe:
488 	/++
489 	Lookup payload by sql string.
490 
491 	Allowing access to `directLookup` from other parts of mysql-native IS intentional.
492 	`PreparedRegistrations` isn't intended as 100% encapsulation, it's mainly just
493 	to factor out common functionality needed by both `Connection` and `MySQLPool`.
494 	+/
495 	Payload[const(char[])] directLookup;
496 
497 	/// Returns null if not found
498 	Nullable!Payload opIndex(const(char[]) sql) pure nothrow
499 	{
500 		Nullable!Payload result;
501 
502 		auto pInfo = sql in directLookup;
503 		if(pInfo)
504 			result = *pInfo;
505 
506 		return result;
507 	}
508 
509 	/// Set `queuedForRelease` flag for a statement in `directLookup`.
510 	/// Does nothing if statement not in `directLookup`.
511 	private void setQueuedForRelease(const(char[]) sql, bool value)
512 	{
513 		if(auto pInfo = sql in directLookup)
514 		{
515 			pInfo.queuedForRelease = value;
516 			directLookup[sql] = *pInfo;
517 		}
518 	}
519 
520 	/// Queue a prepared statement for release.
521 	void queueForRelease(const(char[]) sql)
522 	{
523 		setQueuedForRelease(sql, true);
524 	}
525 
526 	/// Remove a statement from the queue to be released.
527 	void unqueueForRelease(const(char[]) sql)
528 	{
529 		setQueuedForRelease(sql, false);
530 	}
531 
532 	/// Queues all prepared statements for release.
533 	void queueAllForRelease()
534 	{
535 		foreach(sql, info; directLookup)
536 			queueForRelease(sql);
537 	}
538 
539 	// Note: AA.clear does not invalidate any keys or values. In fact, it
540 	// should really be safe/trusted, but is not. Therefore, we mark this
541 	// as trusted.
542 	/// Eliminate all records of both registered AND queued-for-release statements.
543 	void clear() @trusted
544 	{
545 		static if(__traits(compiles, (){ int[int] aa; aa.clear(); }))
546 			directLookup.clear();
547 		else
548 			directLookup = null;
549 	}
550 
551 	/// If already registered, simply returns the cached Payload.
552 	Payload registerIfNeeded(const(char[]) sql, Payload delegate(const(char[])) @safe doRegister)
553 	out(info)
554 	{
555 		// I'm confident this can't currently happen, but
556 		// let's make sure that doesn't change.
557 		assert(!info.queuedForRelease);
558 	}
559 	do
560 	{
561 		if(auto pInfo = sql in directLookup)
562 		{
563 			// The statement is registered. It may, or may not, be queued
564 			// for release. Either way, all we need to do is make sure it's
565 			// un-queued and then return.
566 			pInfo.queuedForRelease = false;
567 			return *pInfo;
568 		}
569 
570 		auto info = doRegister(sql);
571 		directLookup[sql] = info;
572 
573 		return info;
574 	}
575 }
576