Oj
:tropical_drink: A Clojure library for talking to your database.
Install / Use
/learn @taylorlapeyre/OjREADME

oj
A refreshing Clojure library for talking to your database, heavily influenced by Ring.
Features
- Gives you a standard interface for running and generating SQL
- Focuses on the most common and useful features of SQL
- Enforces type checking and validation for queries
- Sensible defaults
- Concise and powerful API
- Encourages reusable components
Anti-features
- Doesn't try to implement the entiretly of SQL
- Doesn't require you to write SQL
- Doesn't create its own domain-specific language
- Doesn't surprise you
The SPEC file provides a complete description of the OJ interface.
Installation
Add this to your Leiningen :dependencies:
[oj "0.3.0"]
You'll also need a database driver (thanks to yesql for providing this handy table):
|Database|:dependencies Entry|
|---|---|
|PostgreSQL|[org.postgresql/postgresql "9.3-1102-jdbc41"]|
|MySQL|[mysql/mysql-connector-java "5.1.32"]|
|Oracle|[com.oracle/ojdbc14 "10.2.0.4.0"]|
|SQLite|[org.xerial/sqlite-jdbc "3.7.2"]|
|Derby|[org.apache.derby/derby "10.11.1.1"]|
Usage
Queries are represented as a Clojure map. The full specification of a query map can be found here.
(def users-named-taylor
{:table :users
:select [:id :email]
:where {:first_name "taylor"}})
Queries can be executed by passing a query map and a database config into oj/exec:
(def db {:subprotocol "mysql"
:subname "//127.0.0.1:3306/wishwheel3"
:user "root"
:password ""})
(oj/exec users-named-taylor db)
; => ({:id 1 :email "taylorlapeyre@gmail"} ...)
Modifiers are functions that transform a query map into another query map. This allows us to chain them together. Some basic modifiers are provided by default at oj.modifiers.
(require [oj.core :as oj]
[oj.modifiers :as db])
(defn find-by-username [username]
(-> (db/query :users)
(db/select [:id :username :email :created_at])
(db/where {:username username})
(oj/exec db-config)
(first)))
(find-by-username "taylorlapeyre")
; => {:id 1 :username "taylorlapeyre"}
OJ's roots in regular Clojure data structures make it extremely powerful for building abstractions.
(defn user [& forms]
(let [query (reduce merge {:table :users} forms)]
(oj/exec query db)))
(user {:where {:id 1}})
=> SELECT * FROM users WHERE users.id=1
(user {:where {:id 1}}
{:select [:id :username]})
=> SELECT id, username FROM users WHERE users.id=1
Not quite ActiveRecord, but it's getting there. And in 3 lines of code no less!
Of course, you can also perform all of the standard CRUD operations that you'd expect:
(defn create [user-data]
(when (valid? user-data)
(-> (db/query :users)
(db/insert user-data)
(oj/exec db-config))))
(defn update [id user-data]
(when (valid? user-data)
(-> (db/query :users)
(db/where {:id id})
(db/update user-data)
(oj/exec db-config))))
(defn delete [id]
(-> (db/query :users)
(db/where {:id id})
(db/delete)
(oj/exec db-config)))
How about using SQL's aggregate functions? OJ allows you to use those as well, using a Clojure-like syntax.
For example, to get the average price of all items:
(-> (db/query :items)
(select '(avg :price))
(oj/exec db-config))
; => 46.76
For more advanced uses, OJ will provide the data in a useful format.
(-> (db/query :items)
(group :published)
(select [:published '(avg :price)])
(oj/exec db-config))
; ({:published 1 :avg {:price 64.35}}, {:published 0 :avg {:price 10.35}})
OJ gives you a lot of flexibility. For instance, you could write some custom modifier functions and then execute them when you like. This allows you to combine them.
(defn find-by-username
[query username]
(-> query
(db/where {:username username})))
(-> (query :users)
(find-by-username "taylor")
(oj/exec db-config)
(first))
Printing SQL Queries
If you'd like SQL queries logged to your console when executed, you can enable it by setting the environment variable PRINT_DB_LOGS to true.
Contributing
- Fork this repository
- Create a new branch
- Do your thing
- Submit a pull request with a description of the change.
TODO
- Joins
License
Copyright © 2014 Taylor Lapeyre
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Related Skills
oracle
344.1kBest practices for using the oracle CLI (prompt + file bundling, engines, sessions, and file attachment patterns).
prose
344.1kOpenProse VM skill pack. Activate on any `prose` command, .prose files, or OpenProse mentions; orchestrates multi-agent workflows.
Command Development
96.8kThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
Plugin Structure
96.8kThis skill should be used when the user asks to "create a plugin", "scaffold a plugin", "understand plugin structure", "organize plugin components", "set up plugin.json", "use ${CLAUDE_PLUGIN_ROOT}", "add commands/agents/skills/hooks", "configure auto-discovery", or needs guidance on plugin directory layout, manifest configuration, component organization, file naming conventions, or Claude Code plugin architecture best practices.
