---
# System prepended metadata

title: fnc12/sqlite_orm 使用心得
tags: [learning note, '2020-05-09', c++]

---

fnc12/sqlite_orm 使用心得
===
sqlite_orm[^sqlite_orm] 是一個 C++ 的 slite 函式庫，雖然在 github 上跟同類型的庫相比算是星星較多的，但是有不少潛在的問題，以下針對使用過程留下一些筆記，避免後人繼續坑...


### 環境建置
首先，它仰賴 Catch2，於是我從 github[^Catch2] 上 clone 下來編譯後安裝。

接著 `make` 遲遲過不了，後來發現是自動下載有點問題，不知道為什麼仰賴的 sqlite 函式庫是透過自動下載但是卻總是載不完整導致 sha1 驗證過不了。於是我移除 cmake 對 sqlite-amalgamation-3210000.zip 的 sha1 驗證，並從官方網站[^sqlite]手動下載。

總算完成 `make install`，趕緊開個 hello world 使用看看這個函式庫，但是 `cmake` 卻總是卡住，後來才發現是套件的別名與路徑有問題（作者提供的引用方式語法不對），於是我在 `cmake` 使用 `find_package(sqlite_orm REQUIRED NAMES sqlite_orm  SqliteOrm)`，並且用指令修復標頭檔的相對位置：
```bash
mkdir /usr/local/include/sqlite_orm
sudo mv sqlite_orm.h sqlite_orm/.
```

### 封裝問題
接著我試著要依照OOP的寫法封裝成 Repository ，卻碰到了問題。

從原始碼可以看出 ` make_storage` 的回傳型別與傳入的參數有關[^make_storage]，也就是其型別受到資料庫的欄位設定影響。

> I mean there is no common storage_t type cause storage_t is templated just like `std::vector`: `std::vector<int>` ≠ `std::vector<char>` 

因此要獲得型別必須使用以下方法[^type]：
```cpp=
struct Employee {
    int id;
    std::string name;
    int age;
    std::string address;
    double salary;
};

inline auto initStorage(const std::string &path) {
    using namespace sqlite_orm;
    return make_storage(path,
                        make_table("COMPANY",
                                   make_column("ID",
                                               &Employee::id,
                                               primary_key()),
                                   make_column("NAME",
                                               &Employee::name),
                                   make_column("AGE",
                                               &Employee::age),
                                   make_column("ADDRESS",
                                               &Employee::address),
                                   make_column("SALARY",
                                               &Employee::salary)));
}

using Storage = decltype(initStorage(""));

Storage storage = initStorage("storage.sqlite");
```

注意當中的 `using Storage = decltype(initStorage(""));`，

在獲得型別 `Storage` 必須先調用 `initStorage`，欲調用 `initStorage` 則必先定義該函數，這使得獲得型別時，程式碼已經處於定義階段，因此無法在宣告階段的標頭檔使用該型別[^undefined]，換句話說，若要撰寫宣告與定義分離的的程式碼（`.cpp` 與`.h`）在實現上有一定程度的困難：
```cpp
// SpeciesRepository.h
class SpeciesRepository{
  public:
    SpeciesRepository();
    
  private:
    Storage storage;
};
```

最後總算是把大部分的前置作業放到 `.hpp`，再從 class include 解決：
```cpp=
//SpeciesStorage.hpp
#pragma once
 
#include <string>
#include <vector>
#include <sqlite_orm/sqlite_orm.h>

struct Species{
    int id;
    int parentId;
    int generation;
    int batch;
    bool testing;
    bool tested;
    std::vector<char> chromosome;
    int scored;
};

inline auto createSpeciesStorage(const std::string &path) {
  using namespace sqlite_orm;
    return make_storage(path,
                            make_table("species",
                                       make_column("id", &Species::id, autoincrement(), primary_key()),
                                       make_column("parent_id", &Species::parentId),
                                       make_column("generation", &Species::generation),
                                       make_column("batch", &Species::batch),
                                       make_column("testing", &Species::testing),
                                       make_column("tested", &Species::tested),
                                       make_column("chromosome", &Species::chromosome),
                                       make_column("scored", &Species::scored)
                            ));
}

using SpeciesStorage = decltype(createSpeciesStorage(""));  
```

```cpp
#pragma once
#include <memory>
#include "SpeciesStorage.hpp"

class SpeciesRepository{
  public:
    SpeciesRepository(const std::string &path);

  private:
    std::unique_ptr<SpeciesStorage> storage;
};
```

### 又一個教學問題
好了，解決封裝問題了，該來寫一些 CRUD 的邏輯了，沒想到又碰到問題，依照 `READ.md` 提供的語法[^crud]嘗試：
```cpp
storage.update_all(set(c(&User::lastName) = "Hardey",
                       c(&User::typeId) = 2),
                   where(c(&User::firstName) == "Tom"));
```

卻得到的錯誤訊息：
```
error: invalid operands
      to binary expression ('internal::expression_t<bool Species::*>' and 'int')
```

接著我在範例發現不一樣的寫法[^example]：
```cpp
storage.update_all(set(assign(&Track::trackArtist, 3)),
                               where(is_equal(&Track::trackName, "Mr. Bojangles")));
```

### IDE 解析問題
我使用 Netbeans 11.1，IDE 解析得到一大堆紅底線，程式碼維護起來更為困難。主要原因是該函式庫大量使用 template ，因此 IDE 不能順暢的辨識型別。類似的問題在我使用 STL 的容器也會發生。


### 結論
花了不少時間在故障排除，雖然過程中也有學點東西，不過基本上已經到達我的停損點了，使用該函式庫的計畫暫時擱置，準備動身去嘗試其他替代方案了。對這個套件的作者沒有惡意，只是個人使用起來的體驗蠻糟糕的...

[^example]: https://github.com/fnc12/sqlite_orm/blob/e8a9e9416f421303f4b8970caab26dadf8bae98b/examples/foreign_key.cpp#L73

[^crud]: https://github.com/fnc12/sqlite_orm#crud

[^undefined]: https://stackoverflow.com/a/8752879/11833578

[^type]: https://github.com/fnc12/sqlite_orm/wiki/FAQ#quick--binary-fat

[^make_storage]: https://github.com/fnc12/sqlite_orm/blob/master/include/sqlite_orm/sqlite_orm.h#L11837

[^Catch2]: https://github.com/catchorg/Catch2
[^sqlite]: https://www.sqlite.org/2017/sqlite-amalgamation-3210000.zip

[^sqlite_orm]: https://github.com/fnc12/sqlite_orm

###### tags: `learning note` `c++` `2020-05-09`