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