The database port: what a database adapter must provide.
Lived at wagoe.platform.ports.database until BOU-303,
which is where a port cannot live — every module's persistence layer had to
reach into platform's shell to name it, and .wagoe/check-ports.edn carried
the 20 sites that did as its largest burn-down entry.
Unlike the router abstraction ADR-037 removed, this seam is real: four adapters implement it (postgresql, mysql, sqlite, h2), and the differences between them — dialect, boolean representation, JDBC URL shape — are exactly what the methods below name.
For executing queries rather than describing a database, see
wagoe.platform.database.
Protocol defining the common interface for database adapters.
This protocol abstracts database-specific behavior while allowing common database operations to be implemented in a shared core namespace. Each database type (SQLite, PostgreSQL, MySQL, H2) implements this protocol to provide database-specific functionality.
Design Philosophy:
The database port: what a database adapter must provide. Lived at `wagoe.platform.ports.database` until BOU-303, which is where a port cannot live — every module's persistence layer had to reach into platform's shell to name it, and `.wagoe/check-ports.edn` carried the 20 sites that did as its largest burn-down entry. Unlike the router abstraction ADR-037 removed, this seam is real: four adapters implement it (postgresql, mysql, sqlite, h2), and the differences between them — dialect, boolean representation, JDBC URL shape — are exactly what the methods below name. For executing queries rather than describing a database, see `wagoe.platform.database`. Protocol defining the common interface for database adapters. This protocol abstracts database-specific behavior while allowing common database operations to be implemented in a shared core namespace. Each database type (SQLite, PostgreSQL, MySQL, H2) implements this protocol to provide database-specific functionality. Design Philosophy: - Keep protocol narrow - prefer common behavior in core - Only include methods that differ between databases - Database-agnostic operations belong in core namespace - Type conversions use shared utilities where possible
What a database may honour, each consumed somewhere (ADR-039):
:schemas a schema per tenant, switched by search_path :transactional-ddl DDL in a rolled-back transaction is undone :index-if-not-exists CREATE INDEX IF NOT EXISTS, DROP INDEX IF EXISTS :column-if-not-exists ALTER TABLE … ADD COLUMN IF NOT EXISTS, … DROP COLUMN IF EXISTS :full-text to_tsvector / ts_rank :on-conflict INSERT … ON CONFLICT DO UPDATE :row-locks SELECT … FOR UPDATE :alter-foreign-key ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY
What a database may honour, each consumed somewhere (ADR-039): :schemas a schema per tenant, switched by search_path :transactional-ddl DDL in a rolled-back transaction is undone :index-if-not-exists CREATE INDEX IF NOT EXISTS, DROP INDEX IF EXISTS :column-if-not-exists ALTER TABLE … ADD COLUMN IF NOT EXISTS, … DROP COLUMN IF EXISTS :full-text to_tsvector / ts_rank :on-conflict INSERT … ON CONFLICT DO UPDATE :row-locks SELECT … FOR UPDATE :alter-foreign-key ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY
(capable? adapter capability)Whether adapter claims capability. An unknown capability or a nil
adapter throws, so neither a typo nor a missing adapter quietly turns a
feature off.
Whether `adapter` claims `capability`. An unknown capability or a nil adapter throws, so neither a typo nor a missing adapter quietly turns a feature off.
Protocol defining database-specific behavior for multi-database support.
Each database adapter (SQLite, PostgreSQL, MySQL, H2) implements this protocol to provide database-specific functionality while common operations are handled by the shared database core namespace.
Protocol defining database-specific behavior for multi-database support. Each database adapter (SQLite, PostgreSQL, MySQL, H2) implements this protocol to provide database-specific functionality while common operations are handled by the shared database core namespace.
(jdbc-driver this)Return the JDBC driver class string for this database.
Returns: String - fully qualified JDBC driver class name
Example: (jdbc-driver sqlite-adapter) ;; => "org.sqlite.JDBC"
Return the JDBC driver class string for this database. Returns: String - fully qualified JDBC driver class name Example: (jdbc-driver sqlite-adapter) ;; => "org.sqlite.JDBC"
(dialect this)Return the dialect keyword this adapter is dispatched on.
Returns: :sqlite, :mysql, :ansi (H2), or nil (PostgreSQL)
This is the adapter's identity, not a HoneySQL argument — the mapping to a
HoneySQL dialect lives in wagoe.platform.core.database.query, where
:sqlite becomes no dialect.
Callers dispatch on it, and several resolve nil with
(or (dialect adapter) :postgresql), so nil must mean PostgreSQL and
nothing else. SQLite answered nil until BOU-430 and was read as PostgreSQL.
Example: (dialect sqlite-adapter) ;; => :sqlite (dialect h2-adapter) ;; => :ansi (dialect postgres-adapter) ;; => nil
Return the dialect keyword this adapter is dispatched on. Returns: :sqlite, :mysql, :ansi (H2), or nil (PostgreSQL) This is the adapter's identity, not a HoneySQL argument — the mapping to a HoneySQL dialect lives in wagoe.platform.core.database.query, where :sqlite becomes no dialect. Callers dispatch on it, and several resolve nil with `(or (dialect adapter) :postgresql)`, so nil must mean PostgreSQL and nothing else. SQLite answered nil until BOU-430 and was read as PostgreSQL. Example: (dialect sqlite-adapter) ;; => :sqlite (dialect h2-adapter) ;; => :ansi (dialect postgres-adapter) ;; => nil
(boolean->db this boolean-value)Convert boolean value to database-specific representation.
Args: boolean-value: Boolean value (true/false/nil)
Returns: Database-specific boolean representation
Example: (boolean->db sqlite-adapter true) ;; => 1 (boolean->db postgres-adapter true) ;; => true
Convert boolean value to database-specific representation. Args: boolean-value: Boolean value (true/false/nil) Returns: Database-specific boolean representation - SQLite/MySQL: 1/0 - PostgreSQL/H2: true/false Example: (boolean->db sqlite-adapter true) ;; => 1 (boolean->db postgres-adapter true) ;; => true
(db->boolean this db-value)Convert database boolean representation to Clojure boolean.
Args: db-value: Database boolean value
Returns: Boolean - true/false
Example: (db->boolean sqlite-adapter 1) ;; => true (db->boolean postgres-adapter true) ;; => true
Convert database boolean representation to Clojure boolean. Args: db-value: Database boolean value Returns: Boolean - true/false Example: (db->boolean sqlite-adapter 1) ;; => true (db->boolean postgres-adapter true) ;; => true
(table-exists? this datasource table-name)Check if a table exists in the database.
Uses database-specific table introspection:
Args: datasource: Database connection pool or connection table-name: String or keyword table name
Returns: Boolean - true if table exists
Example: (table-exists? sqlite-adapter ds :users) ;; => true
Check if a table exists in the database. Uses database-specific table introspection: - SQLite: Query sqlite_master - PostgreSQL: Query information_schema.tables - MySQL: Query information_schema.tables - H2: Query INFORMATION_SCHEMA.TABLES Args: datasource: Database connection pool or connection table-name: String or keyword table name Returns: Boolean - true if table exists Example: (table-exists? sqlite-adapter ds :users) ;; => true
(build-where this filters)Build database-specific WHERE clause conditions from filter map.
Handles database-specific differences such as:
Args: filters: Map of field -> value filters
Returns: HoneySQL WHERE clause fragment or nil
Example: (build-where postgres-adapter {:name "john" :active true}) ;; => [:and [:escape [:ilike :name "%john%"] [:inline "!"]] [:= :active true]]
Build database-specific WHERE clause conditions from filter map.
Handles database-specific differences such as:
- PostgreSQL: Use ILIKE for case-insensitive string matching
- Others: Use LIKE for string matching
- Boolean handling varies by database
Args:
filters: Map of field -> value filters
Returns:
HoneySQL WHERE clause fragment or nil
Example:
(build-where postgres-adapter {:name "john" :active true})
;; => [:and [:escape [:ilike :name "%john%"] [:inline "!"]] [:= :active true]](jdbc-url this db-config)Generate JDBC URL string from database configuration.
Args: db-config: Database configuration map
Returns: String - JDBC URL formatted for this database type
Example: (jdbc-url postgres-adapter {:host "localhost" :port 5432 :name "mydb"}) ;; => "jdbc:postgresql://localhost:5432/mydb"
Generate JDBC URL string from database configuration.
Args:
db-config: Database configuration map
Returns:
String - JDBC URL formatted for this database type
Example:
(jdbc-url postgres-adapter {:host "localhost" :port 5432 :name "mydb"})
;; => "jdbc:postgresql://localhost:5432/mydb"(pool-defaults this)Return default connection pool settings optimized for this database.
Returns: Map - HikariCP pool configuration defaults
Example: (pool-defaults sqlite-adapter) ;; => {:minimum-idle 1 :maximum-pool-size 5 :connection-timeout-ms 30000}
Return default connection pool settings optimized for this database.
Returns:
Map - HikariCP pool configuration defaults
Example:
(pool-defaults sqlite-adapter)
;; => {:minimum-idle 1 :maximum-pool-size 5 :connection-timeout-ms 30000}(init-connection! this datasource db-config)Initialize database connection with database-specific settings.
The pool calls this with each connection as it opens, since these settings are per connection (BOU-606). Initialization such as:
Args: datasource: Database connection pool or connection db-config: Database configuration map
Returns: nil - side effects only
Example: (init-connection! sqlite-adapter datasource config)
Initialize database connection with database-specific settings. The pool calls this with each connection as it opens, since these settings are per connection (BOU-606). Initialization such as: - SQLite: Apply PRAGMA settings - PostgreSQL: Set application_name, timezone - MySQL: Set sql_mode, timezone - H2: Set MODE, timezone Args: datasource: Database connection pool or connection db-config: Database configuration map Returns: nil - side effects only Example: (init-connection! sqlite-adapter datasource config)
(get-table-info this datasource table-name)Get column information for a table.
Uses database-specific column introspection to return standardized column information including name, type, constraints, and primary key status.
Args: datasource: Database connection pool or connection table-name: String or keyword table name
Returns: Vector of column info maps with keys:
Example: (get-table-info sqlite-adapter ds :users) ;; => [{:name "id" :type "TEXT" :not-null true :primary-key true} ...]
Get column information for a table.
Uses database-specific column introspection to return standardized
column information including name, type, constraints, and primary key status.
Args:
datasource: Database connection pool or connection
table-name: String or keyword table name
Returns:
Vector of column info maps with keys:
- :name - column name string
- :type - database type string
- :not-null - boolean
- :default - default value or nil
- :primary-key - boolean
- :generated - boolean: the database fills it (identity,
auto-increment, rowid, or a computed column)
- :generated-always - boolean: it also refuses a value an INSERT or
UPDATE supplies (a computed column, an identity GENERATED ALWAYS)
- :max-length - a character column's declared length, or nil (always
nil on SQLite, which enforces none)
Example:
(get-table-info sqlite-adapter ds :users)
;; => [{:name "id" :type "TEXT" :not-null true :primary-key true} ...]Schema for database configuration - supports embedded and server-based databases.
Embedded databases (SQLite, H2 file/memory):
Server databases (PostgreSQL, MySQL, H2 server):
Schema for database configuration - supports embedded and server-based databases. Embedded databases (SQLite, H2 file/memory): - Require :adapter and :database-path - database-path can be file path or 'mem:name' for H2 Server databases (PostgreSQL, MySQL, H2 server): - Require :adapter, :host, :port, and :name - Optional :username, :password, :connection-params
How long column-type makes a :string given no length.
How long `column-type` makes a :string given no length.
(embedded-db-config? db-config)Check if configuration is for an embedded database (SQLite or H2 file/memory).
Args: db-config: Database configuration map
Returns: Boolean - true if embedded database configuration
Example: (embedded-db-config? {:adapter :sqlite :database-path "./app.db"}) ;; => true (embedded-db-config? {:adapter :h2 :database-path "mem:testdb"}) ;; => true
Check if configuration is for an embedded database (SQLite or H2 file/memory).
Args:
db-config: Database configuration map
Returns:
Boolean - true if embedded database configuration
Example:
(embedded-db-config? {:adapter :sqlite :database-path "./app.db"}) ;; => true
(embedded-db-config? {:adapter :h2 :database-path "mem:testdb"}) ;; => trueSchema for embedded database configuration (SQLite, H2 file/memory).
Schema for embedded database configuration (SQLite, H2 file/memory).
Which database an adapter speaks, and what it honours (ADR-039).
Which database an adapter speaks, and what it honours (ADR-039).
(capabilities this)The set of capability-keywords this database honours. The conformance
suite checks each claim both ways (ADR-039). To ask about one, call
capable?.
The set of `capability-keywords` this database honours. The conformance suite checks each claim both ways (ADR-039). To ask about one, call `capable?`.
(column-type this logical)The DDL type for logical, one of logical-column-types, or
[:string length]; :string alone is 255 long.
Example: (column-type mysql-adapter :uuid) ;; => "CHAR(36)" (column-type mysql-adapter [:string 50]) ;; => "VARCHAR(50)"
The DDL type for `logical`, one of `logical-column-types`, or [:string length]; :string alone is 255 long. Example: (column-type mysql-adapter :uuid) ;; => "CHAR(36)" (column-type mysql-adapter [:string 50]) ;; => "VARCHAR(50)"
(engine this)Which database this is: :postgresql, :mysql, :sqlite or :h2. Never nil.
Ask this, not dialect, not the driver and not the product name (ADR-039).
Which database this is: :postgresql, :mysql, :sqlite or :h2. Never nil. Ask this, not `dialect`, not the driver and not the product name (ADR-039).
(now-ms-sql this)A SQL expression for the database clock in epoch millis, for times every replica must agree on, such as a lease expiry (BOU-679).
A SQL expression for the database clock in epoch millis, for times every replica must agree on, such as a lease expiry (BOU-679).
(explain-db-config db-config)Get detailed validation errors for invalid database configuration.
Args: db-config: Database configuration map
Returns: Malli explanation map or nil if valid
Example: (explain-db-config {:adapter :invalid})
Get detailed validation errors for invalid database configuration.
Args:
db-config: Database configuration map
Returns:
Malli explanation map or nil if valid
Example:
(explain-db-config {:adapter :invalid})(get-adapter-type db-config)Get the adapter type from database configuration.
Args: db-config: Database configuration map
Returns: Keyword - :sqlite, :h2, :postgresql, or :mysql
Example: (get-adapter-type {:adapter :sqlite :database-path "./app.db"}) ;; => :sqlite
Get the adapter type from database configuration.
Args:
db-config: Database configuration map
Returns:
Keyword - :sqlite, :h2, :postgresql, or :mysql
Example:
(get-adapter-type {:adapter :sqlite :database-path "./app.db"}) ;; => :sqliteThe keys of a table, for naming the columns a refused write hit.
The keys of a table, for naming the columns a refused write hit.
(key-columns this datasource table-name key-name)The columns of key key-name on table-name, in key order, or nil where
the driver's refusal names them itself. MySQL's names only the key
(BOU-712); PostgreSQL's names them in a Detail that follows lc_messages
(BOU-721).
The columns of key `key-name` on `table-name`, in key order, or nil where the driver's refusal names them itself. MySQL's names only the key (BOU-712); PostgreSQL's names them in a Detail that follows lc_messages (BOU-721).
(unique-keys this datasource table-name)Every unique key on table-name, the primary key included, as a vector
of its lower-case column names in key order. A partial key or one on an
expression is left out: it does not make its columns unique.
Example: (unique-keys h2-adapter ds :product_tags) ;; => [["product_id" "tag_id"]]
Every unique key on `table-name`, the primary key included, as a vector of its lower-case column names in key order. A partial key or one on an expression is left out: it does not make its columns unique. Example: (unique-keys h2-adapter ds :product_tags) ;; => [["product_id" "tag_id"]]
The LIKE escape character. Not backslash, which MySQL string literals would need doubled (BOU-684).
The LIKE escape character. Not backslash, which MySQL string literals would need doubled (BOU-684).
(like-pattern mode value)A LIKE pattern, for ESCAPE like-escape, matching value literally:
anywhere (:contains), at the start (:starts-with) or at the end
(:ends-with). % _ and ! are escaped.
A LIKE pattern, for `ESCAPE like-escape`, matching `value` literally: anywhere (:contains), at the start (:starts-with) or at the end (:ends-with). % _ and ! are escaped.
Case-insensitive LIKE that matches user input literally (BOU-684).
Case-insensitive LIKE that matches user input literally (BOU-684).
(like this field pattern)(like this field mode value)A HoneySQL condition matching field against value literally, where
mode puts it (see like-pattern), regardless of case, on every engine.
Deprecated, removed in 2.0: the 3-arity, which takes a LIKE pattern as
is, so % and _ in user input match anything (BOU-684).
Example: (like h2-adapter :email :contains "john") ;; => [:escape [:like [:lower :email] [:lower "%john%"]] [:inline "!"]]
A HoneySQL condition matching `field` against `value` literally, where `mode` puts it (see `like-pattern`), regardless of case, on every engine. Deprecated, removed in 2.0: the 3-arity, which takes a LIKE `pattern` as is, so % and _ in user input match anything (BOU-684). Example: (like h2-adapter :email :contains "john") ;; => [:escape [:like [:lower :email] [:lower "%john%"]] [:inline "!"]]
What column-type answers for.
What `column-type` answers for.
Optional: an adapter that encodes query parameters for its engine. Its own protocol, so an adapter that predates it keeps working (BOU-648).
Optional: an adapter that encodes query parameters for its engine. Its own protocol, so an adapter that predates it keeps working (BOU-648).
(param->db this value)value as this engine takes it as a query parameter.
`value` as this engine takes it as a query parameter.
Schema for connection pool configuration.
Schema for connection pool configuration.
(requires-credentials? db-config)Check if the database configuration requires authentication credentials.
Args: db-config: Database configuration map
Returns: Boolean - true if database typically requires username/password
Example: (requires-credentials? {:adapter :postgresql :host "localhost"}) ;; => true (requires-credentials? {:adapter :sqlite :database-path "./app.db"}) ;; => false
Check if the database configuration requires authentication credentials.
Args:
db-config: Database configuration map
Returns:
Boolean - true if database typically requires username/password
Example:
(requires-credentials? {:adapter :postgresql :host "localhost"}) ;; => true
(requires-credentials? {:adapter :sqlite :database-path "./app.db"}) ;; => falseOptional: an adapter that decodes what its driver returns, the read side of ParamEncoder (BOU-653).
Optional: an adapter that decodes what its driver returns, the read side of ParamEncoder (BOU-653).
(value-decoder this)A fn decoding each value read from this engine, or nil when its values need none.
A fn decoding each value read from this engine, or nil when its values need none.
(server-db-config? db-config)Check if configuration is for server-based database (PostgreSQL, MySQL, H2 server).
Args: db-config: Database configuration map
Returns: Boolean - true if server-based database configuration
Example: (server-db-config? {:adapter :postgresql :host "localhost" :port 5432 :name "mydb"}) ;; => true
Check if configuration is for server-based database (PostgreSQL, MySQL, H2 server).
Args:
db-config: Database configuration map
Returns:
Boolean - true if server-based database configuration
Example:
(server-db-config? {:adapter :postgresql :host "localhost" :port 5432 :name "mydb"}) ;; => trueSchema for server-based database configuration (PostgreSQL, MySQL, H2 server).
Schema for server-based database configuration (PostgreSQL, MySQL, H2 server).
(valid-db-config? db-config)Check if database configuration is valid without throwing exceptions.
Args: db-config: Database configuration map
Returns: Boolean - true if configuration is valid
Example: (valid-db-config? {:adapter :sqlite :database-path "db.sqlite"}) ;; => true (valid-db-config? {:adapter :postgresql :host "localhost" :port 5432 :name "mydb"}) ;; => true
Check if database configuration is valid without throwing exceptions.
Args:
db-config: Database configuration map
Returns:
Boolean - true if configuration is valid
Example:
(valid-db-config? {:adapter :sqlite :database-path "db.sqlite"}) ;; => true
(valid-db-config? {:adapter :postgresql :host "localhost" :port 5432 :name "mydb"}) ;; => true(validate-db-config db-config)Validate database configuration against Malli schema.
Args: db-config: Database configuration map
Returns: db-config if valid
Throws: ExceptionInfo with detailed validation errors if configuration is invalid
Example: (validate-db-config {:adapter :sqlite :database-path "db.sqlite"}) (validate-db-config {:adapter :postgresql :host "localhost" :port 5432 :name "mydb"})
Validate database configuration against Malli schema.
Args:
db-config: Database configuration map
Returns:
db-config if valid
Throws:
ExceptionInfo with detailed validation errors if configuration is invalid
Example:
(validate-db-config {:adapter :sqlite :database-path "db.sqlite"})
(validate-db-config {:adapter :postgresql :host "localhost" :port 5432 :name "mydb"})cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |