A professional Clojure library that implements authentic Pick/D3-style multivalue database functionality on top of SQL databases. PickDict brings the power of multivalue databases to modern Clojure applications while maintaining full compatibility with traditional SQL databases.
PickDict is available on Clojars. Add this to your project.clj
:
[org.clojars.hector/pickdict "0.1.0"]
For local development or if you need the latest changes:
git clone https://github.com/hectorqlucero/pickdict.git
cd pickdict
lein install
If GitHub Actions fails, deploy manually:
# 1. Get your Clojars deploy token from: https://clojars.org/tokens
# 2. Set credentials
export CLOJARS_USERNAME=hector
export CLOJARS_PASSWORD=your_deploy_token
# 3. Run deployment
./deploy.sh
This project uses GitHub Actions to automatically publish to Clojars when a new version is tagged.
Use the provided release script:
# Update version and create release
./release.sh 0.1.1
# Or for patch releases
./release.sh 0.1.0.1
The script will:
project.clj
versionCHANGELOG.md
Update version in project.clj
:
(defproject org.clojars.hector/pickdict "0.1.1" ;; Increment version
Update CHANGELOG.md with the new changes
Commit and push your changes:
git add .
git commit -m "Bump version to 0.1.1"
git push origin main
Create a Git tag:
git tag v0.1.1
git push origin v0.1.1
GitHub Actions will automatically:
Set these in your GitHub repository settings under "Secrets and variables" → "Actions":
CLOJARS_USERNAME
: Your Clojars usernameCLOJARS_PASSWORD
: Your Clojars deploy token (not password)A professional Clojure library that implements authentic Pick/D3-style multivalue database functionality on top of SQL databases. PickDict brings the power of multivalue databases to modern Clojure applications while maintaining full compatibility with traditional SQL databases.
Pick/D3 is a powerful multivalue database system that revolutionized data management in the 1970s. Unlike traditional relational databases, Pick/D3 allows:
PickDict brings this proven architecture to Clojure, enabling developers to build sophisticated data applications with elegant, concise code.
✨ Authentic Pick/D3 Architecture - True multivalue database implementation with one dictionary per table
🔢 Multivalue Fields - Store arrays of related data in single fields using configurable delimiters
🧮 Advanced Expression Engine - Full Clojure expressions with automatic variable binding
🔗 Relationship Support - Built-in translation fields for cross-table lookups
⚡ High Performance - Optimized SQL backend with connection pooling
🔄 Legacy Compatibility - Supports traditional MULTIPLY/SUM operations
🛡️ Type Safety - Comprehensive error handling and validation
📊 SQLite Backend - Persistent storage with clojure.java.jdbc
🧪 Comprehensive Testing - Full test suite with 14 assertions
PickDict is available on Clojars. Add this to your project.clj
:
[org.clojars.hector/pickdict "0.1.0"]
For local development or if you need the latest changes:
git clone https://github.com/hectorqlucero/pickdict.git
cd pickdict
lein install
PickDict requires the following runtime dependencies:
Get started with PickDict in minutes:
(require '[pickdict.core :as pick])
;; 1. Configure database connection
(def db {:dbtype "sqlite" :dbname "inventory.db"})
;; 2. Create table with multivalue fields
(pick/create-file! db "PRODUCT"
{:id "INTEGER PRIMARY KEY AUTOINCREMENT"
:name "TEXT NOT NULL"
:price "REAL"
:categories "TEXT" ;; Multivalue: "electronics]popular]new"
:stock_levels "TEXT"}) ;; Multivalue: "10]5]20"
;; 3. Create dictionary for data interpretation
(pick/create-dictionary! db "PRODUCT_DICT")
;; 4. Define dictionary fields
(pick/define-dictionary-field! db "PRODUCT_DICT" "PRODUCT_NAME" "A" "1" "" "Product Name")
(pick/define-dictionary-field! db "PRODUCT_DICT" "PRICE" "A" "2" "" "Unit Price")
(pick/define-dictionary-field! db "PRODUCT_DICT" "CATEGORIES" "A" "3" "" "Categories")
(pick/define-dictionary-field! db "PRODUCT_DICT" "TOTAL_STOCK" "C" "" "SUM:STOCK_LEVELS" "Total Stock")
;; 5. Create and query data
(pick/create-record! db "PRODUCT"
{:name "Wireless Headphones"
:price 99.99
:categories "electronics]audio]wireless"
:stock_levels "50]30]20"})
;; 6. Query with automatic interpretation
(pick/find-all db "PRODUCT")
;; Returns: {:PRODUCT_NAME "Wireless Headphones"
;; :PRICE 99.99
;; :CATEGORIES ["electronics" "audio" "wireless"]
;; :TOTAL_STOCK 100}
PickDict uses the ]
character as a delimiter to store multiple values in a single database field:
;; Database field contains: "electronics]audio]wireless"
;; PickDict interprets as: ["electronics" "audio" "wireless"]
A dictionary defines how to interpret data from a table. Each table has exactly one dictionary containing multiple field definitions:
;; One dictionary per table (Pick/D3 standard)
PRODUCT_DICT → Defines how to interpret PRODUCT table data
CUSTOMER_DICT → Defines how to interpret CUSTOMER table data
PickDict supports three fundamental field types:
(create-file! db table-name schema)
Creates a new table with the specified schema.
Parameters:
db
: Database connection maptable-name
: String name of the table to createschema
: Map of column names to SQL typesExample:
(pick/create-file! db "CUSTOMER"
{:id "INTEGER PRIMARY KEY AUTOINCREMENT"
:name "TEXT NOT NULL"
:email "TEXT"})
(drop-table! db table-name)
Drops a table and its associated dictionary.
Parameters:
db
: Database connection maptable-name
: String name of the table to drop(create-dictionary! db dict-name)
Creates a new dictionary for a table.
Parameters:
db
: Database connection mapdict-name
: String name of the dictionary (typically TABLE_DICT
)(define-dictionary-field! db dict-name field-name field-type position expression description)
Defines a new field in a dictionary.
Parameters:
db
: Database connection mapdict-name
: String name of the dictionaryfield-name
: String name of the fieldfield-type
: "A" (Attribute), "T" (Translate), or "C" (Computed)position
: Column position (for Attribute fields) or empty stringexpression
: Expression string (for Translate/Computed fields)description
: Human-readable descriptionExamples:
;; Attribute field
(pick/define-dictionary-field! db "PRODUCT_DICT" "NAME" "A" "1" "" "Product Name")
;; Translate field
(pick/define-dictionary-field! db "PRODUCT_DICT" "SUPPLIER_NAME" "T" "1" "TSUPPLIER;name" "Supplier Name")
;; Computed field
(pick/define-dictionary-field! db "PRODUCT_DICT" "TOTAL_VALUE" "C" "" "(* PRICE QUANTITY)" "Total Value")
(create-record! db table-name data)
Creates a new record in the specified table.
Parameters:
db
: Database connection maptable-name
: String name of the tabledata
: Map of column names to values(find-all db table-name)
Retrieves all records from a table with dictionary interpretation.
Returns: Sequence of maps with interpreted field values
(find-by-id db table-name id)
Retrieves a single record by ID with dictionary interpretation.
Returns: Map with interpreted field values or nil
if not found
(update-record! db table-name id data)
Updates an existing record.
Parameters:
db
: Database connection maptable-name
: String name of the tableid
: Record ID to updatedata
: Map of column names to new values(delete-record! db table-name id)
Deletes a record by ID.
(read-all-records db table-name)
Retrieves all records without dictionary interpretation (raw data).
Returns: Sequence of maps with raw database values
;; Create a customer
(pick/create-record! db "CUSTOMER"
{:name "John Doe"
:email "john@example.com"
:phone "555-0123"})
;; Create a product with multivalue fields
(pick/create-record! db "PRODUCT"
{:name "Gaming Laptop"
:price 1299.99
:categories "electronics]gaming]laptop"
:specifications "16GB RAM]512GB SSD]RTX 3060"})
;; Get all products with dictionary interpretation
(def products (pick/find-all db "PRODUCT"))
;; Get specific product by ID
(def product (pick/find-by-id db "PRODUCT" 1))
;; Get raw data (for debugging or admin purposes)
(def raw-data (pick/read-all-records db "PRODUCT"))
;; Update product price
(pick/update-record! db "PRODUCT" 1
{:price 1199.99
:categories "electronics]gaming]laptop]discount"})
;; Update customer information
(pick/update-record! db "CUSTOMER" 1
{:phone "555-0124"
:email "john.doe@newemail.com"})
;; Delete a product
(pick/delete-record! db "PRODUCT" 1)
;; Delete a customer
(pick/delete-record! db "CUSTOMER" 1)
A
)Direct mapping to table columns by position. Use for simple field access:
;; Map to first column (name)
(define-dictionary-field! db "CUSTOMER_DICT" "CUSTOMER_NAME" "A" "1" "" "Customer Name")
;; Map to second column (email)
(define-dictionary-field! db "CUSTOMER_DICT" "EMAIL" "A" "2" "" "Email Address")
;; Map to third column (phone)
(define-dictionary-field! db "CUSTOMER_DICT" "PHONE" "A" "3" "" "Phone Number")
T
)Lookup values from related tables using the format T{table};{field}
:
;; Lookup customer name from CUSTOMER table
(define-dictionary-field! db "ORDER_DICT" "CUSTOMER_NAME" "T" "1" "TCUSTOMER;name" "Customer Name")
;; Lookup product names from PRODUCT table
(define-dictionary-field! db "ORDER_DICT" "PRODUCT_NAMES" "T" "2" "TPRODUCT;name" "Product Names")
;; Lookup supplier contact from SUPPLIER table
(define-dictionary-field! db "PRODUCT_DICT" "SUPPLIER_EMAIL" "T" "1" "TSUPPLIER;email" "Supplier Email")
C
)Perform calculations using Clojure expressions or legacy operations:
;; Full Clojure expressions
(define-dictionary-field! db "INVOICE_DICT" "SUBTOTAL" "C" "" "(+ 99.99 49.99 29.99)" "Subtotal")
(define-dictionary-field! db "INVOICE_DICT" "TAX" "C" "" "(* SUBTOTAL 0.08)" "Tax (8%)")
(define-dictionary-field! db "INVOICE_DICT" "TOTAL" "C" "" "(+ SUBTOTAL TAX)" "Total Amount")
;; Legacy operations (still supported)
(define-dictionary-field! db "PRODUCT_DICT" "TOTAL_STOCK" "C" "" "SUM:STOCK_LEVELS" "Total Stock")
(define-dictionary-field! db "PRODUCT_DICT" "LINE_TOTALS" "C" "" "MULTIPLY:QUANTITIES,PRICES" "Line Totals")
Expressions automatically bind variables from other dictionary fields:
;; Define base fields first
(define-dictionary-field! db "INVOICE_DICT" "QUANTITY" "A" "1" "" "Quantity")
(define-dictionary-field! db "INVOICE_DICT" "PRICE" "A" "2" "" "Unit Price")
;; Computed field can reference previous fields
(define-dictionary-field! db "INVOICE_DICT" "LINE_TOTAL" "C" "" "(* QUANTITY PRICE)" "Line Total")
(define-dictionary-field! db "INVOICE_DICT" "DISCOUNTED_TOTAL" "C" "" "(* LINE_TOTAL 0.9)" "90% Discount")
For multivalue fields, expressions are evaluated element-wise:
;; If quantities = "2]1]5" and prices = "10]20]30"
(define-dictionary-field! db "INVOICE_DICT" "LINE_TOTALS" "C" "" "MULTIPLY:QUANTITIES,PRICES" "Line Totals")
;; Result: "20]20]150"
(define-dictionary-field! db "INVOICE_DICT" "SUBTOTAL" "C" "" "SUM:LINE_TOTALS" "Subtotal")
;; Result: "190"
;; Conditional logic
(define-dictionary-field! db "PRODUCT_DICT" "STOCK_STATUS" "C" ""
"(if (> TOTAL_STOCK 100) \"In Stock\" \"Low Stock\")" "Stock Status")
;; String manipulation
(define-dictionary-field! db "CUSTOMER_DICT" "INITIALS" "C" ""
"(str (first (clojure.string/upper-case (subs FIRST_NAME 0 1)))
(first (clojure.string/upper-case (subs LAST_NAME 0 1))))" "Initials")
;; Date calculations
(define-dictionary-field! db "ORDER_DICT" "DAYS_AGO" "C" ""
"(/ (- (System/currentTimeMillis) (.getTime (java.text.SimpleDateFormat. \"yyyy-MM-dd\") (.parse ORDER_DATE))) (* 24 60 60 1000))" "Days Since Order")
CUSTOMER_NAME
);; Always wrap database operations in try-catch
(try
(pick/create-record! db "PRODUCT" product-data)
(catch Exception e
(log/error "Failed to create product:" (.getMessage e))
(throw e)))
;; Validate data before operations
(defn create-product! [db product]
{:pre [(string? (:name product))
(number? (:price product))
(pos? (:price product))]}
(pick/create-record! db "PRODUCT" product))
For production use, configure your database connection appropriately:
;; Development
(def db {:dbtype "sqlite" :dbname "app.db"})
;; Production (with connection pooling)
(def db {:dbtype "postgresql"
:dbname "production_db"
:host "localhost"
:port 5432
:user "app_user"
:password "secure_password"
:maximum-pool-size 10})
-- Add indexes for better query performance
CREATE INDEX idx_product_name ON PRODUCT(name);
CREATE INDEX idx_customer_email ON CUSTOMER(email);
CREATE INDEX idx_order_date ON ORDER(order_date);
"Table doesn't exist" errors:
create-file!
before using the table"Dictionary field not found" errors:
create-dictionary!
"Expression evaluation failed" errors:
read-all-records
to inspect raw dataMultivalue parsing issues:
]
character only;; Inspect raw data without dictionary interpretation
(def raw-records (pick/read-all-records db "PRODUCT"))
(doseq [record raw-records]
(println "Raw record:" record))
;; Test expressions in REPL
(def test-data {:QUANTITY 5 :PRICE 10.99})
;; Test expression: (* QUANTITY PRICE)
(eval `(let [~'QUANTITY ~(:QUANTITY test-data)
~'PRICE ~(:PRICE test-data)]
(* QUANTITY PRICE)))
doc/example_usage.clj
lein test
to verify functionalitylein repl
for interactive debuggingSee doc/example_usage.clj
for a comprehensive example including:
Run the complete example:
(load-file "doc/example_usage.clj")
(example-usage/run-complete-crud-demo)
;; Product table with rich multivalue data
(pick/create-file! db "PRODUCT"
{:id "INTEGER PRIMARY KEY AUTOINCREMENT"
:name "TEXT NOT NULL"
:price "REAL"
:categories "TEXT"
:tags "TEXT"
:specifications "TEXT"
:images "TEXT"})
;; Dictionary with business logic
(pick/create-dictionary! db "PRODUCT_DICT")
(pick/define-dictionary-field! db "PRODUCT_DICT" "PRODUCT_NAME" "A" "1" "" "Product Name")
(pick/define-dictionary-field! db "PRODUCT_DICT" "CATEGORIES" "A" "2" "" "Categories")
(pick/define-dictionary-field! db "PRODUCT_DICT" "TAGS" "A" "3" "" "Tags")
(pick/define-dictionary-field! db "PRODUCT_DICT" "IS_FEATURED" "C" "" "(some #(= % \"featured\") TAGS)" "Is Featured")
(pick/define-dictionary-field! db "PRODUCT_DICT" "CATEGORY_COUNT" "C" "" "(count CATEGORIES)" "Category Count")
;; Invoice table with complex calculations
(pick/create-file! db "INVOICE"
{:id "INTEGER PRIMARY KEY AUTOINCREMENT"
:customer_id "INTEGER"
:line_items "TEXT" ;; JSON-like multivalue structure
:tax_rate "REAL"
:discount_percent "REAL"})
;; Advanced financial calculations
(pick/create-dictionary! db "INVOICE_DICT")
(pick/define-dictionary-field! db "INVOICE_DICT" "CUSTOMER_NAME" "T" "1" "TCUSTOMER;name" "Customer Name")
(pick/define-dictionary-field! db "INVOICE_DICT" "SUBTOTAL" "C" "" "(reduce + LINE_ITEM_TOTALS)" "Subtotal")
(pick/define-dictionary-field! db "INVOICE_DICT" "TAX_AMOUNT" "C" "" "(* SUBTOTAL (/ TAX_RATE 100))" "Tax Amount")
(pick/define-dictionary-field! db "INVOICE_DICT" "DISCOUNT_AMOUNT" "C" "" "(* SUBTOTAL (/ DISCOUNT_PERCENT 100))" "Discount Amount")
(pick/define-dictionary-field! db "INVOICE_DICT" "TOTAL" "C" "" "(- (+ SUBTOTAL TAX_AMOUNT) DISCOUNT_AMOUNT)" "Final Total")
# Run all tests
lein test
# Run specific test namespace
lein test pickdict.core-test
# Run tests with coverage
lein cloverage
# Start REPL
lein repl
# Run tests on file changes
lein test-refresh
# Check for outdated dependencies
lein ancient
# Generate documentation
lein codox
# Build JAR
lein jar
# Install locally
lein install
# Run linter
lein eastwood
# Check test coverage
lein cloverage
# Run all quality checks
lein do test, eastwood, cloverage
We welcome contributions to PickDict! Please see our Contributing Guide for details.
git clone https://github.com/yourusername/pickdict.git
git checkout -b feature/your-feature
lein test
Copyright © 2025 Hector
Distributed under the Eclipse Public License 2.0.
This project is not affiliated with Rocket Software or other Pick/D3 vendors. "Pick" and "D3" are trademarks of Rocket Software, Inc.
PickDict - Bringing the power of multivalue databases to modern Clojure applications.
Can you improve this documentation?Edit on GitHub
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 |