This is implemented and will ship in the next release. Thanks for the nudge, and bgrommes was right that no Variant exception was needed.
https://feedback.objo.dev/feature/1405
SelectSQL, ExecuteSQL, and their Async variants now take optional parameters on every database class (SQLiteDatabase, MySQLDatabase, PostgreSQLDatabase, SQLServerDatabase, ODBCDatabase) and on the shared Database interface. Arguments bind to the ? placeholders in order, and the prepared statement is created, executed, and disposed for you:
Var rs As RecordSet = db.SelectSQL("SELECT name, age FROM users WHERE age > ? AND city = ?", 21, "Perth")
db.ExecuteSQL("INSERT INTO users (name, age) VALUES (?, ?)", "Alice", 30)
Each argument's runtime type decides how it binds, using the same mapping as PreparedStatement.Bind: String, Integer, Double, Decimal, Boolean, MemoryBlock (binary), and Nothing (SQL NULL). Supplying a number of values that doesn't match the ? placeholders throws a DatabaseException naming both counts, and a ? inside a string literal in your SQL is not treated as a placeholder.
For your jacket routine, one nuance worth knowing: a captured ParamArray forwards as a single value today, so db.SelectSQL(sql, params) inside the jacket won't splat into individual bindings. The pattern that works is discrete Object parameters, which keep the full runtime type detection:
Public Function DBSelect(db As Database, ByRef rs As RecordSet, sql As String, p0 As Object, p1 As Object) As Boolean
Try
rs = db.SelectSQL(sql, p0, p1)
Return True
Catch e As DatabaseException
# Application-specific error handling goes here
Return False
End Try
End Function
If most of your calls share a shape, a couple of overloads like that cover the common cases, and call sites that don't fit can use db.SelectSQL(...) directly inside their own Try/Catch - which is now a one-line call instead of a Prepare plus per-index Binds.