Prepared Statements
As of version 1.1.0 mSQLite supports prepared statements. Prepared statements, in simple terms, are a way to prepare a query before actually executing it, you can think of it as a compiled SQL template of a sort. There are two major benefits of using prepared statements:
- In case you want to execute the same query, only with different parameters, more than one time the query only needs to be prepared (or compiled) once. This can offer a performance boost for complex queries.
- They're safe from SQL injections if parameter binding is used. In other words, when parameters are bound with the binding API that mSQLite supports, no data needs to be quoted, SQLite will handle it for you.
Getting started
To prepare a query you need to use $sqlite_prepare. It works very much like $sqlite_query, except that instead of returning a result set produced by the query, it returns a prepared statement.Example of preparing a statement and then executing it:
Code
; Assumes that %db is already open, and table contacts exists in the database
var %sql = INSERT INTO contacts (name, email) VALUES ('Joe', '[email protected]') var %stmt = $sqlite_prepare(%db, %sql) sqlite_exec %stmt sqlite_free %stmt |
Code
; Assumes that %db is already open, and table contacts exists in the database
var %sql = INSERT INTO contacts (name, email) VALUES (?, ?) var %stmt = $sqlite_prepare(%db, %sql) sqlite_bind_value %stmt 1 Joe sqlite_bind_value %stmt 2 [email protected] sqlite_exec %stmt sqlite_free %stmt |
Binding parameters
This section is to show what different types of binding parameters mSQLite support. Each type of binding falls into one of the two categories:
-
Automatic input bindingEven before version 1.1.0 mSQLite supported this type in form of binary variables. In 1.1.0 you can also bind regular variables with similiar syntax, using @ to prefix the variable name. Here's an example that shows how to bind a regular variable and a binary variable:
Note that variables bound with automatic binding must be global variables and can only contain letters, numbers and underscores.Codeset %image_name mIRC Logo
bread mIRC.png 0 $file(mIRC.png).size ℑ_bytes
var %sql = INSERT INTO images (name, bytes) VALUES (@image_name, ℑ_bytes)
sqlite_exec %db %sql -
Manual input bindingManual input binding always requires a prepared statement. There are three ways to bind parameters: sqlite_bind_param, sqlite_bind_value and binding them as extra arguments for sqlite_query, sqlite_exec and sqlite_unbuffered_query. The first way is similiar to automatic binding of variables, with the exception being that you can use more complex naming for the variables and you can specify the type of the variable if needed. Example:
With manual binding of variables, you can use variable names such as %contact.name, that contain special characters like period. With automatic binding you can't do this. Another demonstrated feature that isn't possible with automatic binding is specifying the type for the bound variable. In this particular case we're telling mSQLite that the telephone number should always be considered a text. If we didn't do this, mSQLite would consider it as a number since it contains only numbers.Code; Contact info
set %contact.name Joe
set %contact.tel 123456789
; Insert contact
var %sql = INSERT INTO contacts (name, tel) VALUES (:name, :tel)
var %stmt = $sqlite_prepare(%db, %sql)
sqlite_bind_param %stmt :name contact.name
sqlite_bind_param %stmt :tel contact.tel $SQLITE_TEXT
sqlite_exec %stmt
sqlite_free %stmt
Both types of binding, sqlite_bind_param and sqlite_bind_value, support both named parameters and numeric parameters. Numeric parameters are specified with a question mark in the query, named parameters are prefixed with a colon. Here's a couple of examples to demonstrate them:
The third way of binding parameters to prepared statements is most likely the most intuitive one if you only need numeric parameters and don't want to re-use global variables as bound parameters nor call sqlite_bind_value a bunch of times each time you re-execute the statement. An example:Code; Numeric parameters
var %sql = INSERT INTO contacts (name, tel) VALUES (?, ?)
; Named parameters
var %sql = INSERT INTO contacts (name, tel) VALUES (:name, :tel)
; Numeric and named parameters
var %sql = INSERT INTO contacts (name, tel) VALUES (:name, ?2)
Note that we use /noop here because the name contains a space. By using noop and calling the sqlite_exec as an identifier instead of as a command, we can make mIRC tokenize the parameters correctly.Code; Insert contact
var %sql = INSERT INTO contacts (name, tel) VALUES (?, ?)
var %stmt = $sqlite_prepare(%db, %sql)
noop $sqlite_exec(%stmt, Joe Gibson, 123456789)
sqlite_free %stmt
As of version 1.2.0 you can bind parameters to non-prepared statements using the same intuitive syntax as above. The only limitation is that you must call the query functions using the identifier format (i.e. $sqlite_query instead of /sqlite_query):
This is recommended way of inserting user data in queries, as it's the most clean and the most secure way to do it, and prevents the chance of SQL injections completely. Again, you can use /noop if you just want to execute a query, but don't care for the return value.Code; Insert contact
var %sql = INSERT INTO contacts (name, tel) VALUES (?, ?)
noop $sqlite_exec(%db, %sql, Joe Gibson, 123456789)
; Select all contacts with name Joe Gibson
var %sql = SELECT * FROM contacts WHERE name = ?
var %result = $sqlite_query(%db, %sql, Joe Gibson)
All unbound parameters will be substituted with NULL when a query is executed. -
Output bindingThe last type of binding is output binding. With output binding, you can fetch data from a result set directly into variables using sqlite_fetch_bound or sqlite_current_bound. To bind an output variable you use sqlite_bind_field or its alias sqlite_bind_column. You can bind column names using its numeric index, or its name. This is very straightforward, so here's an example to demonstrate the usage:
You can also bind columns to binary variables, by prefixing the variable name with an ampersand as usual with binary variables. If you bind column with a binary data to a regular variable, the binary data will be converted into text and assigned to the variable. Similiarly if you bind a non-binary data to a binary variable, the binary variable will contain ASCII representation of the data.Codevar %sql = SELECT name, email, tel FROM contacts
var %result = $sqlite_query(%db, %sql)
; Bind using column index
sqlite_bind_field %result 1 contact.name
sqlite_bind_field %result 3 contact.tel
; Bind using column name
sqlite_bind_field %result email contact.email
; Fetch the contacts
while ($sqlite_fetch_bound(%result)) {
echo -a Name: %contact.name - Email: %contact.email - Tel: %contact.tel
}
Important guidelines
There are a few things you need to be aware when binding parameters. Here's a list of some things you should know.
-
Do not escape strings yourselfSQLite will do it for you. In non-prepared statements, you generally have to use $sqlite_escape_string to escape special characters in strings to prevent SQL injection. With prepared statements there's no need for this.
-
You can not bind values inside textParameter binding has no effect inside strings. For example you can't do this:
Instead, you should be doing this:Codevar %sql = SELECT * FROM contacts WHERE name LIKE '?@%'
var %stmt = $sqlite_prepare(%db, %sql)
sqlite_bind_value %stmt 1 joeCodevar %sql = SELECT * FROM contacts WHERE name LIKE ?
var %stmt = $sqlite_prepare(%db, %sql)
sqlite_bind_value %stmt 1 joe@% -
Be extra cautious with variable names when using sqlite_bind_fieldBecause mSQLite has no access to local variables of aliases that mSQLite is being used from, it must bind variables bound with sqlite_bind_field as global variables. Because of this, you must be very careful with the variable names you use in order not to overwrite variables from other scripts. It's a good idea to prefix your variables in order to avoid name conflicts. Example:Codevar %sql = SELECT name, email FROM contacts
var %result = $sqlite_query(%db, %sql)
sqlite_bind_field %result name contacts.name ; %contacts.name
sqlite_bind_field %result email contacts.email ; %contacts.email -
Use $sqlite_bind_value instead of /sqlite_bind_value when the value might contain spacesYou must use the identifier form of sqlite_bind_value when the data that is being bound might contain spaces. The reason is that the function contains another parameter after the value to be bound, and mIRC doesn't know how to deal with arguments containing more than one word when used as command. An example:Code; Don't do this!
sqlite_bind_value %stmt 1 Joe Johnson
; Do this!
noop $sqlite_bind_value(%stmt, 1, Joe Johnson)