{"id":13709872,"url":"https://github.com/ak-coram/cl-duckdb","last_synced_at":"2025-08-26T20:51:11.022Z","repository":{"id":61932326,"uuid":"453540240","full_name":"ak-coram/cl-duckdb","owner":"ak-coram","description":"Common Lisp CFFI wrapper around the DuckDB C API","archived":false,"fork":false,"pushed_at":"2024-11-05T19:03:31.000Z","size":173,"stargazers_count":36,"open_issues_count":4,"forks_count":1,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-11-05T20:21:05.138Z","etag":null,"topics":["c-bindings","common-lisp","data-science","duckdb","lisp","olap","parquet","sql"],"latest_commit_sha":null,"homepage":"https://github.com/ak-coram/cl-duckdb/blob/main/README.org","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ak-coram.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-29T23:07:50.000Z","updated_at":"2024-11-05T19:03:53.000Z","dependencies_parsed_at":"2024-02-13T18:27:06.895Z","dependency_job_id":"d770ded9-e621-497d-8b0e-832ed1fc5992","html_url":"https://github.com/ak-coram/cl-duckdb","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ak-coram%2Fcl-duckdb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ak-coram%2Fcl-duckdb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ak-coram%2Fcl-duckdb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ak-coram%2Fcl-duckdb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ak-coram","download_url":"https://codeload.github.com/ak-coram/cl-duckdb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224521404,"owners_count":17325229,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-bindings","common-lisp","data-science","duckdb","lisp","olap","parquet","sql"],"created_at":"2024-08-02T23:00:47.625Z","updated_at":"2025-08-26T20:51:10.032Z","avatar_url":"https://github.com/ak-coram.png","language":"Common Lisp","funding_links":[],"categories":["Client APIs","Expert Systems"],"sub_categories":[],"readme":"* cl-duckdb\n\n#+begin_html\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://upload.wikimedia.org/wikipedia/commons/4/43/Pair_of_mandarin_ducks.jpg\" target=\"_blank\"\u003e\n    \u003cimg alt=\"鴛鴦戲水\" title=\"鴛鴦戲水\" src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Pair_of_mandarin_ducks.jpg/440px-Pair_of_mandarin_ducks.jpg\" width=\"220\" height=\"165\"\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ak-coram/cl-duckdb/actions\"\u003e\n    \u003cimg alt=\"Build Status\" src=\"https://github.com/ak-coram/cl-duckdb/workflows/CI/badge.svg\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n#+end_html\n\nCommon Lisp [[https://cffi.common-lisp.dev/][CFFI]] wrapper around the [[https://duckdb.org/][DuckDB]] C API\n\n** Dependencies\n\nCurrently the following Common Lisp implementations and operating\nsystems are tested via [[https://github.com/ak-coram/cl-duckdb/blob/main/.github/workflows/CI.yml][CI]]. Android via Termux (ECL \u0026 SBCL) and some\nother BSDs are also known to work.\n\n- [[https://sbcl.org/][SBCL]] (Linux, FreeBSD, Windows, macOS, macOS on AArch64)\n- [[https://ccl.clozure.com/][CCL]] (Linux, FreeBSD, macOS)\n- [[https://ecl.common-lisp.dev/][ECL]] (Linux, FreeBSD, macOS, macOS on AArch64), see the [[./ECL.org][ECL.org]] document for specifics\n\nThe following native libraries need to be installed in a location\nwhere CFFI can find them:\n\n- [[https://sourceware.org/libffi/][libffi]]\n- [[https://duckdb.org/][DuckDB]]\n\nFor example on Ubuntu or Debian (amd64):\n\n#+begin_src sh\n  sudo apt-get install libffi-dev unzip\n  # Download libduckdb-linux-amd64.zip from the C/C++ section of https://duckdb.org/docs/installation/\n  sudo unzip ~/Downloads/libduckdb-linux-amd64.zip libduckdb.so -d /usr/lib/\n#+end_src\n\n** Installation\n\ncl-duckdb can be installed via [[https://www.quicklisp.org/][Quicklisp]]:\n\n#+begin_src lisp\n  (ql:quickload :duckdb)\n#+end_src\n\nThe latest version is available from the [[https://ultralisp.org/][Ultralisp]] distribution:\n\n#+begin_src lisp\n  ;; Install the ultralisp distribution if you don't have it already\n  (ql-dist:install-dist \"http://dist.ultralisp.org/\" :prompt nil)\n  ;; Load cl-duckdb\n  (ql:quickload :duckdb)\n#+end_src\n\nAlternatively you can also rely on [[https://github.com/ocicl/ocicl][ocicl]].\n\n** Packages\n\n- DUCKDB (nicknamed DDB): provides the high-level API.\n- DUCKDB-API: contains the low-level bindings to the DuckDB C API.\n\n** Usage\n\n*** Connecting to a database\n\nThis library relies on the special variable DUCKDB:*CONNECTION* for a\ndefault database connection. Setting up a global default connection is\nrecommmended for interactive REPL sessions:\n\n#+begin_src lisp\n  ;; Use an in-memory database as the default connection\n  (ddb:initialize-default-connection)\n#+end_src\n\n#+begin_src lisp\n  ;; Use a persistent database as the default connection\n  (ddb:initialize-default-connection :path \"my_database.ddb\")\n#+end_src\n\n#+begin_src lisp\n  ;; Clean up the default connection at the end of the session\n  (duckdb:disconnect-default-connection)\n#+end_src\n\nFor manual connection management most functions requiring a database\nconnection also accept a connection object as a keyword argument (see\nDUCKDB:OPEN-DATABASE and DUCKDB:CONNECT for creating one).\n\nTo dynamically bind and automatically clean up a default connection,\nrefer to DUCKDB:WITH-DEFAULT-CONNECTION and\nDUCKDB:WITH-TRANSIENT-CONNECTION instead.\n\n*** Basic example\n\n#+begin_src lisp\n  ;; Use an in-memory transient database\n  (ddb:with-transient-connection\n    ;; Create a new range table containing integers\n    (ddb:run \"CREATE TABLE range (i INTEGER PRIMARY KEY)\"\n             \"CREATE SEQUENCE seq_range_i START 1\")\n\n    ;; Use a prepared statement to populate the table with a 1000 values\n    (ddb:with-statement (statement \"INSERT INTO range VALUES (nextval('seq_range_i'))\")\n      (dotimes (_ 1000) (ddb:perform statement)))\n\n    ;; Solve Project Euler Problem 9\n    (let* ((euler9-query (ddb:concat \"SELECT a.i * b.i * c.i AS solution \"\n                                     \"FROM range AS c \"\n                                     \"JOIN range AS b ON b.i \u003c c.i \"\n                                     \"JOIN range AS a ON a.i \u003c b.i \"\n                                     \"WHERE a.i + b.i + c.i = ? \"\n                                     \"AND a.i * a.i + b.i * b.i = c.i * c.i\"))\n           (parameters '(1000))\n           (results (ddb:query euler9-query parameters)))\n      (format t \"PE9 Solution: ~a~%\" (ddb:get-result results 'solution 0))))\n#+end_src\n\n*** Interactive example: query remote Parquet data\n\nThe DUCKDB:Q (short for QUERY) and DUCKDB:FQ (short for FORMAT-QUERY)\nfunctions are provided as shorthands for interactive REPL use:\n\n#+begin_src lisp\n  (ddb:initialize-default-connection)\n  (ddb:q \"INSTALL httpfs\") ; =\u003e ((\"Success\" . #()))\n  (let ((url \"https://github.com/apache/parquet-mr/raw/master/parquet-hadoop/src/test/resources/test-file-with-no-column-indexes-1.parquet\"))\n    (ddb:fq \"SELECT * FROM read_parquet(?) WHERE id \u003c 10\" url))\n  ;; +----+------+--------------------------------+----------------------------------------+\n  ;; | id | name | location                       | phoneNumbers                           |\n  ;; +----+------+--------------------------------+----------------------------------------+\n  ;; |  0 | p0   | NIL                            | ((phone ((number . 0) (kind . cell)))) |\n  ;; |  1 | p1   | ((lon . 1.0d0) (lat . 2.0d0))  | ((phone ((number . 1) (kind . cell)))) |\n  ;; |  2 | p2   | ((lon . 2.0d0) (lat))          | ((phone ((number . 2) (kind . cell)))) |\n  ;; |  3 | p3   | NIL                            | ((phone ((number . 3) (kind . cell)))) |\n  ;; |  4 | p4   | ((lon . 4.0d0) (lat . 8.0d0))  | ((phone ((number . 4) (kind . cell)))) |\n  ;; |  5 | p5   | ((lon . 5.0d0) (lat))          | ((phone ((number . 5) (kind . cell)))) |\n  ;; |  6 | p6   | NIL                            | ((phone ((number . 6) (kind . cell)))) |\n  ;; |  7 | p7   | ((lon . 7.0d0) (lat . 14.0d0)) | ((phone ((number . 7) (kind . cell)))) |\n  ;; |  8 | p8   | ((lon . 8.0d0) (lat))          | ((phone ((number . 8) (kind . cell)))) |\n  ;; |  9 | p9   | NIL                            | ((phone ((number . 9) (kind . cell)))) |\n  ;; +----+------+--------------------------------+----------------------------------------+\n  ;; =\u003e NIL\n#+end_src\n\n*** Sparks\n\nThere's some support for plotting query results directly in the REPL\nvia [[https://github.com/tkych/cl-spark][cl-spark]]:\n\n#+begin_src lisp\n  (ddb:initialize-default-connection) ; =\u003e #\u003cDUCKDB::CONNECTION {1014081EF3}\u003e\n\n  (ddb:bind-static-table\n   'numbers `((\"x\" . (,(loop :for i :from 0d0 :by 0.2 :below pi :collect i)\n                       :duckdb-double)))) ; =\u003e NIL\n\n  (ddb:spark-query \"SELECT x, sin(x) AS y, cos(x) AS z FROM numbers\" nil '(x y z))\n  ;; X ▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇█\n  ;; Y ▁▂▃▄▆▆▇▇█▇▇▆▅▄▃▁\n  ;; Z █▇▇▇▆▆▅▅▄▃▃▂▁▁▁▁\n  ;; =\u003e NIL\n\n  (ddb:vspark-query \"SELECT pow(2, x) AS y FROM numbers\" nil nil 'y)\n  ;; 1.0                    4.5                     8.0\n  ;; ˫-----------------------+------------------------˧\n  ;; ▏\n  ;; █▏\n  ;; ██▎\n  ;; ███▋\n  ;; █████▍\n  ;; ███████▏\n  ;; █████████▎\n  ;; ███████████▋\n  ;; ██████████████▌\n  ;; █████████████████▊\n  ;; █████████████████████▍\n  ;; █████████████████████████▋\n  ;; ██████████████████████████████▌\n  ;; ████████████████████████████████████▎\n  ;; ██████████████████████████████████████████▋\n  ;; ██████████████████████████████████████████████████\n  ;; =\u003e NIL\n\n  (ddb:vspark-query \"SELECT round(x, 2)::text AS x, sqrt(x) AS y FROM numbers\" nil\n                    'x 'y)\n  ;;     0.0            0.8660254             1.7320508\n  ;;     ˫---------------------+----------------------˧\n  ;; 0.0 ▏\n  ;; 0.2 ███████████▉\n  ;; 0.4 ████████████████▊\n  ;; 0.6 ████████████████████▋\n  ;; 0.8 ███████████████████████▊\n  ;; 1.0 ██████████████████████████▌\n  ;; 1.2 █████████████████████████████▏\n  ;; 1.4 ███████████████████████████████▍\n  ;; 1.6 █████████████████████████████████▋\n  ;; 1.8 ███████████████████████████████████▋\n  ;; 2.0 █████████████████████████████████████▌\n  ;; 2.2 ███████████████████████████████████████▍\n  ;; 2.4 █████████████████████████████████████████▎\n  ;; 2.6 ██████████████████████████████████████████▊\n  ;; 2.8 ████████████████████████████████████████████▌\n  ;; 3.0 ██████████████████████████████████████████████\n  ;; =\u003e NIL\n#+end_src\n\n*** Writing queries via SxQL\n\nIf you want to use a syntax based on s-expressions for your queries,\nthen the SxQL library is an option:\n\n#+begin_src lisp\n  (ddb:initialize-default-connection)\n\n  ;; Load SxQL\n  (ql:quickload :sxql)\n  (use-package :sxql)\n\n  ;; Create a table\n  (ddb:run (yield (create-table :numbers\n                    ((i :type 'integer\n                        :primary-key t)))))\n\n  ;; Define utility function\n  (defun query-sxql (q) (multiple-value-call #'ddb:query (yield q)))\n\n  ;; Populate table with values\n  (loop :for x :below 100\n        :do (query-sxql (insert-into :numbers (set= :i x))))\n\n  (query-sxql (select ((:as (:sum :i) :sum))\n                (from :numbers)\n                (where (:even :i))))\n  ;; =\u003e ((\"sum\" . #(4950)))\n#+end_src\n\nPlease refer to the [[https://github.com/fukamachi/sxql][SxQL documentation]] for more examples.\n\n*** Appenders\n\n[[https://duckdb.org/docs/data/appender][Appenders]] are one of the ways of loading bulk data into DuckDB. They append rows to a single table of a database:\n\n#+begin_src lisp\n  (ddb:initialize-default-connection) ; =\u003e #\u003cDUCKDB::CONNECTION {100B1088F3}\u003e\n\n  (ddb:run \"CREATE TABLE roman_numerals (i INTEGER, value TEXT)\") ; =\u003e NIL\n  (ddb:with-appender (appender \"roman_numerals\")\n    (loop :for i :from 1 :below 4999\n          :do (ddb:append-row appender (list i (format nil \"~:@R\" i))))) ; =\u003e NIL\n  (ddb:get-result (ddb:query \"SELECT * FROM roman_numerals WHERE i = 1848\" nil)\n                  'value 0) ; =\u003e \"MDCCCXXXXVIII\"\n#+end_src\n\n*** Querying Lisp vectors and lists as table columns\n\nCurrently only the following types are supported (the values are\ncurrently copied into DuckDB data chunks internally). Using a\ncombination of vectors and list for different columns is possible, but\neach column should have the same length. Tables using Lisp data\nstructures are not bound to a single connection and work across\ndifferent ones.\n\n**** Specialized vectors\n\n| Common Lisp type    | DuckDB Type |\n|---------------------+-------------|\n| bit                 | BOOLEAN     |\n| (unsigned-byte 8)   | UTINYINT    |\n| (unsigned-byte 16)  | USMALLINT   |\n| (unsigned-byte 32)  | UINTEGER    |\n| (unsigned-byte 64)  | UBIGINT     |\n| (unsigned-byte 128) | UHUGEINT    |\n| (signed-byte 8)     | TINYINT     |\n| (signed-byte 16)    | SMALLINT    |\n| (signed-byte 32)    | INTEGER     |\n| (signed-byte 64)    | BIGINT      |\n| (signed-byte 128)   | HUGEINT     |\n| single-float        | REAL        |\n| double-float        | DOUBLE      |\n\n**** Lists \u0026 unspecialized vectors\n\nList columns or unspecialized vectors need to specify the DuckDB\ncolumn type and can contain the following values:\n\n- Booleans (nil, t, :false, :true, :null)\n- Integers in range of the corresponding column type\n- Floating point numbers (single-float \u0026 double-float)\n- Strings\n- Date, time or datetime values\n- UUIDs\n- NIL values\n\n**** Examples\n\n#+begin_src lisp\n  (ddb:initialize-default-connection) ; =\u003e #\u003cDUCKDB::CONNECTION {10074E8BE3}\u003e\n\n  ;; Use vectors as columns in a query:\n  (let ((indexes (make-array '(10) :element-type '(unsigned-byte 8)\n                                   :initial-contents '(1 2 3 4 5 6 7 8 9 10)))\n        (primes (make-array '(10) :element-type '(unsigned-byte 8)\n                                  :initial-contents '(2 3 5 7 11 13 17 19 23 29))))\n    (ddb:with-static-table ('primes `((i . ,indexes)\n                                      (p . ,primes)))\n      (ddb:format-query \"SELECT * FROM primes\" nil)))\n  ;; +----+----+\n  ;; | i  | p  |\n  ;; +----+----+\n  ;; |  1 |  2 |\n  ;; |  2 |  3 |\n  ;; |  3 |  5 |\n  ;; |  4 |  7 |\n  ;; |  5 | 11 |\n  ;; |  6 | 13 |\n  ;; |  7 | 17 |\n  ;; |  8 | 19 |\n  ;; |  9 | 23 |\n  ;; | 10 | 29 |\n  ;; +----+----+\n  ;; =\u003e NIL\n\n  ;; DuckDB column types always have to be specified for lists (NIL\n  ;; values are converted to NULL):\n  (ddb:with-static-table ('integers `((i . (,(loop :for i :below 1000\n                                                   :if (evenp i) :collect i\n                                                     :else :collect nil)\n                                            :duckdb-integer))))\n    (ddb:query (ddb:concat \"SELECT sum(i) AS sum \"\n                           \", COUNT(i) AS not_null_count \"\n                           \"FROM integers\")\n               nil)) ; =\u003e ((\"sum\" . #(249500)) (\"not_null_count\" . #(500)))\n\n  (ddb:with-static-table ('lyrics `((\"in the year\" . (,(list (format nil \"~R\" 2525))\n                                                      :duckdb-varchar))))\n    (ddb:query \"SELECT * FROM lyrics\" nil))\n  ;; =\u003e ((\"in the year\" . #(\"two thousand five hundred twenty-five\")))\n\n  ;; If another table with the same name exists, you can use the\n  ;; static_table table function directly:\n  (ddb:run (ddb:concat \"CREATE TABLE polysemy (\\\"That you have but slumbered here, \"\n                       \"While these visions did appear\\\" VARCHAR)\"))\n  (ddb:with-static-table\n      ('polysemy `((\"If we shadows have offended, Think but this, and all is mended:\"\n                    . (() :duckdb-varchar))))\n    (ddb:query (ddb:concat \"SELECT A.*, B.* FROM static_table('polysemy') AS A \"\n                           \"JOIN polysemy AS B ON true\")\n               nil))\n  ;; =\u003e ((\"If we shadows have offended, Think but this, and all is mended:\" . #())\n  ;;     (\"That you have but slumbered here, While these visions did appear\" . #()))\n\n  (ddb:with-static-table ('bools `((v . ((nil t :false :true :null)\n                                         :duckdb-boolean))))\n    (ddb:format-query \"SELECT v, v IS NULL AS is_null FROM bools\" nil))\n  ;; +-----+---------+\n  ;; | v   | is_null |\n  ;; +-----+---------+\n  ;; | NIL | NIL     |\n  ;; | T   | NIL     |\n  ;; | NIL | NIL     |\n  ;; | T   | NIL     |\n  ;; | NIL | T       |\n  ;; +-----+---------+\n  ;; =\u003e NIL\n\n  ;; Static tables can be managed in the global scope using the\n  ;; BIND-STATIC-TABLE, UNBIND-STATIC-TABLE and CLEAR-STATIC-TABLES\n  ;; functions. Temporarily overriding a table definition via\n  ;; WITH-STATIC-TABLE works as expected:\n  (ddb:bind-static-table\n   'alphabet\n   `((c . ((\"α\" \"β\" \"γ\" \"δ\") :duckdb-varchar)))) ; =\u003e NIL\n\n  (labels ((get-characters ()\n             (loop :with results := (ddb:query \"SELECT c FROM alphabet\" nil)\n                   :for c :across (ddb:get-result results 'c)\n                   :collect c)))\n    (ddb:with-static-table ('alphabet `((c . ((\"Ⴀ\" \"Ⴁ\" \"Ⴂ\" \"Ⴃ\")\n                                              :duckdb-varchar))))\n      (ddb:with-static-table ('alphabet `((c . ((\"𐌀\" \"𐌁\" \"𐌂\" \"𐌃\" \"𐌄\")\n                                                :duckdb-varchar))))\n        (format t \"Etruscan: ~{~a~^, ~}~%\" (get-characters)))\n      (format t \"Asomtavruli: ~{~a~^, ~}~%\" (get-characters)))\n    (format t \"Greek: ~{~a~^, ~}~%\" (get-characters)))\n  ;; Etruscan: 𐌀, 𐌁, 𐌂, 𐌃, 𐌄\n  ;; Asomtavruli: Ⴀ, Ⴁ, Ⴂ, Ⴃ\n  ;; Greek: α, β, γ, δ\n  ;; =\u003e NIL\n\n  (ddb:unbind-static-table 'alphabet) ; =\u003e NIL\n  (ddb:clear-static-tables) ; =\u003e NIL\n#+end_src\n\n** Type \u0026 Value conversions\n\n| DuckDB Type     | Common Lisp Type             | Note                                          |\n|-----------------+------------------------------+-----------------------------------------------|\n| NULL            | null                         | nil (or :null for param. binding)             |\n| BOOLEAN         | boolean                      | t, nil (or :true \u0026 :false for param. binding) |\n| VARCHAR         | string                       |                                               |\n| BLOB            | (vector (unsigned-byte 8))   |                                               |\n| REAL            | single-float                 |                                               |\n| DOUBLE          | double-float                 |                                               |\n| DECIMAL         | ratio                        | Max width of 38                               |\n| TINYINT         | integer                      |                                               |\n| UTINYINT        | integer                      |                                               |\n| SMALLINT        | integer                      |                                               |\n| USMALLINT       | integer                      |                                               |\n| INTEGER         | integer                      |                                               |\n| UINTEGER        | integer                      |                                               |\n| BIGINT          | integer                      |                                               |\n| UBIGINT         | integer                      |                                               |\n| HUGEINT         | integer                      |                                               |\n| UHUGEINT        | integer                      |                                               |\n| DATE            | local-time:date              |                                               |\n| TIMESTAMP       | local-time:timestamp         | Microsecond precision                         |\n| TIME            | local-time-duration:duration | Microsecond precision                         |\n| INTERVAL        | periods:duration             | Microsecond precision                         |\n| UUID            | frugal-uuid:uuid             |                                               |\n| ENUM types      | string                       |                                               |\n| LIST types      | list                         |                                               |\n| STRUCT types    | alist                        |                                               |\n| UNION types     | *                            | Maps to one of the member types (or nil)      |\n| BIT (BITSTRING) | bit-vector                   |                                               |\n\n- https://github.com/dlowe-net/local-time\n- https://github.com/enaeher/local-time-duration\n- https://github.com/jwiegley/periods\n- https://github.com/ak-coram/cl-frugal-uuid\n\n*** NIL as boolean FALSE vs NIL as NULL / custom return value for SQL NULL\n\n#+begin_src lisp\n  (ddb:initialize-default-connection)\n  ;; =\u003e #\u003cDUCKDB::CONNECTION {101CAC0A73}\u003e\n\n  ;; The boolean TRUE and FALSE values are mapped to T and NIL\n  ;; respectively in Lisp, but SQL NULL is also mapped to NIL causing\n  ;; some ambiguity:\n\n  (ddb:query \"SELECT TRUE AS x, FALSE AS y, NULL AS z\" '())\n  ;; =\u003e ((\"x\" . #(T)) (\"y\" . #(NIL)) (\"z\" . #(NIL)))\n\n  ;; When necessary it's possible to differentiate between FALSE and\n  ;; NULL by simply using the IS NULL logical operator:\n\n  (ddb:query \"SELECT FALSE IS NOT NULL AS x, NULL IS NULL AS y\" '())\n  ;; =\u003e ((\"x\" . #(T)) (\"y\" . #(T)))\n\n  ;; When binding parameter values, NIL is bound as FALSE when DuckDB\n  ;; can determine that the parameter type is boolean and as NULL\n  ;; otherwise. This means that simple cases like the following work as\n  ;; expected:\n\n  (ddb:run \"CREATE TABLE values (v BOOLEAN)\"\n           '(\"INSERT INTO values (v) VALUES (?)\" (nil))) ; =\u003e NIL\n  (ddb:query \"SELECT v, v IS NOT NULL AS is_not_null FROM values\" '())\n  ;; =\u003e ((\"v\" . #(NIL)) (\"is_not_null\" . #(T)))\n\n  ;; In some cases DuckDB doesn't determine parameter types based on the\n  ;; query and NIL is bound as NULL even for boolean parameters:\n\n  (ddb:query \"SELECT ?::boolean || '' IS NULL AS x\" '(nil))\n  ;; =\u003e ((\"x\" . #(T)))\n\n  ;; To differentiate between FALSE and NULL unambiguously when binding\n  ;; boolean parameters, the keywords :FALSE and :NULL can be used. In\n  ;; the query below the first parameter type is not determined by\n  ;; DuckDB, so NIL would be bound AS NULL as seen in the similar\n  ;; example directly above. The second parameter type is correctly\n  ;; identified as boolean, so NIL would be bound as FALSE in this case.\n\n  (ddb:query \"SELECT ?::boolean || '' IS NOT NULL AS x, ?::boolean IS NULL AS y\"\n             '(:false :null)) ; =\u003e ((\"x\" . #(T)) (\"y\" . #(T)))\n\n  ;; For completeness the :TRUE keyword is also supported. When used as\n  ;; a parameter value, it is equivalent to using T:\n\n  (ddb:query \"SELECT ? = ? AS x\" '(:true t)) ; =\u003e ((\"x\" . #(T)))\n#+end_src\n\nAlternatively the default return value for SQL NULL can be customized:\n\n#+begin_src lisp\n  (ddb:initialize-default-connection)\n  ;; =\u003e #\u003cDUCKDB::CONNECTION {10042C24C3}\u003e\n\n  ;; NIL is used by default\n  (ddb:query \"SELECT ? AS x\" '(:null)) ; =\u003e ((\"x\" . #(NIL)))\n\n  ;; Represent SQL NULL values as :NULL in the result for only one call\n  (ddb:query \"SELECT ? AS x\" '(:null)\n             :sql-null-return-value :null) ; =\u003e ((\"x\" . #(:NULL)))\n\n  ;; Change the default value\n  (setf ddb:*sql-null-return-value* :null) ; =\u003e :NULL\n  (ddb:query \"SELECT ? AS x\" '(:null)) ; =\u003e ((\"x\" . #(:NULL)))\n#+end_src\n\n** Development setup\n\n- Install [[https://www.quicklisp.org/][Quicklisp]]\n- Clone this repository and add it as a local Quicklisp project, for\n  example:\n\n#+begin_src sh\n  git clone git@github.com:ak-coram/cl-duckdb.git ~/Projects/cl-duckdb\n  ln -s ~/Projects/cl-duckdb ~/quicklisp/local-projects/cl-duckdb\n#+end_src\n\n- Start your favored REPL (e.g. sbcl) and load the library using\n  Quicklisp:\n\n#+begin_src lisp\n  (ql:quickload :duckdb)\n#+end_src\n\n*** Running tests\n\n- Load the tests via Quicklisp:\n\n#+begin_src lisp\n  (ql:quickload :duckdb/test)\n#+end_src\n\n- Use [[https://asdf.common-lisp.dev/][ASDF]] or [[https://fiveam.common-lisp.dev/][FiveAM]] to run the tests:\n\n#+begin_src lisp\n  ;; Using ASDF:\n  (asdf:test-system :duckdb)\n  ;; Using FiveAM directly:\n  (fiveam:run! :duckdb)\n#+end_src\n\n*** Running benchmarks\n\n- Load the benchmarks via Quicklisp:\n\n#+begin_src lisp\n  (ql:quickload :duckdb/benchmark)\n#+end_src\n\n- Use [[https://asdf.common-lisp.dev/][ASDF]] or run the benchmarks directly:\n\n#+begin_src lisp\n  ;; Using ASDF:\n  (asdf:test-system :duckdb/benchmark)\n  ;; Running directly:\n  (duckdb/benchmark:run-benchmarks)\n#+end_src\n\n** Legal\n\n- Released under the MIT License, same as DuckDB.\n- [[https://developercertificate.org/][Developer Certificate of Origin]]\n- [[https://en.wikipedia.org/wiki/File:Pair_of_mandarin_ducks.jpg][Source]] for README photo\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fak-coram%2Fcl-duckdb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fak-coram%2Fcl-duckdb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fak-coram%2Fcl-duckdb/lists"}