diff --git a/source/d2sqlite3/database.d b/source/d2sqlite3/database.d index 13b9d5a..b93214d 100644 --- a/source/d2sqlite3/database.d +++ b/source/d2sqlite3/database.d @@ -20,7 +20,7 @@ import d2sqlite3.internal.util; import std.conv : to; import std.exception : enforce; -import std.string : format, toStringz; +import std.string : format, toStringz, chomp; import std.typecons : Nullable; /// Set _UnlockNotify version if compiled with SqliteEnableUnlockNotify or SqliteFakeUnlockNotify @@ -56,6 +56,27 @@ enum Deterministic no = 0 } +struct PrepareMany { + Database db; + Statement current; + string sql_left; + bool empty; + this(Database db, const string sql) { + this.db = db; + this.sql_left = sql; + popFront(); + } + void popFront() { + const size_t old = sql_left.length; + current = Statement(this.db, sql_left); + empty = old == sql_left.length; + sql_left = sql_left.chomp(); + } + Statement front() { + return current; + } +} + /++ An database connection. @@ -278,11 +299,62 @@ public: The statement becomes invalid if the Database goes out of scope and is destroyed. +/ - Statement prepare(string sql) + Statement prepare(const string sql) { return Statement(this, sql); } + /++ + Prepares (compiles) several SQL statements and return them. + + The statements become invalid if the Database goes out of scope and is destroyed. + +/ + + PrepareMany prepare_many(string sql) + { + return PrepareMany(this, sql); + } + unittest { + auto db = Database(":memory:"); + db.execute("CREATE TABLE test (val INTEGER)"); + auto statements = + db.prepare_many(q"[ + --sql comment with a ; in it +INSERT INTO test (val) +VALUES (:v); +SELECT val FROM +-- gosh I love SQL comments and ;s! + test WHERE val > :v; +SELECT 'A fun string containing ; and -- because why not?'; -- I'm so clever +-- SELECT 23; -- don't do this one +SELECT 42; -- do this one + SELECT val FROM test WHERE val < :v; +]"); + assert(!statements.empty); + statements.popFront(); + assert(!statements.empty); + statements.popFront(); + assert(!statements.empty); + assert(statements.front.execute().oneValue!string == + "A fun string containing ; and -- because why not?"); + statements.popFront(); + assert(!statements.empty); + assert(statements.front.execute().oneValue!int == 42); + statements.popFront(); + assert(!statements.empty); + statements.popFront(); + assert(statements.empty); + int count = 4; + foreach(prep;db.prepare_many(q"[ +SELECT 3; +SELECT 2; +SELECT 1; +-- contact!]")) { + --count; + } + assert(count == 0); + } + /// Convenience functions equivalent to an SQL statement. void begin() { execute("BEGIN"); } /// Ditto diff --git a/source/d2sqlite3/statement.d b/source/d2sqlite3/statement.d index 8ea4270..373462b 100644 --- a/source/d2sqlite3/statement.d +++ b/source/d2sqlite3/statement.d @@ -84,23 +84,51 @@ private: } package(d2sqlite3): - this(Database db, string sql) + + this(Database db, const string sql) + { + string temp = sql; + this(db, temp); + // this constructor won't be able to give any indication that + // the sql wasn't fully parsed, so make sure it is. + assert(temp.length == 0); + // the second constructor moves the sql parameter to the + // unparsed tail, so use that if you might be preparing 2 statements + } + + this(Database db, ref string sql) { sqlite3_stmt* handle; + immutable(char)* head = sql.toStringz; + immutable(char)* tail = null; version (_UnlockNotify) { - auto result = sqlite3_blocking_prepare_v2(db, sql.toStringz, sql.length.to!int, - &handle, null); + auto result = sqlite3_blocking_prepare_v2(db, head, sql.length.to!int, + &handle, &tail); } else { - auto result = sqlite3_prepare_v2(db.handle(), sql.toStringz, sql.length.to!int, - &handle, null); + auto result = sqlite3_prepare_v2(db.handle(), head, sql.length.to!int, + &handle, &tail); } enforce(result == SQLITE_OK, new SqliteException(errmsg(db.handle()), result, sql)); p = Payload(db, handle); p.paramCount = sqlite3_bind_parameter_count(p.handle); - debug p.sql = sql; + if(tail == null) { + debug p.sql = sql; + sql = ""; + } else { + /++ + the tail will be 1 past the end of the SQL statement that was just + prepared. So if you prepare a string with two SQL statements, + it will prepare the first statement, then set tail at the + beginning of the second. This allows to prepare many ; delineated + statements, without having to parse the SQL ourselves. + +/ + size_t head_length = tail - head; + debug p.sql = sql[0..head_length]; + sql = sql[head_length..$]; + } } version (_UnlockNotify)