1 /++ 2 This module is part of d2sqlite3. 3 4 Authors: 5 Nicolas Sicard (biozic) and other contributors at $(LINK https://github.com/biozic/d2sqlite3) 6 7 Copyright: 8 Copyright 2011-17 Nicolas Sicard. 9 10 License: 11 $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 +/ 13 module d2sqlite3.statement; 14 15 import d2sqlite3.database; 16 import d2sqlite3.results; 17 import d2sqlite3.sqlite3; 18 import d2sqlite3.internal.memory; 19 import d2sqlite3.internal.util; 20 21 import std.conv : to; 22 import std.exception : enforce; 23 import std..string : format, toStringz; 24 import std.typecons : Nullable; 25 26 /// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify 27 version (SqliteEnableUnlockNotify) version = _UnlockNotify; 28 else version (SqliteFakeUnlockNotify) version = _UnlockNotify; 29 30 /++ 31 A prepared statement. 32 33 This struct is a reference-counted wrapper around a `sqlite3_stmt*` pointer. 34 Instances of this struct are typically returned by `Database.prepare()`. 35 +/ 36 struct Statement 37 { 38 import std.meta : allSatisfy; 39 import std.traits : isIntegral, isSomeChar, isBoolean, isFloatingPoint, 40 isSomeString, isStaticArray, isDynamicArray, isIterable; 41 import std.typecons : RefCounted, RefCountedAutoInitialize; 42 43 private: 44 45 /// Returns $(D true) if the value can be directly bound to the statement 46 enum bool isBindable(T) = 47 is(T == typeof(null)) || is(T == void*) || isIntegral!T || isSomeChar!T 48 || isBoolean!T || isFloatingPoint!T || isSomeString!T || isStaticArray!T 49 || isDynamicArray!T || is(T == Nullable!U, U...); 50 51 struct Payload 52 { 53 Database db; 54 sqlite3_stmt* handle; // null if error or empty statement 55 int paramCount; 56 debug string sql; 57 58 ~this() 59 { 60 debug ensureNotInGC!Statement(sql); 61 finalize(); 62 } 63 64 void finalize() 65 { 66 sqlite3_finalize(handle); 67 handle = null; 68 } 69 } 70 71 RefCounted!(Payload, RefCountedAutoInitialize.no) p; 72 73 void checkResult(int result) 74 { 75 enforce(result == SQLITE_OK, new SqliteException(errmsg(p.handle), result)); 76 } 77 78 version (_UnlockNotify) 79 { 80 auto sqlite3_blocking_prepare_v2(Database db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) 81 { 82 int rc; 83 while(SQLITE_LOCKED == (rc = sqlite3_prepare_v2(db.handle(), zSql, nByte, ppStmt, pzTail))) 84 { 85 rc = db.waitForUnlockNotify(); 86 if(rc != SQLITE_OK) break; 87 } 88 return rc; 89 } 90 } 91 92 package(d2sqlite3): 93 this(Database db, string sql) 94 { 95 sqlite3_stmt* handle; 96 version (_UnlockNotify) 97 { 98 auto result = sqlite3_blocking_prepare_v2(db, sql.toStringz, sql.length.to!int, 99 &handle, null); 100 } 101 else 102 { 103 auto result = sqlite3_prepare_v2(db.handle(), sql.toStringz, sql.length.to!int, 104 &handle, null); 105 } 106 enforce(result == SQLITE_OK, new SqliteException(errmsg(db.handle()), result, sql)); 107 p = Payload(db, handle); 108 p.paramCount = sqlite3_bind_parameter_count(p.handle); 109 debug p.sql = sql; 110 } 111 112 version (_UnlockNotify) 113 { 114 /// Setup and waits for unlock notify using the provided `IUnlockNotifyHandler` 115 auto waitForUnlockNotify() 116 { 117 return p.db.waitForUnlockNotify(); 118 } 119 } 120 121 public: 122 /++ 123 Gets the SQLite internal _handle of the statement. 124 +/ 125 sqlite3_stmt* handle() @property nothrow 126 { 127 return p.handle; 128 } 129 130 /++ 131 Explicitly finalizes the prepared statement. 132 133 After a call to `finalize()`, the `Statement` object is destroyed and cannot be used. 134 +/ 135 void finalize() 136 { 137 p.finalize(); 138 destroy(p); 139 } 140 141 /++ 142 Tells whether the statement is empty (no SQL statement). 143 +/ 144 bool empty() @property nothrow 145 { 146 return p.handle is null; 147 } 148 /// 149 unittest 150 { 151 auto db = Database(":memory:"); 152 auto statement = db.prepare(" ; "); 153 assert(statement.empty); 154 } 155 156 /++ 157 Binds values to parameters of this statement, using parameter index. 158 159 Params: 160 index = The index of the parameter (starting from 1). 161 162 value = The bound _value. The type of value must be compatible with the SQLite 163 types: it must be a boolean or numeric type, a string, an array, null, 164 or a Nullable!T where T is any of the previous types. 165 +/ 166 void bind(T)(int index, T value) 167 if (is(T == typeof(null)) || is(T == void*)) 168 in 169 { 170 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 171 } 172 body 173 { 174 assert(p.handle); 175 checkResult(sqlite3_bind_null(p.handle, index)); 176 } 177 178 /// ditto 179 void bind(T)(int index, T value) 180 if (isIntegral!T || isSomeChar!T || isBoolean!T) 181 in 182 { 183 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 184 } 185 body 186 { 187 assert(p.handle); 188 checkResult(sqlite3_bind_int64(p.handle, index, value.to!long)); 189 } 190 191 /// ditto 192 void bind(T)(int index, T value) 193 if (isFloatingPoint!T) 194 in 195 { 196 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 197 } 198 body 199 { 200 assert(p.handle); 201 checkResult(sqlite3_bind_double(p.handle, index, value.to!double)); 202 } 203 204 /// ditto 205 void bind(T)(int index, T value) 206 if (isSomeString!T) 207 in 208 { 209 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 210 } 211 body 212 { 213 assert(p.handle); 214 string str = value.to!string; 215 auto ptr = anchorMem(cast(void*) str.ptr); 216 checkResult(sqlite3_bind_text64(p.handle, index, cast(const(char)*) ptr, str.length, &releaseMem, SQLITE_UTF8)); 217 } 218 219 /// ditto 220 void bind(T)(int index, T value) 221 if (isStaticArray!T) 222 in 223 { 224 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 225 } 226 body 227 { 228 assert(p.handle); 229 checkResult(sqlite3_bind_blob64(p.handle, index, cast(void*) value.ptr, value.sizeof, SQLITE_TRANSIENT)); 230 } 231 232 /// ditto 233 void bind(T)(int index, T value) 234 if (isDynamicArray!T && !isSomeString!T) 235 in 236 { 237 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 238 } 239 body 240 { 241 assert(p.handle); 242 auto arr = cast(void[]) value; 243 checkResult(sqlite3_bind_blob64(p.handle, index, anchorMem(arr.ptr), arr.length, &releaseMem)); 244 } 245 246 /// ditto 247 void bind(T)(int index, T value) 248 if (is(T == Nullable!U, U...)) 249 in 250 { 251 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 252 } 253 body 254 { 255 if (value.isNull) 256 { 257 assert(p.handle); 258 checkResult(sqlite3_bind_null(p.handle, index)); 259 } 260 else 261 bind(index, value.get); 262 } 263 264 /++ 265 Binds values to parameters of this statement, using parameter names. 266 267 Params: 268 name = The name of the parameter, including the ':', '@' or '$' that introduced it. 269 270 value = The bound _value. The type of value must be compatible with the SQLite 271 types: it must be a boolean or numeric type, a string, an array, null, 272 or a Nullable!T where T is any of the previous types. 273 274 Warning: 275 While convenient, this overload of `bind` is less performant, because it has to 276 retrieve the column index with a call to the SQLite function 277 `sqlite3_bind_parameter_index`. 278 +/ 279 void bind(T)(string name, T value) 280 in 281 { 282 assert(name.length); 283 } 284 body 285 { 286 assert(p.handle); 287 auto index = sqlite3_bind_parameter_index(p.handle, name.toStringz); 288 assert(index > 0, "no parameter named '%s'".format(name)); 289 bind(index, value); 290 } 291 292 /++ 293 Binds all the arguments at once in order. 294 +/ 295 void bindAll(Args...)(Args args) 296 in 297 { 298 assert(Args.length == this.parameterCount, "parameter count mismatch"); 299 } 300 body 301 { 302 foreach (index, _; Args) 303 bind(index + 1, args[index]); 304 } 305 306 /++ 307 Clears the bindings. 308 309 This does not reset the statement. Use `Statement.reset()` for this. 310 +/ 311 void clearBindings() 312 { 313 assert(p.handle); 314 checkResult(sqlite3_clear_bindings(p.handle)); 315 } 316 317 /++ 318 Executes the statement and return a (possibly empty) range of results. 319 +/ 320 ResultRange execute() 321 { 322 return ResultRange(this); 323 } 324 325 /++ 326 Resets a this statement before a new execution. 327 328 Calling this method invalidates any `ResultRange` struct returned by a previous call 329 to `Database.execute()` or `Statement.execute()`. 330 331 This does not clear the bindings. Use `Statement.clearBindings()` for this. 332 +/ 333 void reset() 334 { 335 assert(p.handle); 336 checkResult(sqlite3_reset(p.handle)); 337 } 338 339 /++ 340 Binds arguments, executes and resets the statement, in one call. 341 342 This convenience function is equivalent to: 343 --- 344 bindAll(args); 345 execute(); 346 reset(); 347 --- 348 +/ 349 void inject(Args...)(Args args) 350 if (allSatisfy!(isBindable, Args)) 351 { 352 bindAll(args); 353 execute(); 354 reset(); 355 } 356 357 /++ 358 Binds the fields of a struct in order, executes and resets the statement, in one call. 359 +/ 360 void inject(T)(auto ref const T obj) 361 if (is(T == struct)) 362 { 363 import std.meta : Filter; 364 import std.traits : FieldNameTuple; 365 366 enum accesible(string F) = __traits(compiles, __traits(getMember, obj, F)); 367 enum bindable(string F) = isBindable!(typeof(__traits(getMember, obj, F))); 368 369 alias FieldNames = Filter!(bindable, Filter!(accesible, FieldNameTuple!T)); 370 assert(FieldNames.length == this.parameterCount, "parameter count mismatch"); 371 foreach (i, field; FieldNames) 372 bind(i + 1, __traits(getMember, obj, field)); 373 execute(); 374 reset(); 375 } 376 377 /++ 378 Binds iterable values in order, executes and resets the statement, in one call. 379 +/ 380 void inject(T)(auto ref T obj) 381 if (!isBindable!T && isIterable!T) 382 in 383 { 384 static if (__traits(compiles, obj.length)) 385 assert(obj.length == this.parameterCount, "parameter count mismatch"); 386 } 387 body 388 { 389 static if (__traits(compiles, { foreach (string k, ref v; obj) {} })) 390 { 391 foreach (string k, ref v; obj) bind(k, v); 392 } 393 else 394 { 395 int i = 1; 396 foreach (ref v; obj) bind(i++, v); 397 } 398 execute(); 399 reset(); 400 } 401 402 /// Gets the count of bind parameters. 403 int parameterCount() nothrow 404 { 405 assert(p.handle); 406 return p.paramCount; 407 } 408 409 /++ 410 Gets the name of the bind parameter at the given index. 411 412 Params: 413 index = The index of the parameter (the first parameter has the index 1). 414 415 Returns: The name of the parameter or null is not found or out of range. 416 +/ 417 string parameterName(int index) 418 in 419 { 420 assert(index > 0 && index <= p.paramCount, "parameter index out of range"); 421 } 422 body 423 { 424 assert(p.handle); 425 return sqlite3_bind_parameter_name(p.handle, index).to!string; 426 } 427 428 /++ 429 Gets the index of a bind parameter. 430 431 Returns: The index of the parameter (the first parameter has the index 1) 432 or 0 is not found or out of range. 433 +/ 434 int parameterIndex(string name) 435 in 436 { 437 assert(name.length); 438 } 439 body 440 { 441 assert(p.handle); 442 return sqlite3_bind_parameter_index(p.handle, name.toStringz); 443 } 444 } 445 446 /++ 447 Turns $(D_PARAM value) into a _literal that can be used in an SQLite expression. 448 +/ 449 string literal(T)(T value) 450 { 451 import std..string : replace; 452 import std.traits : isBoolean, isNumeric, isSomeString, isArray; 453 454 static if (is(T == typeof(null))) 455 return "NULL"; 456 else static if (isBoolean!T) 457 return value ? "1" : "0"; 458 else static if (isNumeric!T) 459 return value.to!string(); 460 else static if (isSomeString!T) 461 return format("'%s'", value.replace("'", "''")); 462 else static if (isArray!T) 463 return "'X%(%X%)'".format(cast(Blob) value); 464 else 465 static assert(false, "cannot make a literal of a value of type " ~ T.stringof); 466 } 467 /// 468 unittest 469 { 470 assert(null.literal == "NULL"); 471 assert(false.literal == "0"); 472 assert(true.literal == "1"); 473 assert(4.literal == "4"); 474 assert(4.1.literal == "4.1"); 475 assert("foo".literal == "'foo'"); 476 assert("a'b'".literal == "'a''b'''"); 477 auto a = cast(Blob) x"DEADBEEF"; 478 assert(a.literal == "'XDEADBEEF'"); 479 }