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