An interesting twist in my recent usage of SQLite was the fact that I noticed my research scripts and the database intertwine more. SQLite is unique in that it really lives in-process, unlike standalone database servers. There is a feature that does not get used very frequently but can be indispensable in some situations.
Understanding SQLite’s Unique Strengths
Normally, it is your Ruby (or Python, or Go, or whatever) program which calls SQLite to make it “do stuff”. However, SQLite can call your code, exposing unique capabilities.
User-Defined Functions in SQLite
SQLite doesn’t support stored procedures, but it does support user-defined functions (UDFs). UDFs let you define your own SQL functions, expanding SQLite’s functionality.
For instance, SELECT RANDOM() calls a built-in SQL function called RANDOM, which returns a random integer. Similarly, SELECT LOWER(‘HELLO’) converts text to lowercase using the LOWER function. These are scalar functions, processing data from a single result row.
Creating Your Own Scalar UDF
At Cheddar, a small library called Tou generates time-ordered UUIDs. We can make these available to our database:
We create an enumerator for a deterministic sequence of Tou IDs. This allows inserting users with IDs into our table. SQLite can provide these values through functions like TOU(), enabling automatic ID generation in queries.
Enhancing Queries with Ruby Functions
We expose our ID generator to SQLite using:
db.create_function("TOU", _arity = 0) do |func| func.result = id_gen.next end
Text Matching with Ruby Regex
For regex matching in SQLite, create a Ruby function:
db.create_function("TEXT_MATCHES_RE", _arity = 2) do |func, text, regexp_str| re = Regexp.new(regexp_str) func.result = text&.match?(re) ? 1 : 0 end
Aggregate Functions and Statistical Analysis
While SQLite offers MIN and MAX, more advanced functions like standard deviation can be added:
class StddevSampHandler # Code for standard deviation end
Example Usage
db.query("CREATE TABLE metric_samples (name VARCHAR NOT NULL, value FLOAT)") # Inserting and querying data
Considerations for UDFs
SQL functions can be used in queries and SQL statements run by the database. However, they cannot be stored with the database or executed without the host application defining the function.
Function Retention with SQLITE_DIRECTONLY
To prevent functions from misuse, mark them as SQLITE_DIRECTONLY:
db.create_function("TOU2", _arity = 0, text_rep_and_flags) do |func| func.result = id_gen.next end
Conclusion
Wiring Ruby methods into SQLite offers powerful capabilities for data exploration and processing. When used appropriately, these functions can provide valuable enhancements to your database operations.