Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare many statements from a single string. #59

Open
wants to merge 7 commits into
base: v1.x.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions source/d2sqlite3/database.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}

Comment on lines +59 to +79
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is lacking visibility attributes (private / package) and attributes (@safe / nothrow / ...)

/++
An database connection.

Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed, a string implicitly converts to a const string and vice versa.

{
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"[
Comment on lines +317 to +321
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see this was a unittest at first, please fix the alignment

--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
Expand Down
40 changes: 34 additions & 6 deletions source/d2sqlite3/statement.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +88 to +99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand the point of this change. Why complicate the API this much ?

{
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)
Expand Down