<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: fumi</title>
    <description>The latest articles on DEV Community by fumi (@conpoi).</description>
    <link>https://dev.to/conpoi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3796370%2F29e8c81c-744f-47e1-959a-c825284c73c3.jpeg</url>
      <title>DEV Community: fumi</title>
      <link>https://dev.to/conpoi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/conpoi"/>
    <language>en</language>
    <item>
      <title>bisql (Clojure Data Access Library) released v0.4.0: Support Malli Validation</title>
      <dc:creator>fumi</dc:creator>
      <pubDate>Thu, 23 Apr 2026 19:54:18 +0000</pubDate>
      <link>https://dev.to/conpoi/bisql-clojure-data-access-library-released-v040-support-malli-validation-1o0o</link>
      <guid>https://dev.to/conpoi/bisql-clojure-data-access-library-released-v040-support-malli-validation-1o0o</guid>
      <description>&lt;h2&gt;
  
  
  Bisql
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;strong&gt;Bisql&lt;/strong&gt;, a data access library for &lt;strong&gt;2-way SQL&lt;/strong&gt; in Clojure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hatappo/bisql" rel="noopener noreferrer"&gt;https://github.com/hatappo/bisql&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since it's “2-way,” I called it &lt;strong&gt;bisql&lt;/strong&gt; with the &lt;code&gt;bi-&lt;/code&gt; prefix.&lt;br&gt;&lt;br&gt;
It is pronounced like &lt;strong&gt;bicycle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A common weakness of SQL-template libraries, not just 2-way SQL libraries, is that writing &lt;strong&gt;all&lt;/strong&gt; SQL as templates can become tedious.&lt;/p&gt;

&lt;p&gt;To address that, many SQL-template libraries support a &lt;strong&gt;query builder&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
You can write some queries as templates and others with a builder.&lt;/p&gt;

&lt;p&gt;But I think that approach is fundamentally inconsistent.&lt;/p&gt;

&lt;p&gt;If every query function is maintained as SQL or SQL templates, the cost of reviewing and understanding the data access layer drops significantly. Every database access has a concrete representation as an actual SQL file.&lt;/p&gt;

&lt;p&gt;Once a query builder gets mixed in, that consistency is gone.&lt;/p&gt;

&lt;p&gt;And in practice, what starts as “we’ll only use the builder for simple queries” often drifts into using it for complex queries as well, until the generated SQL is no longer obviously what you intended.&lt;/p&gt;

&lt;p&gt;Bisql takes a different approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;every database access should be written as SQL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That said, hand-writing even simple CRUD operations for every table is tedious. So Bisql takes another approach there: it &lt;strong&gt;generates a large and comprehensive set of typical CRUD queries automatically&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It connects to a real database, inspects the schema, considers indexes, and generates SQL templates for many index-friendly query patterns.&lt;/p&gt;

&lt;p&gt;Then the &lt;code&gt;defquery&lt;/code&gt; macro converts all of those &lt;code&gt;.sql&lt;/code&gt; template files into Clojure functions at once.&lt;/p&gt;

&lt;p&gt;So you keep the consistency of SQL-first development without the repetitive CRUD work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Malli Support
&lt;/h2&gt;

&lt;p&gt;In this release, generated SQL templates can now include &lt;code&gt;:malli/in&lt;/code&gt; and &lt;code&gt;:malli/out&lt;/code&gt; declaration metadata.&lt;/p&gt;

&lt;p&gt;These hold schemas for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the parameters passed to a query function&lt;/li&gt;
&lt;li&gt;the response data returned from the query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bisql SQL templates can already carry arbitrary metadata that becomes metadata on the generated query functions. Malli support builds on that.&lt;/p&gt;

&lt;p&gt;If a query function has &lt;code&gt;:malli/in&lt;/code&gt; and &lt;code&gt;:malli/out&lt;/code&gt; metadata, Bisql can automatically run Malli validation during query execution. This behavior is configurable.&lt;/p&gt;

&lt;p&gt;Bisql also generates a base Malli schema file for each table as &lt;code&gt;schema.clj&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
The &lt;code&gt;:malli/in&lt;/code&gt; and &lt;code&gt;:malli/out&lt;/code&gt; metadata refer to those generated schemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example of a generated query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*:name crud.get-by-id */&lt;/span&gt;
&lt;span class="cm"&gt;/*:cardinality :one */&lt;/span&gt;
&lt;span class="cm"&gt;/*:malli/in [:map {:closed true} [:id int?]] */&lt;/span&gt;
&lt;span class="cm"&gt;/*:malli/out [:maybe sql.postgresql.public.users.schema/row] */&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/*$id*/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example of a generated schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sql.postgresql.public.users.schema&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:refer-clojure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:exclude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bisql.schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bisql.schema&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:clojure-lsp/ignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:clojure-lsp/unused-public-var&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:map&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;int?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bisql.schema/malli-default-sentinel&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:display-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bisql.schema/malli-default-sentinel&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:created-at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bisql.schema/offset-date-time?&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bisql.schema/malli-default-sentinel&lt;/span&gt;&lt;span class="p"&gt;]]])&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:clojure-lsp/ignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:clojure-lsp/unused-public-var&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bisql.schema/malli-map-all-entries-optional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:clojure-lsp/ignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:clojure-lsp/unused-public-var&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;bisql.schema/malli-map-all-entries-strip-default-sentinel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, typical CRUD queries and their schemas can now be generated automatically, and validation can run transparently with very little manual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Small Expression Language for &lt;code&gt;if&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This release also adds a small expression language for &lt;code&gt;if&lt;/code&gt; conditions inside SQL templates.&lt;/p&gt;

&lt;p&gt;That makes conditional rendering more expressive without leaving SQL templates or introducing a separate query builder layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/hatappo/bisql" rel="noopener noreferrer"&gt;https://github.com/hatappo/bisql&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Malli validation docs: &lt;a href="https://hatappo.github.io/bisql/docs/malli-validation/" rel="noopener noreferrer"&gt;https://hatappo.github.io/bisql/docs/malli-validation/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Clojars: &lt;a href="https://clojars.org/io.github.hatappo/bisql" rel="noopener noreferrer"&gt;https://clojars.org/io.github.hatappo/bisql&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clojure</category>
      <category>sql</category>
      <category>database</category>
      <category>rdb</category>
    </item>
  </channel>
</rss>
