<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title/><link>http://www.matildasmeds.com/</link><description>Recent content on</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Matilda Smeds</copyright><lastBuildDate>Mon, 02 Sep 2024 22:20:00 +0200</lastBuildDate><atom:link href="http://www.matildasmeds.com/index.xml" rel="self" type="application/rss+xml"/><item><title>No More Unchecked Sqlx Queries</title><link>http://www.matildasmeds.com/posts/no-more-unchecked-sqlx-queries/</link><pubDate>Mon, 02 Sep 2024 22:20:00 +0200</pubDate><guid>http://www.matildasmeds.com/posts/no-more-unchecked-sqlx-queries/</guid><description>This post is intended for SQLx users, who want to take advantage of the powerful compile time checks SQLx crate provides. More specifically, we look into practical ways how to make any SQLx query checked.
The Rust compiler can catch many mistakes, preventing bugs from happening at runtime. This makes Rust a very robust language to work with. If I compare working with Rust with any of my previous projects, I do see less regressions in general.</description><content:encoded><![CDATA[<p>This post is intended for SQLx users, who want to take advantage of the powerful compile time checks SQLx crate provides. More specifically, we look into practical ways how to make any SQLx query checked.</p>
<p>The Rust compiler can catch many mistakes, preventing bugs from happening at runtime. This makes Rust a very robust language to work with. If I compare working with Rust with any of my previous projects, I do see less regressions in general.</p>
<p>As this high level of robustness comes from compile time checks, Rust feels comparatively a bit more brittle at the edges. There may be limitations to compiler checks, when dealing with external data sources like databases and JSON from third party APIs.</p>
<p>With this post, I hope to offer a useful reference, for how to avoid writing unchecked queries in the first place, and of course, how to refactor existing unchecked queries into checked ones. Different flavours of unchecked and checked queries alike are presented, so that the reader can see the practical differences, and keep refining their craft.</p>
<p>First we look into what are the differences between a checked and an unchecked queries, and the reasons to prefer checked queries. Then we cover two cases where we might feel tempted to write an unchecked query, but fortunately, won&rsquo;t have to. Most examples are written for Postgres, but can be applied to other relational databases as well. I’ve added one example for MySQL in that vein.</p>
<h2 id="what-are-checked-sqlx-queries">What are checked SQLx queries?</h2>
<p><a href="https://docs.rs/sqlx/latest/sqlx/index.html">SQLx</a> is a popular and full-fledged crate for interacting with relational databases. It supports PostgreSQL, MySQL and SQLite. While there are several other crates available, SQLx is a pretty safe pick, unless you want support for <a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping">ORMs</a>, which SQLx does not provide. In that case you may want to look into <a href="https://docs.rs/diesel/latest/diesel/">diesel</a>.</p>
<p>One of my favorite things about SQLx are the compile-time checks, because they really help to make the application more robust. We just need to use them!</p>
<h2 id="checked-query-vs-unchecked-query">Checked query vs. unchecked query</h2>
<p>A checked query is a query that can be statically validated at compile time. SQLx compares column and table names to the database schema, validates the syntax, and verifies data types without running any code.</p>
<p>An unchecked query has no guarantees at all. It could contain any number of typos and issues with types, and the compiler cannot catch them.</p>
<p>I think unchecked queries are a bad practice, to put in bluntly. With unchecked queries, we give up all guarantees on the correctness of the query. Also, as this blog post aims to show, they can be avoided altogether.</p>
<h2 id="why-care-about-unchecked-queries">Why care about unchecked queries?</h2>
<p>If you have a test DB, and run all the queries against it, fine, your test suite will catch the issue. But what if you don&rsquo;t? Then, checked queries will save your day. And even if you do, they&rsquo;ll save you some time anyway, because you&rsquo;ll get corrective feedback already at compile time, just as we Rust developers like it.</p>
<p>After all, an unchecked SQL query is just a string, from the compiler&rsquo;s perspective. The syntax could be invalid, or there could be a typo in the column name, and the compiler could not catch it.</p>
<p>The power of Rust lies in compiler checks (or at least a decent share), so let&rsquo;s keep our queries checked!</p>
<h2 id="how-to-recognize-unchecked-queries-in-the-wild">How to recognize unchecked queries in the wild?</h2>
<p>As a general rule, queries created using the <a href="https://docs.rs/sqlx/latest/sqlx/fn.query.html">sqlx::query()</a> and <a href="https://docs.rs/sqlx/latest/sqlx/fn.query_as.html">sqlx::query_as()</a> functions are unchecked, while those created with the macros <a href="https://docs.rs/sqlx/latest/sqlx/macro.query.html">sqlx::query!</a> and <a href="https://docs.rs/sqlx/latest/sqlx/macro.query_as.html">sqlx::query_as!</a> (and several others) are checked. If you see <a href="https://docs.rs/sqlx/latest/sqlx/struct.QueryBuilder.html">QueryBuilder</a> being used, that query is not checked. Check the documentation for other functions and macros.</p>
<p>Personally I like to use <a href="https://docs.rs/sqlx/latest/sqlx/fn.query_as.html">sqlx::query_as!</a> macro with an appropriate struct, except maybe for very simple queries, when I would use the <a href="https://docs.rs/sqlx/latest/sqlx/macro.query.html">sqlx::query!()</a> macro. In our project, we mainly use these two, and steer clear from functions that don&rsquo;t check the query.</p>
<h2 id="temptations">Temptations</h2>
<p>The idea of checked queries, is that if SQLx is not happy with the query, the compiler will complain. There are some specific cases however, where it may not be evident, how to construct that checked query.</p>
<p>After analyzing our codebase, I&rsquo;ve identified two cases where one may be tempted to write an unchecked query. First is the case of different variants of essentially the same query. The second case involves the usage of IN operator. In this post we review both.</p>
<h2 id="dealing-with-variants-of-the-same-query">Dealing with variants of the same query</h2>
<p>One may feel tempted to write unchecked queries, when there are subtle variants of the same query.</p>
<p>To give some practical examples, let&rsquo;s imagine a basic table called <code>users</code>, with the following fields.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="o">+</span><span class="c1">----------------+-------------+
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="k">Column</span><span class="w"> </span><span class="n">Name</span><span class="w">    </span><span class="o">|</span><span class="w"> </span><span class="k">Data</span><span class="w"> </span><span class="k">Type</span><span class="w">   </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------------+-------------+
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="o">|</span><span class="w"> </span><span class="n">id</span><span class="w">             </span><span class="o">|</span><span class="w"> </span><span class="n">UUID</span><span class="w">        </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="o">|</span><span class="w"> </span><span class="n">name</span><span class="w">           </span><span class="o">|</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="w">     </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="o">|</span><span class="w"> </span><span class="n">email</span><span class="w">          </span><span class="o">|</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="w">     </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="o">|</span><span class="w"> </span><span class="n">created_at</span><span class="w">     </span><span class="o">|</span><span class="w"> </span><span class="k">TIMESTAMP</span><span class="w">   </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="o">|</span><span class="w"> </span><span class="n">updated_at</span><span class="w">     </span><span class="o">|</span><span class="w"> </span><span class="k">TIMESTAMP</span><span class="w">   </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="o">|</span><span class="w"> </span><span class="n">is_guest</span><span class="w">       </span><span class="o">|</span><span class="w"> </span><span class="nb">BOOLEAN</span><span class="w">     </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="o">+</span><span class="c1">----------------+-------------+</span></span></span></code></pre></div><p>Why would we ever have several variants of essentially the same query?</p>
<p>Let&rsquo;s imagine the following use cases:</p>
<ul>
<li>Fetch all users, or only those who are guest users, or only those who have already registered (i.e. are no longer guest users)</li>
<li>Fetch all users, who were last updated before a specific date </li>
<li>Fetch all users, who were last updated between two dates</li>
<li>Fetch a user or several, with a specific list of ids</li>
</ul>
<p>Not sure if it is typical just for gaming, where data models do change and sometimes we need to patch live data, but we do have variants of quite a few queries in the codebase.</p>
<p>Perhaps we need to update or fetch data for existing users, based on when they were last updated. Perhaps we know that users updated after a specific date will already have the correct data. Hence the queries may have different parameters, to get exactly the correct results.</p>
<p>Additionally, these queries may be quite large, and indeed you may want to write the query just once, even if it has variations.</p>
<p>One may be tempted to write unchecked queries just for convenience. Let&rsquo;s take a brief look at what not to do.</p>
<h2 id="quick-and-dirty-sql-string">Quick and dirty SQL string</h2>
<p>The quick and dirty approach is to write the SQL query as a separate &amp;str. Maybe like this</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">query</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;SELECT id FROM users&#34;</span><span class="p">;</span><span class="w"> </span><span class="c1">// Imagine a more complex query here
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="c1">// There are three variants, depending on the value of is_guest_option
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">is_guest</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">is_guest_option</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="n">query</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;</span><span class="si">{query}</span><span class="s"> WHERE is_guest = </span><span class="si">{is_guest}</span><span class="s">&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w"></span><span class="kd">let</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqlx</span>::<span class="n">query</span><span class="p">(</span><span class="n">query</span><span class="p">).</span><span class="n">fetch_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="p">).</span><span class="k">await</span><span class="p">;</span><span class="w"> </span><span class="c1">// Unchecked query
</span></span></span></code></pre></div><p>The resulting query is unchecked, because this query is built dynamically. Hence there are no guarantees on its correctness.</p>
<h2 id="parameterized-query">Parameterized query</h2>
<p>A parameterized query seems more evolved, and curiously enough some <a href="https://en.wikipedia.org/wiki/Large_language_model">LLMs</a> recommend this pattern.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">query</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;SELECT id FROM users&#34;</span><span class="p">.</span><span class="n">to_string</span><span class="p">();</span><span class="w"> </span><span class="c1">// Imagine a more complex query here
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">is_guest</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">is_guest_option</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="n">query</span><span class="p">.</span><span class="n">push_str</span><span class="p">(</span><span class="s">&#34; WHERE is_guest = ?&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="kd">let</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqlx</span>::<span class="n">query</span><span class="p">(</span><span class="o">&amp;</span><span class="n">query</span><span class="p">)</span><span class="w"> </span><span class="c1">// Unchecked query
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="p">.</span><span class="n">bind</span><span class="p">(</span><span class="n">is_guest</span><span class="p">)</span><span class="w"> </span><span class="c1">// Bind the parameter dynamically
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="p">.</span><span class="n">fetch_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="k">await</span><span class="p">;</span></span></span></code></pre></div><p>Binding parameters feels kind of smart too. However, I would not recommend this approach, as the query is still unchecked.</p>
<h2 id="how-to-make-these-queries-checked">How to make these queries checked?</h2>
<p>This is the heart of the matter, how to make these queries checked. Conditional compilation is one alternative, and using an optional parameter another. Let&rsquo;s review these alternatives.</p>
<h2 id="conditional-compilation-works-but-is-repetitive">Conditional compilation works, but is repetitive</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// This is checked, but not DRY
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">let</span><span class="w"> </span><span class="n">query</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">is_guest</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">is_guest_option</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">sqlx</span>::<span class="fm">query!</span><span class="p">(</span><span class="s">&#34;SELECT id FROM users WHERE is_guest = ?&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">is_guest</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="n">sqlx</span>::<span class="fm">query!</span><span class="p">(</span><span class="s">&#34;SELECT id FROM users&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="kd">let</span><span class="w"> </span><span class="n">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">query</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">fetch_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="k">await</span><span class="p">;</span></span></span></code></pre></div><p>This query is checked, so it is definitely better than sprinkling the codebase with unchecked queries. However, in each of the branches we need to write the entire query, making the code repetitive and harder to maintain. Managing more complex queries, especially with many variants, will become quite unwieldy.</p>
<p>What would be a better alternative? Well, we can use what I like to call &ldquo;Option pattern&rdquo;. It will be easier to manage, especially when the queries grow in size.</p>
<h2 id="option-pattern-to-the-rescue">Option pattern to the rescue</h2>
<p>Let&rsquo;s take our good old <code>SELECT id FROM users</code> query. Let&rsquo;s also have more variation, so we can see firsthand how nicely we can compose queries with the Option pattern. Specifically, we will add optional date parameters to the mix, that allow us to search for users that were last updated before a date, or after it, or without date restrictions. We also add the option to limit the query to just guest users, or only to the registered users (not guests), if that parameter is passed.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// Postgres version
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">let</span><span class="w"> </span><span class="n">ids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqlx</span>::<span class="fm">query_as!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">Uuid</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="s">&#34;SELECT id FROM users </span><span class="se">\</span><span class="s">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s">     WHERE ($1::timestamptz IS NULL OR updated_at &lt; $1) </span><span class="se">\</span><span class="s">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s">       AND ($2::timestamptz IS NULL OR updated_at &gt; $2) </span><span class="se">\</span><span class="s">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s">       AND ($3::boolean IS NULL OR is_guest = $3)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="n">updated_before_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="n">updated_after_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="n">is_guest_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">fetch_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="k">await</span><span class="p">;</span></span></span></code></pre></div><p>Pretty neat! A checked query, that supports different variants. As <code>None</code> evaluates to <code>NULL</code>, we conveniently omit the condition for that parameter, when the <code>Option</code> is <code>None</code>. Postgres requires explicit type casting in this case, which is why we use the <code>::</code> type cast operator, as seen above.</p>
<p>The same idea should work with MySQL as well, adjusting the syntax slightly. MySQL uses ? as parameter placeholder markers, so the query would look like so. The casts are implicit, unlike in Postgres.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="o">//</span><span class="w"> </span><span class="n">MySQL</span><span class="w"> </span><span class="k">version</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="n">let</span><span class="w"> </span><span class="n">ids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqlx</span><span class="p">::</span><span class="n">query_as</span><span class="o">!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">Uuid</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="s2">&#34;SELECT id FROM users \
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">     WHERE (? IS NULL OR updated_at &lt; ?) \
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">       AND (? IS NULL OR updated_at &gt; ?) \
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">       AND (? IS NULL OR is_guest = ?)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="n">updated_before_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="n">updated_before_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="n">updated_after_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="n">updated_after_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="n">is_guest_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="n">is_guest_option</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">fetch_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">await</span><span class="p">;</span></span></span></code></pre></div><p>I&rsquo;ve come across one more situation where we may be tempted to write an unchecked query. </p>
<h2 id="when-in-does-not-compile-use-any-instead">When IN does not compile, use ANY instead</h2>
<p>SQLx has an important limitation when using the <a href="https://www.postgresql.org/docs/current/functions-subquery.html#FUNCTIONS-SUBQUERY-IN">IN operator</a> with list parameters. The <code>IN</code> operator works with a single item, subqueries, or hard-coded values. However, it cannot be included in a checked query, if it takes a list as a parameter. This can be confusing when encountered for the first time, so it is useful to mention in this post as well.</p>
<p>The solution is to use the <a href="https://www.postgresql.org/docs/current/functions-subquery.html#FUNCTIONS-SUBQUERY-ANY-SOME">ANY operator</a>, which works seamlessly with lists and individual items alike.</p>
<p>Here&rsquo;s an example of a checked query using <code>IN</code> that will not compile in:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Does not compile
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">let</span><span class="w"> </span><span class="n">records</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqlx</span>::<span class="fm">query!</span><span class="p">(</span><span class="s">&#34;SELECT name, email, created_at </span><span class="se">\</span><span class="s">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s">    FROM users WHERE id IN ($1)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="n">ids</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">fetch_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="k">await</span><span class="p">;</span><span class="err">`</span></span></span></code></pre></div><p>To address this issue, we can use <code>ANY</code> operator instead:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// This compiles
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">let</span><span class="w"> </span><span class="n">records</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqlx</span>::<span class="fm">query!</span><span class="p">(</span><span class="s">&#34;SELECT name, email, created_at </span><span class="se">\</span><span class="s">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s">    FROM users WHERE id = ANY($1)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="n">ids</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">fetch_all</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="k">await</span><span class="p">;</span></span></span></code></pre></div><p>And there, we have a checked query! Here are some <a href="https://stackoverflow.com/questions/34627026/in-vs-any-operator-in-postgresql/34627688#34627688">further reflections</a> on the rather subtle difference between IN and ANY.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>I believe that with these two tools in our toolkit, there should be no need to write an unchecked SQLx query, ever.</p>
<p>Let me know if you have a use case for unchecked queries that cannot be effectively covered with the ideas from this blog post. I&rsquo;m more than happy to change my mind when new information arises. Hope it has been a useful read. Until next time!</p>
]]></content:encoded></item><item><title>My logging recipe for server side Rust</title><link>http://www.matildasmeds.com/posts/rust-logging-recipe/</link><pubDate>Thu, 11 Apr 2024 21:15:09 +0200</pubDate><guid>http://www.matildasmeds.com/posts/rust-logging-recipe/</guid><description>In this post I show one way of instrumenting your backend code, using the tracing crate from the tokio ecosystem. We also discuss logging and what makes it useful. This post would be most useful to the reader who might already be logging, but is not yet using tracing::instrument attribute macro.
What is logging? Logs are messages emitted by your program that are used for debugging and alerting purposes. In a sense the program tells its story in the log lines emitted.</description><content:encoded><![CDATA[<p>In this post I show one way of instrumenting your backend code, using the <a href="https://docs.rs/tracing/latest/tracing/">tracing crate</a> from the <a href="https://tokio.rs/">tokio ecosystem</a>. We also discuss logging and what makes it useful. This post would be most useful to the reader who might already be logging, but is not yet using <a href="https://docs.rs/tracing/latest/tracing/attr.instrument.html">tracing::instrument</a> attribute macro.</p>
<h2 id="what-is-logging">What is logging?</h2>
<p>Logs are messages emitted by your program that are used for debugging and alerting purposes. In a sense the program tells its story in the log lines emitted. Whether there are errors, warnings, general information, debugging information, all this can be expressed as logs. Logging can be considered a key pillar in any Observability stack, alongside with metrics and traces.</p>
<p>Here&rsquo;s a succinct <a href="https://www.baeldung.com/cs/trace-vs-log">definition</a> by Amanda Viescinski:</p>
<blockquote>
<p>A log is a time-stamped record of discrete events that have occurred over time in a system. Therefore, a log provides an ordered history of the occurrence of events in a system.</p>
</blockquote>
<h3 id="easy-retrieval-is-necessary-for-logs-to-be-useful">Easy retrieval is necessary for logs to be useful</h3>
<p>To be useful in real life, you need to be able to find and group relevant log lines easily. In practice, this means using relevant identifiers and spans. Without easy retrieval logging is much less useful, as anyone who has been there can testify. We will discuss identifiers and spans in a moment.</p>
<p>Eventually, to be able to view and search logs in production, you need to send them to a log management system. That is beyond the scope of this article. We will discuss how to make your logs useful, and how to do that in async Rust, using tokio, tracing and <a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/">tracing-subscriber</a> crates.</p>
<h3 id="relevant-identifiers-to-filter-log-lines">Relevant identifiers to filter log lines</h3>
<p>Identifiers link the log line to a particular user, organization, resource.</p>
<p>Let&rsquo;s think about the typical scenarios when one needs to dig into the logs. More often than not, a particular user has encountered an issue while using the application. Ideally, we should be able to pull all the log lines for that user to start hunting for clues. That is why I think the most important identifier in logging is generally the user_id. </p>
<p>When we search logs with the user_id (or similar), we find the answer to the question, what are all the things that have happened to this user. With a bit of luck we will see an error that gives enough hints to resolve the issue.</p>
<h3 id="unit-of-work">Unit of work</h3>
<p>In backend development, a natural unit of work is a request made to our own server. There could be smaller units of work that one might want to track separately, for instance a particular method that validates order items on a purchase order, or larger ones, comprising several requests, such as completing different steps when signing up for a service.</p>
<p>To keep it simple though I recommend starting with the request as the basic unit of work, and then adding on top of that if needed. So, in addition to the user_id, we now have request_id as well, which helps us group the log lines per request.</p>
<p>For sub-request units of work we can use any identifier that makes sense in that context.</p>
<p>For a work unit compassing several requests the happiest scenario is when one can use an identifier that occurs naturally in the system, for instance a match_id, if it is a game, or a sales_order_id for order management software.</p>
<h3 id="use-spans-to-represent-units-of-work">Use spans to represent units of work</h3>
<p>According to tracing crate&rsquo;s <a href="https://docs.rs/tracing/latest/tracing/span/index.html">documentation</a>:</p>
<blockquote>
<p>Spans represent periods of time in which a program was executing in a particular context.</p>
</blockquote>
<p>Also,</p>
<blockquote>
<p>As a rule of thumb, spans should be used to represent discrete units of work (e.g., a given request’s lifetime in a server) or periods of time spent in a given context (e.g., time spent interacting with an instance of an external system, such as a database).</p>
</blockquote>
<p>Span is a named scope that represents a unit of work. It maps to a specific method in your code, and then it shows what was the context of a particular log line. Any descriptive name is good, as long as it identifies conceptually what the span is about, and is distinct from all other spans.</p>
<p>When a span has fields, these are added to each log line emitted in that span. These fields can carry identifiers, but they can also record outcomes and durations and other data that is determined during the execution of the span. Log lines can be searched and grouped easily, using the data included in the fields.</p>
<p>Spans can also be nested if needed. For instance, in the order management context, we could have &ldquo;SalesOrder&rdquo; as the parent span, and &ldquo;place_order&rdquo; and &ldquo;confirm_order&rdquo; as child-spans, or &ldquo;SalesOrder::place_order&rdquo; and &ldquo;SalesOrder::confirm_order&rdquo; as two separate, non-nested spans.</p>
<blockquote>
<p>In a nutshell: Span represents a unit of work.
Identifiers link the span (with its log lines) to resources: user, organization, match, purchase order, and so on.</p>
</blockquote>
<h2 id="lets-put-it-to-practice">Let&rsquo;s put it to practice</h2>
<p>You will need both the <a href="https://docs.rs/tracing/latest/tracing/">tracing crate</a>, and <a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/">tracing-subscriber crate</a>.
The first to emit, and the second to process tracing events.</p>
<h3 id="set-up-tracing-subscriber">Set up tracing-subscriber</h3>
<p>To actually see the emitted log lines, you need to set up the tracing-subscriber. Place this initialization code somewhere in the initialization phase of your program.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">tracing_subscriber</span>::<span class="n">fmt</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="p">.</span><span class="n">init</span><span class="p">();</span></span></span></code></pre></div><p>This is the quickest way to initialize the tracing-subscriber.</p>
<p>There are plenty of useful opt-in features though. For instance, you can format your lines as json, and include the moments when a span is created and exited, like so:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">tracing_subscriber</span>::<span class="n">fmt</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"> </span><span class="p">.</span><span class="n">json</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span><span class="p">.</span><span class="n">with_span_events</span><span class="p">(</span><span class="n">FmtSpan</span>::<span class="no">NEW</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">FmtSpan</span>::<span class="no">EXIT</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span><span class="p">.</span><span class="n">init</span><span class="p">();</span></span></span></code></pre></div><p>For this example to work, remember to add the required opt-in features in your Cargo.toml as well.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">tracing-subscriber</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.3&#34;</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;env-filter&#34;</span><span class="p">,</span> <span class="s2">&#34;json&#34;</span><span class="p">]</span> <span class="p">}</span></span></span></code></pre></div><h3 id="instrument-async-methods-with-tracing-crate">Instrument async methods with tracing crate</h3>
<p>Creating a span for an async method is easy, using the attribute macro <a href="https://docs.rs/tracing/latest/tracing/attr.instrument.html">instrument</a></p>
<p>Let&rsquo;s assume an implementation for a game of TicTacToe, and a method called process_turn. We can add player_id and match_id as fields on the Span, like so:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">struct</span> <span class="nc">TicTacToe</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">TicTacToe</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="cp">#[tracing::instrument(name = </span><span class="s">&#34;TicTacToe::process_turn&#34;</span><span class="cp">, skip_all, fields(match_id = %match_id, player_id = %player_id))]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="k">pub</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">process_turn</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">match_id</span>: <span class="kp">&amp;</span><span class="nc">MatchId</span><span class="p">,</span><span class="w"> </span><span class="n">player_id</span>: <span class="kp">&amp;</span><span class="nc">PlayerId</span><span class="p">,</span><span class="w"> </span><span class="n">turn</span>: <span class="kp">&amp;</span><span class="nc">PlayerTurn</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="n">UpdatedBoard</span><span class="p">,</span><span class="w"> </span><span class="n">Error</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="c1">// validate parameters, update match, etc
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// emit relevant log lines, for instance
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">tracing</span>::<span class="fm">info!</span><span class="p">(</span><span class="s">&#34;Turn processed successfully!&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="c1">// return updated board
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><p>With this, a span is opened, with fields for match_id and player_id. All log lines emitted between entering this method and exiting it, including in the calls made within the method, will now be part of the same span, with these fields recorded for each line. It is enough to fish for log lines related to a particular game or user, to gain qualitative insight to what has happened in that game.</p>
<p>With <strong>name</strong> we set the name for the span. By default this kind of span records all the parameters of the function. With <strong>skip_all</strong> you can skip them all, as usually this is not needed. You can also skip any parameters individually with <strong>skip</strong>. I like <strong>skip_all</strong> however, because that way I can amend the method signature without touching the attribute macro.</p>
<p><strong>fields</strong> declares what fields can be recorded on the span. Note that when setting their values, we can evaluate statements, so that exact values can be extracted and formatted exactly as we please. You can even create a new identifier if you want, for instance like this, using <a href="https://docs.rs/uuid/latest/uuid/">uuid crate</a>.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="cp">#[tracing::instrument(name = </span><span class="s">&#34;process_request&#34;</span><span class="cp">, fields(request_id = Uuid::new_v4().to_string())]</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">process_request</span><span class="p">(</span><span class="n">req</span>: <span class="nc">Request</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="n">Response</span><span class="p">,</span><span class="w"> </span><span class="n">Error</span><span class="o">&gt;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="c1">// process request
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><h3 id="how-to-record-values-after-creating-the-span">How to record values after creating the span?</h3>
<p>How about the times, when you want to include a value for a field that is not yet available when creating the span. An outcome or duration can be recorded only when the value is known. In these cases, we need to declare the field when creating the span and record the value later.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="cp">#[tracing::instrument(name = </span><span class="s">&#34;process_request&#34;</span><span class="cp">, fields(request_id = Uuid::new_v4().to_string(), duration)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">process_request</span><span class="p">(</span><span class="n">req</span>: <span class="nc">Request</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="n">Response</span><span class="p">,</span><span class="w"> </span><span class="n">Error</span><span class="o">&gt;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">start</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span>::<span class="n">time</span>::<span class="n">Instant</span>::<span class="n">now</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="c1">// process request
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">tracing</span>::<span class="n">Span</span>::<span class="n">current</span><span class="p">().</span><span class="n">record</span><span class="p">(</span><span class="s">&#34;duration&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">start</span><span class="p">.</span><span class="n">elapsed</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><p>This adds a field called duration on the span. Then in the method we access the current span, and record a value on the field at the end of the method. The duration will appear on the span exit, or any log line we emit on the span after recording the value. Note that all fields need to be declared when creating the span, otherwise the field won&rsquo;t show on the span.</p>
<h3 id="word-of-warning-how-not-to-create-spans-in-async-rust">Word of warning: How not to create spans in async Rust</h3>
<p>I made my first attempt at logging async code in Rust using span.enter(). Sadly, without having read the ample warnings for this <a href="https://docs.rs/tracing/latest/tracing/struct.Span.html#method.enter">method</a>, on tracing documentation.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">_entered</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">span</span><span class="p">.</span><span class="n">enter</span><span class="p">();</span></span></span></code></pre></div><p>There are constructs in Rust, that are not safe in async code, and the span guard <a href="https://docs.rs/tracing/latest/tracing/span/struct.Entered.html">Entered</a> is one of those. It may look innocent, but it is hard to handle correctly.</p>
<p>Of course I held the span guard across awaits, and then marvelled at why things looked good on my machine, but not on multi-user environments. Some log lines would not have spans at all, while others would have a cascade of spans not belonging to that line. If the spans don&rsquo;t end up on the lines where they belong, they are useless.</p>
<p>The bigger lesson of course is, to take a good look at the documentation of any new methods you want to use in async code.</p>
<p>Having burnt my fingers with the span guard, I was so happy to discover the recipe I present in this post. It is easy and works great for async Rust.</p>
<h3 id="final-words">Final words</h3>
<p>I find these two crates flexible and easy to use. There&rsquo;s more to discover in the docs, and plenty of opportunities for fine tuning your logs.</p>
<p>For learning more about observability in Rust, I want to give a whole-hearted recommendation for Luca Palmieri&rsquo;s <a href="https://github.com/mainmatter/rust-telemetry-workshop">rust-telemetry-workshop</a>. This open-sourced repository is a test driven learning resource, that has been really useful for me to get hit the ground running with observability, using Rust. Each small chapter explores one or two concepts at a time. You will need to make the tests pass to move on to the next exercise. A bit like good ole rustlings, but for observability!</p>
<p>All in all, I hope this post has been useful for you! Let me know what you think, if there is anything that was not clear or something that you would like to know more about. Until next time!</p>
]]></content:encoded></item></channel></rss>