Hello,
In the last days I've been improving the low-level layers of GRDB the SQLite toolkit by asking SQLite to not copy C strings or blob buffers arguments when appropriate (SQLITE_STATIC vs. SQLITE_TRANSIENT).
// Avoid a useless copy of the "Arthur\0" buffer by
// instructing SQLite to use the temporary one created by Swift
let sql = "INSERT INTO player (name) VALUES (?)"
let statement = try db.makeStatement(sql: sql)
try statement.execute(arguments: ["Arthur"])
To this end, I execute the SQLite statement from within the String.withCString closure, in order to guarantee the validity of the C string pointer for the whole duration of SQLite operations:
// Simulated execution
"Arthur".withCString { cString in
// Give cString to SQLite (asking to not copy it)
// Execute statement
}
When executing a statement that accepts several arguments, it's the same, but I must take care of nesting calls to String.withCString:
try statement.execute(arguments: ["Arthur", "Barbara", "Charles"])
// Simulated execution
"Arthur".withCString { cString in
// Give cString to SQLite (asking to not copy it)
"Barbara".withCString { cString in
// Give cString to SQLite (asking to not copy it)
"Charles".withCString { cString in
// Give cString to SQLite (asking to not copy it)
// Execute statement
}
}
}
The real code, which handles an arbitrary number of elements, must use recursion in order to nest the calls to String.withCString.
This recursion can lead to stack overflow when there are a large number of arguments.
Hence my question: How can I prevent stack overflow without hard-coding a limit (say, only use this optimization when there are less than 20 arguments)?
I believe that relying on tail call optimization is not an option, since the recursion happens from within the String.withCString closure, not at the very end of the recursive function.