<p>I recently started updating some projects from <a href="https://gorm.io/">GORM</a> v1 to v2.</p> <p>While doing so, I found a nasty difference in behaviour which made this much more complex than anticipated.</p> <p>One of the types of constructs which I use very often is:</p> <div class="highlight"><pre><span></span><span class="kd">func</span> <span class="nx">FindUser</span><span class="p">(</span><span class="nx">id</span> <span class="kt">int64</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="nx">User</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">user</span> <span class="nx">User</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">Raw</span><span class="p">(</span><span class="s">`select * from user where id=?`</span><span class="p">,</span> <span class="nx">id</span><span class="p">).</span><span class="nx">Scan</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">obj</span><span class="p">).</span><span class="nx">Error</span> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> <span class="k">if</span> <span class="nx">errors</span><span class="p">.</span><span class="nx">Is</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">gorm</span><span class="p">.</span><span class="nx">ErrRecordNotFound</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span> <span class="p">}</span> <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">err</span> <span class="p">}</span> <span class="k">return</span> <span class="o">&amp;</span><span class="nx">user</span><span class="p">,</span> <span class="kc">nil</span> <span class="p">}</span> </pre></div> <p>So, what the code is supposed to be doing:</p> <ul> <li>When <code>id</code> contains a valid value, a user object should be returned without an error</li> <li>When <code>id</code> contains a non-existing value, <code>nil</code> should be returned as the user without an error</li> <li>When something else goes wrong, <code>nil</code> should be returned as the user with an error</li> </ul> <p>Turns out that this no longer works in version 2. In the <a href="https://v1.gorm.io/docs/error_handling.html#RecordNotFound-Error">version 1 documentation</a>, it's defined as follows:</p> <blockquote><p>GORM provides a shortcut to handle <code>RecordNotFound</code> errors. If there are several errors, it will check if any of them is a <code>RecordNotFound</code> error.</p> </blockquote> <p>In <a href="https://gorm.io/docs/error_handling.html#ErrRecordNotFound">version 2 documentation</a>, the definition changed to:</p> <blockquote><p>GORM returns <code>ErrRecordNotFound</code> when failed to find data with <code>First</code>, <code>Last</code>, <code>Take</code>, if there are several errors happened, you can check the <code>ErrRecordNotFound</code> error with <code>errors.Is</code>.</p> </blockquote> <p>Luckily, I have test coverage to check this, but I'm unsure what would be the proper fix. One option would be to change the code to:</p> <div class="highlight"><pre><span></span><span class="kd">func</span> <span class="nx">FindUser</span><span class="p">(</span><span class="nx">id</span> <span class="kt">int64</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="nx">User</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">user</span> <span class="nx">User</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">Raw</span><span class="p">(</span><span class="s">`select * from user where id=?`</span><span class="p">,</span> <span class="nx">id</span><span class="p">).</span><span class="nx">Scan</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">obj</span><span class="p">).</span><span class="nx">Error</span> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">err</span> <span class="p">}</span> <span class="k">if</span> <span class="nx">user</span><span class="p">.</span><span class="nx">ID</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span> <span class="p">}</span> <span class="k">return</span> <span class="o">&amp;</span><span class="nx">user</span><span class="p">,</span> <span class="kc">nil</span> <span class="p">}</span> </pre></div> <p>There are several reasons though why I don't like this type of code. One of them is that queries don't always query for example the <code>ID</code> field which means you have to check different fields. This makes the code less clear for me and make it much more difficult what I'm trying to do.</p> <p>The best alternative I have found until now is:</p> <div class="highlight"><pre><span></span><span class="kd">func</span> <span class="nx">FindUser</span><span class="p">(</span><span class="nx">id</span> <span class="kt">int64</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="nx">User</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">user</span> <span class="nx">User</span> <span class="nx">res</span> <span class="o">:=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">Raw</span><span class="p">(</span><span class="s">`select * from user where id=?`</span><span class="p">,</span> <span class="nx">id</span><span class="p">).</span><span class="nx">Scan</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">obj</span><span class="p">)</span> <span class="k">if</span> <span class="nx">res</span><span class="p">.</span><span class="nx">Error</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">res</span><span class="p">.</span><span class="nx">Error</span> <span class="p">}</span> <span class="k">if</span> <span class="nx">res</span><span class="p">.</span><span class="nx">RowsAffected</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span> <span class="p">}</span> <span class="k">return</span> <span class="o">&amp;</span><span class="nx">user</span><span class="p">,</span> <span class="kc">nil</span> <span class="p">}</span> </pre></div> <p>Since this is spread over several places, I reported it on <a href="https://github.com/go-gorm/gorm/issues/3678">the issue tracker</a> hoping that they reconsider this change.</p>

Related Posts

  • Waiting for a MySQL database in Go
  • Combining channels and wait groups
  • Pretty-print JSON with Go
  • Using the Docker client from Go part 2
  • Handling Unix timestamps in JSON