{"id":642,"date":"2020-02-27T11:41:30","date_gmt":"2020-02-27T10:41:30","guid":{"rendered":"http:\/\/www.renaudpradenc.com\/?p=642"},"modified":"2020-02-27T11:58:10","modified_gmt":"2020-02-27T10:58:10","slug":"sqlite-debugging-with-gwendal-roue","status":"publish","type":"post","link":"https:\/\/www.renaudpradenc.com\/?p=642","title":{"rendered":"SQLite debugging with Gwendal Rou\u00e9"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">The crash<\/h2>\n\n\n\n<p>A surprising crash was reported in our app. Some users would write a number in a text field, for instance <code>\"7\"<\/code>. We expected the field to be saved as a string in the database, and read back as a string, but this is not what was happening. <\/p>\n\n\n\n<p>I wrote a unit test to reproduce the bug. The Product record is saved to the database and read back:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    func testStoresQuantityUnitAsString() {\n        modelDatabase.dbQueue.inDatabase { (db) in\n            let product = PFProduct()\n            product.uuid = \"TEST\"\n            product.quantityUnit = \"7\" \/\/ This is a string\n            try! product.insert(in: db)\n            \n            let reloaded = PFProduct.load(withPrimaryKey: product.uuid, from: db)\n            XCTAssertEqual(reloaded?.quantityUnit, \"7\")\n        }\n    }<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">XCTAssertEqual failed: throwing \"-[__NSCFNumber length]: unrecognized selector sent to instance<\/pre>\n\n\n\n<p>Our bug was reproduced: a string was written in the <code>quantityUnit<\/code> field as a string, but we got a <code>NSNumber<\/code> when it was read back from the database.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">I ask Gwendal for help<\/h2>\n\n\n\n<p>I am very lucky: since my employer <code><a href=\"https:\/\/www.incwo.com\">incwo<\/a><\/code> shares its premises with <a href=\"https:\/\/www.pierlis.com\">Pierlis<\/a>, I only had to walk 10 meters to talk to <a href=\"https:\/\/twitter.com\/groue\">Gwendal Rou\u00e9<\/a>, who is the author of <a href=\"https:\/\/github.com\/groue\/GRDB.swift\">GRDB<\/a> and knows a thing or two about databases and SQLite in particular.<\/p>\n\n\n\n<p>What&#8217;s reassuring is that, in the beginning, Gwendal was perplex as well. The database migration did create the column as a <code>STRING<\/code>, and we could verify that by opening the database in <a href=\"https:\/\/sqlitebrowser.org\">DBBrowser For SQLite<\/a>. And then, he had an idea. He opened a Terminal window and launched sqlite3:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>> sqlite3\nSQLite version 3.28.0 2019-04-15 14:49:49\nEnter \".help\" for usage hints.\nConnected to a transient in-memory database.\nUse \".open FILENAME\" to reopen on a persistent database.<\/code><\/pre>\n\n\n\n<p>With an in-memory database, it is now possible to execute SQL queries. Gwendal began by creating a table with a <code>STRING<\/code> column:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite> CREATE TABLE t1(a STRING);<\/code><\/pre>\n\n\n\n<p>Then he inserted three rows with different types (a string which contains digits, a alphabetic string, and a number):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite> INSERT INTO t1 (a) VALUES ('7');\nsqlite> INSERT INTO t1 (a) VALUES ('TOTO');\nsqlite> INSERT INTO t1 (a) VALUES (12);<\/code><\/pre>\n\n\n\n<p>The typeof() operator gives the actual type of storage of a field:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite> SELECT typeof(a) FROM t1;\ninteger\ntext\ninteger<\/code><\/pre>\n\n\n\n<p>What? <code>'7'<\/code> \u2014a string \u2014 was stored as an integer! <\/p>\n\n\n\n<p>This actually confirms Gwendal&#8217;s intuition. In <a href=\"https:\/\/www.sqlite.org\/datatype3.html\">this page of SQLite&#8217;s documentation<\/a>, you can see that <code>STRING<\/code> is not a storage class of SQLite. <code>TEXT<\/code> is. But as Gwendal tried to explain to me, SQLite tries to be compatible with SQL queries of other databases and will not complain if we use column types it does not know; instead it will do a guess, based on the actual type of the data. This is called \u201cType Affinity\u201d in the document.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The fix<\/h2>\n\n\n\n<p>So we knew the problem: we ought not use <code>STRING<\/code> as a type for a column in SQLite. And the solution: use <code>TEXT<\/code> instead. But how can we fix the code? In SQLite, the type of a column can not be changed after its creation. This is a very usual problem and I know the steps:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li> disable foreign keys checks during the migration<\/li><li>create a <code>products_temp<\/code> table with the exact same fields as the <code>products<\/code> table, but the type of the <code>quantityUnit<\/code> column declared as <code>TEXT<\/code> instead of <code>STRING<\/code>. <\/li><li>copy data from the <code>products<\/code> table to the <code>products_temp<\/code> table. <\/li><li>delete the <code>products<\/code> table<\/li><li>rename <code>products_temp<\/code> to <code>products<\/code><\/li><\/ul>\n\n\n\n<p>A question still remained. Will copy the data work? That is, we knew that some columns were stored as integer. Will copying them to a <code>TEXT<\/code> column convert them seamlessly to strings ?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite> CREATE TABLE t2 (a TEXT);\nsqlite> INSERT INTO t2 SELECT a FROM t1;\nsqlite> SELECT a, typeof(a) FROM t2;\n7|text\nTOTO|text\n12|text<\/code><\/pre>\n\n\n\n<p>Yes it will!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>In this post my interest was more showing how Gwendal typed SQL queries to debug than the actual bug. <\/p>\n\n\n\n<p>Gwendal told me that GRDB prevents this types of bugs because the usual way to create a table is using <a href=\"https:\/\/github.com\/groue\/GRDB.swift#database-schema\">higher level functions<\/a>, not using SQL. However our code base is still mainly written in Objective-C and we don&#8217;t use GRDB directly, but through its <a href=\"https:\/\/github.com\/groue\/GRDBObjc\">Objective-C binding<\/a>. I look forward to the day that our whole database layer is re-written in Swift.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The crash A surprising crash was reported in our app. Some users would write a number in a text field, for instance &#8220;7&#8221;. We expected the field to be saved as a string in the database, and read back as a string, but this is not what was happening. I wrote a unit test to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[89],"tags":[121],"class_list":["post-642","post","type-post","status-publish","format-standard","hentry","category-english","tag-sqlite"],"_links":{"self":[{"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=\/wp\/v2\/posts\/642","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=642"}],"version-history":[{"count":5,"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=\/wp\/v2\/posts\/642\/revisions"}],"predecessor-version":[{"id":648,"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=\/wp\/v2\/posts\/642\/revisions\/648"}],"wp:attachment":[{"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=642"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=642"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.renaudpradenc.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=642"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}