cl-sql-utils is a Common Lisp library providing a high-level interface for working with SQL databases, with initial support for SQLite. It aims to provide an intuitive API similar to Python’s sqlite-utils while embracing Common Lisp idioms.
- Simple database connection management
- Table creation and modification
- Foreign key support
- Schema transformation capabilities
- Row insertion and querying
- (PLANNED) Index management
- (PLANNED) Transaction support
- (IN PROGRESS) robust CLI interface
This project is not in quicklisp. You will need to checkout this project into your $HOME/quicklisp/local-projects dir. Then:
(ql:quickload :sql-utils)
Run the tests:
# assumes the project is in your quicklisp local-projects dir
sbcl --eval "(ql:quickload '(:sql-utils :sql-utils/tests))" --eval "(5am:run! 'sql-utils-test.sql-utils-tests::sql-utils-suite)" --eval "(quit)"
;; Create a new database file
(defvar *db* (sql-utils:make-db-connection :sqlite :filename "examples/test.db"))
;; Create a table with constraints
(sql-utils:create-table *db* "people"
'(("id" . "INTEGER")
("name" . "TEXT")
("email" . "TEXT")
("age" . "INTEGER"))
:pk "id"
:not-null '("name" "email")
:defaults '(("age" . "0")))
;; Insert some data
(sql-utils:insert (sql-utils:make-table *db* "people")
'(:name "Alice" :email "alice@example.com" :age 30))
;; Query the data
(sql-utils:rows (sql-utils:make-table *db* "people"))
make-db-connection
- Create a new database connectioncreate-table
- Create a new tableexecute
- Execute raw SQL
transform
- Modify table schemaadd-column
- Add a new columnadd-foreign-key
- Add a foreign key constraintdrop
- Drop/delete a table
lookup
- Find a record by primary keyrow-count
- Get total number of rowsrow-count-where
- Get total number of rows matching a conditionrows
- Query rows from a tablerows-where
- Query rows with conditionsinsert
- Insert a single recordinsert-all
- Insert multiple recordsdelete-record
- Delete a single recorddelete-where
- Delete records matching a condition
;; Create parent table
(sql-utils:create-table *db* "authors"
'(("id" . "INTEGER")
("name" . "TEXT"))
:pk "id")
;; Create child table with foreign key
(sql-utils:create-table *db* "books"
'(("id" . "INTEGER")
("title" . "TEXT")
("author_id" . "INTEGER"))
:pk "id"
:foreign-keys '(("author_id" "authors" "id")))
;; Add a column and change types
(sql-utils:transform table
:types '(("age" . "TEXT"))
:add-column '(("email" . "TEXT")))
The project includes a command-line interface for interacting with SQLite databases.
To build the CLI binary executable:
# From within the root project directory:
sbcl --load "sql-utils.asd" --eval "(ql:quickload '(:sql-utils :sql-utils/sqlite-cli))" --eval "(dump-system-executable :sql-utils/sqlite-cli)"
This will create an executable at bin/sql-utils
.
create-database
- Create a new SQLite database file--enable-wal
- Enable Write-Ahead Logging mode
create-table
- Create a new table with specified columns--pk
- Specify primary key column(s)--not-null
- Mark columns as NOT NULL--default
- Set default values for columns--fk
- Add foreign key constraints--ignore
- Skip if table exists--replace
- Replace existing table--strict
- Apply STRICT mode
drop
- Drop/delete a table--ignore
- Skip if table doesn’t exist
tables
- List tables in the database--fts4
- Show only FTS4 enabled tables--fts5
- Show only FTS5 enabled tables--counts
- Include row counts--columns
- Show column information--schema
- Show table schemas
rows
- Output rows from a table-c/--column
- Select specific columns--where
- Filter rows with WHERE clause-o/--order
- Order results--limit
- Limit number of rows--offset
- Skip initial rows
insert
- Insert records into a table--pk
- Specify column(s) to use as primary key--nl
- Read newline-delimited JSON
delete-record
- Delete a single row by primary key value(s)delete-where
- Delete rows matching a WHERE clause--where
- WHERE clause for deletion--analyze
- Run ANALYZE after deletion
Create a new database:
$ bin/sql-utils create-database examples/test.db --enable-wal
Create a table with constraints:
bin/sql-utils create-table examples/test.db people \
id integer \
name text \
email text \
age integer \
--pk id \
--not-null name \
--not-null email \
--default "age=0"
Insert records:
# Insert a single record
$ echo '(:name "Alice" :email "alice@example.com" :age 30)' | \
bin/sql-utils insert examples/test.db people
$ echo '(:name "FOO")' | bin/sql-utils insert examples/test.db chickens -
# Insert multiple records (list of plists)
$ echo '((:name "Bob" :email "bob@example.com" :age 25)(:name "Carol" :email "carol@example.com" :age 35))' | bin/sql-utils insert examples/test.db people
$ echo '((:id 82 :name "BAZ" :parent_id 44) (:id 67 :name "BUB" :parent_id 44))' | bin/sql-utils insert examples/test.db chickens -
# Insert with primary key
$ echo '(:id 1 :name "BAR")' | bin/sql-utils insert examples/test.db chickens --pk id -
Query table information:
$ bin/sql-utils tables examples/test.db
test_table
test_table_2
test_table_3
people
# List all tables showing schema
$ bin/sql-utils tables examples/test.db --schema
test_table
Schema: CREATE TABLE "test_table" (
[id] INTEGER,
[name] TEXT DEFAULT 'FOO',
[parent_id] INTEGER REFERENCES [test_table]([id])
)
test_table_2
Schema: CREATE TABLE [test_table_2] (
[id] INTEGER,
[test_text] TEXT,
[blah] TEXT
)
test_table_3
Schema: CREATE TABLE [test_table_3] (
[id] INTEGER,
[test_text] TEXT,
[created_at] TEXT DEFAULT CURRENT_TIMESTAMP
)
people
Schema: CREATE TABLE "people" ([id] INTEGER, [name] TEXT, [email] TEXT, [age] INTEGER, PRIMARY KEY([id]))
# Show table contents
$ bin/sql-utils rows examples/test.db people
(id 1 name Alice email alice@example.com age 30)
(id 2 name Bob email bob@example.com age 25)
(id 3 name Carol email carol@example.com age 35)
# Filter and order results
$ bin/sql-utils rows examples/test.db people \
--where "age > 25" \
--order "name DESC" \
--limit 10
(id 3 name Carol email carol@example.com age 35)
(id 1 name Alice email alice@example.com age 30)
Delete records:
# Delete a single record by primary key
$ bin/sql-utils delete-record examples/test.db people 1
# Delete records matching a condition
$ bin/sql-utils delete-where examples/test.db people --where "age < 18"
# Delete all records in a table
$ bin/sql-utils delete-where examples/test.db people
# Drop/delete an entire table
$ bin/sql-utils drop examples/test.db old_table --ignore
Contributions are welcome! Please feel free to submit pull requests.
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
This project was heavily inspired by the Python sqlite-utils library by Simon Willison.