Skip to content

PR for ic-sqlite-vsf #121

Description

@dumblepy

ic-sqlite-vfs: NICP / Nim 連携のための変更設計書

  • 対象リポジトリ: humandebri/ic-sqlite-vfs
  • 想定利用側: dumblepy/nicp_cdk + nim-ic-sqlite
  • 作成日: 2026-08-24
  • 対象バージョン: ic-sqlite-vfs 2.0.0 / main ブランチ確認時点

1. 目的

本設計書は、Nim で実装された Internet Computer Canister から ic-sqlite-vfs を Rust static library 経由で利用するために、ic-sqlite-vfs 本体にどの変更が必要かを整理する。

前提アーキテクチャは以下とする。

Nim Canister
    │
    │ Nim API
    ▼
nim-ic-sqlite
    │
    │ C ABI
    ▼
nicp-sqlite-ffi (Rust staticlib)
    │
    ▼
ic-sqlite-vfs
    │
    ├── SQLite C
    ├── icstable sqlite3_vfs
    └── Stable Memory

重要な設計原則は以下である。

  1. ic-sqlite-vfs に Nim 固有コードを入れない。
  2. C ABI は nim-ic-sqlite 側の Rust wrapper が担当する。
  3. ic-sqlite-vfs は Rust ライブラリとして汎用性を維持する。
  4. NICP 固有の build/lifecycle 管理は nicp_cdk が担当する。
  5. ic-sqlite-vfs 側の変更は、可能な限り upstream に受け入れやすい汎用改善に限定する。

2. 結論

2.1 必須度の整理

変更 必須度 理由
wasm32-wasip1sqlite-precompiled 正式対応 P0 / 強く推奨 NICP の WASI ビルドターゲットと揃えるため
precompiled SQLite archive の target 名整理 P0 現在は WASI 用 object を wasm32-unknown-unknown ディレクトリに格納しており意味が不整合
wasm32-wasip1 build/CI test P0 upstream 更新時の回帰防止
Stable Memory backend の外部注入 API P1 / 条件付き NICP 側が MemoryManager を所有する構成にする場合に必要
Memory trait の public abstraction 化 P1 / 条件付き 同上
C ABI (extern "C") の追加 不要 nim-ic-sqlite の wrapper crate が担当すべき
crate-type = ["staticlib"] の追加 不要 wrapper crate を staticlib にすれば依存 Rust code はそこへリンクできる
NICP lifecycle (init, post_upgrade) の追加 不要 Canister framework 側の責務
Nim/NICP feature の追加 不要 upstream を特定言語・CDKに依存させないため

最重要結論

最初の NICP PoC を成立させるだけなら、ic-sqlite-vfs 本体を変更しなくてもよい。

sqlite-bundled feature を使用して nicp-sqlite-ffiwasm32-wasip1 向けにビルドし、ic-sqlite-vfs 自身の MemoryManager<DefaultMemoryImpl> を利用すればよい。

ただし production の標準構成としては、少なくとも以下を upstream に実装することを推奨する。

P0: sqlite-precompiled + wasm32-wasip1 正式対応

さらに、将来的に NICP の Stable Storage と SQLite が 同一 MemoryManager を共有し、その MemoryManager を NICP 側で所有する設計へ移行する場合は、以下が必要になる。

P1: 外部 Stable Memory backend を Db に注入できる API

2.2 MemoryId / MemoryManager に関する確定方針

今回の設計では、MemoryId(120)ic-sqlite-vfs 本体では予約しない

ic-sqlite-vfs の責務は、consumer から渡された1つの logical memory を SQLite の保存領域として利用することであり、MemoryId namespace の割当ポリシーは consumer/framework 側が所有する。

したがって、NICP では例えば以下を採用できる。

MemoryId(0)   -> NICP StableValue
MemoryId(1)   -> NICP StableTable
...
MemoryId(120) -> SQLite reserved by nicp_cdk

一方、Rust consumer は従来通り以下のように明示的に選択できる。

const SQLITE_MEMORY_ID: MemoryId = MemoryId::new(120);

ここで最も重要な制約は、同一raw stable memoryに対して複数のlive MemoryManagerを独立に動作させないことである。

推奨構成:

raw IC stable memory
        │
        ▼
one MemoryManager
        │
        ├── MemoryId(0)   -> application/framework storage
        ├── MemoryId(1)   -> application/framework storage
        └── MemoryId(120) -> SQLite

避ける構成:

NICP MemoryManager ────┐
                       ├── same raw stable memory
SQLite MemoryManager ──┘

ic-sqlite-vfs の MemoryManager fork は ic-stable-structures 0.7 互換レイアウトを持つが、レイアウト互換性は「2つのmanager instanceを同時に利用してよい」ことを意味しない。allocation metadataやbucket情報を各instanceがheap上に保持するため、同時利用はstale stateを生む可能性がある。

このため長期設計では、ic-sqlite-vfsconsumer-owned VirtualMemory / StableMemory backendを受け取れることをP1要件とする。


3. 現状コードの制約

3.1 crate type

現在の Cargo.toml は以下である。

[lib]
crate-type = ["cdylib", "rlib"]

staticlib は定義されていない。

しかし、これは問題ではない。

nim-ic-sqlite 側に以下の wrapper crate を作成すればよい。

[lib]
crate-type = ["staticlib"]

[dependencies]
ic-sqlite-vfs = { version = "2.0.0", ... }

最終的な static archive は wrapper crate 側で生成する。

したがって ic-sqlite-vfs 自身に staticlib crate-type を追加する必要はない。


3.2 sqlite-precompiled が wasm32-unknown-unknown に固定されている

現在の build.rs は以下の制限を持つ。

fn link_precompiled(manifest_dir: &Path, target: &str) {
    if target != "wasm32-unknown-unknown" {
        panic!("sqlite-precompiled currently supports only wasm32-unknown-unknown");
    }

    let lib_dir = manifest_dir.join("vendor/sqlite/wasm32-unknown-unknown/lib");
    ...
}

そのため以下は失敗する。

cargo build \
  --target wasm32-wasip1 \
  --no-default-features \
  --features sqlite-precompiled

NICP は Nim/C/Rust を WASI ベースでリンクするため、Rust wrapper も wasm32-wasip1 へ統一するのが望ましい。


3.3 precompiled archive の実体とディレクトリ名が一致していない

現在の build script は SQLite を実際には以下でコンパイルしている。

--target=wasm32-wasip1

しかし出力先は以下である。

vendor/sqlite/wasm32-unknown-unknown/lib/libsqlite3.a

つまり現在は、概念的に以下の状態になっている。

wasm32-wasip1 object code
        ↓
vendor/sqlite/wasm32-unknown-unknown/

これは NICP 対応以前に、target semantics と artifact path が一致していない。


4. P0: wasm32-wasip1 precompiled support

4.1 目的

以下を正式なサポート対象にする。

cargo build \
  --target wasm32-wasip1 \
  --no-default-features \
  --features sqlite-precompiled

NICP 側の推奨依存は最終的に以下とする。

ic-sqlite-vfs = {
  version = "2.x",
  default-features = false,
  features = ["sqlite-precompiled"]
}

4.2 vendor directory の変更

推奨構成:

vendor/sqlite/
├── src/
│   ├── sqlite3.c
│   └── sqlite3.h
├── bindings/
├── build-flags.txt
└── wasm32-wasip1/
    └── lib/
        └── libsqlite3.a

既存互換性を維持する必要がある場合は一時的に以下も残せる。

vendor/sqlite/
├── wasm32-unknown-unknown/
│   └── lib/libsqlite3.a
└── wasm32-wasip1/
    └── lib/libsqlite3.a

ただし、実体が同じ WASI object であるなら、長期的には wasm32-wasip1 を canonical path とする。


4.3 build.rs の変更

現状

if target != "wasm32-unknown-unknown" {
    panic!("sqlite-precompiled currently supports only wasm32-unknown-unknown");
}

推奨

fn precompiled_sqlite_dir(manifest_dir: &Path, target: &str) -> PathBuf {
    match target {
        "wasm32-wasip1" => {
            manifest_dir.join("vendor/sqlite/wasm32-wasip1/lib")
        }

        // compatibility mode; only retain if existing consumers need it
        "wasm32-unknown-unknown" => {
            manifest_dir.join("vendor/sqlite/wasm32-unknown-unknown/lib")
        }

        other => {
            panic!(
                "sqlite-precompiled is not available for target {other}; \
                 use sqlite-bundled or a supported precompiled target"
            );
        }
    }
}

fn link_precompiled(manifest_dir: &Path, target: &str) {
    let lib_dir = precompiled_sqlite_dir(manifest_dir, target);

    println!(
        "cargo:rerun-if-changed={}",
        lib_dir.join("libsqlite3.a").display()
    );

    println!("cargo:rustc-link-search=native={}", lib_dir.display());
    println!("cargo:rustc-link-lib=static=sqlite3");
}

4.4 build-sqlite-precompiled.sh の変更

現状

OUT_DIR="vendor/sqlite/wasm32-unknown-unknown"

"$CC_BIN" \
  --target=wasm32-wasip1 \
  ...

推奨

TARGET="${SQLITE_WASM_TARGET:-wasm32-wasip1}"
OUT_DIR="vendor/sqlite/$TARGET"

"$CC_BIN" \
  --target="$TARGET" \
  ...

ただし SQLite の precompiled artifact として公式にサポートする target を一つに絞る場合は、より単純に以下でよい。

OUT_DIR="vendor/sqlite/wasm32-wasip1"

"$CC_BIN" \
  --target=wasm32-wasip1 \
  ...

この方が artifact の意味が明確である。


4.5 Cargo package include の更新

現在は以下のみが package に含まれている。

"/vendor/sqlite/wasm32-unknown-unknown/lib/libsqlite3.a",

以下へ変更する。

"/vendor/sqlite/wasm32-wasip1/lib/libsqlite3.a",

互換 target を残す場合は両方を含める。


4.6 .cargo/config.toml

現在は wasm32_unknown_unknown 向けだけに C compiler wrapper が設定されている。

NICP 連携では wasm32-wasip1 を直接 target とするため、sqlite-bundled も正式にサポートするなら以下も検討する。

[env]
CC_SHELL_ESCAPED_FLAGS = "1"
CFLAGS_wasm32_wasip1 = "--target=wasm32-wasip1"

ただし compiler path を crate 内で固定するより、利用側から次の環境変数で指定可能にする方が portability が高い。

CC_wasm32_wasip1=/path/to/clang
AR_wasm32_wasip1=/path/to/llvm-ar

設計としては、

  • sqlite-precompiled: compiler 不要
  • sqlite-bundled: WASI SDK compiler が必要

と明確に分離する。


5. P0: CI / compatibility test

最低限、以下を CI matrix に追加する。

Rust target             feature
--------------------------------------------
wasm32-unknown-unknown  sqlite-precompiled   existing compatibility
wasm32-wasip1           sqlite-precompiled   NICP / WASI path
wasm32-wasip1           sqlite-bundled       source-build fallback

例:

cargo build \
  --target wasm32-wasip1 \
  --no-default-features \
  --features sqlite-precompiled

および:

cargo build \
  --target wasm32-wasip1 \
  --no-default-features \
  --features sqlite-bundled

Acceptance Criteria

  • wasm32-wasip1 build が成功する。
  • libsqlite3.a が正しい target で生成される。
  • SQLite symbol が final Rust artifact から解決される。
  • ic0.stable64_* imports が維持される。
  • sqlite-precompiled 使用時に host C compiler を要求しない。

6. Stable Memory 所有権の問題

ここが NICP 連携で最も重要な設計ポイントである。

現在の ic-sqlite-vfs は以下の構造になっている。

DefaultMemoryImpl
       │
       ▼
Ic0StableMemory
       │
       ▼
MemoryManager<DefaultMemoryImpl>
       │
       ▼
VirtualMemory<DefaultMemoryImpl>
       │
       ▼
DbMemory
       │
       ▼
Db::init(memory)

現在の型定義:

pub type DbMemory = VirtualMemory<DefaultMemoryImpl>;

したがって Db::init() は実質的に、ic-sqlite-vfs が定義する DefaultMemoryImpl に結びついた VirtualMemory しか受け取れない。


7. NICP 側 MemoryManager を使いたい場合の問題

将来的な理想構成を以下とする。

                 raw IC stable memory
                         │
                         ▼
              one shared MemoryManager
                         │
        ┌────────────────┼────────────────┐
        ▼                ▼                ▼
   MemoryId(1)      MemoryId(2)      MemoryId(120)
        │                │                │
 IcStableValue      IcStableTable        SQLite

この場合、MemoryManager は Canister 全体で一つだけ存在するべきである。

独立した二つの MemoryManager instance を同じ raw stable memory に対して使う構成は避ける。

NG:

NICP MemoryManager ──────┐
                         ├── same raw stable memory
Rust MemoryManager ──────┘

両方が allocation metadata を heap に cache するため、片側が bucket allocation を行った後、もう片側の in-memory state が stale になる可能性がある。


8. P1: 外部 Stable Memory backend API

8.1 この変更が必要になる条件

次の構成を採用する場合のみ必要である。

nicp_cdk owns MemoryManager
        │
        ├── Nim stable structures
        │
        └── selected VirtualMemory
                 │
                 ▼
           ic-sqlite-vfs

一方、最初の実装を以下にするなら不要である。

ic-sqlite-vfs / Rust wrapper owns MemoryManager
        │
        ├── SQLite MemoryId
        └── C ABI で Nim へ VM read/write を提供

したがって P1 は NICP PoC の blocker ではない


8.2 現在の内部 Memory trait

ic-sqlite-vfs には既に以下の概念が存在する。

pub trait Memory {
    fn identity(&self) -> MemoryBackendIdentity;
    fn size(&self) -> u64;
    fn grow(&self, pages: u64) -> i64;
    fn read(&self, offset: u64, dst: &mut [u8]);
    fn write(&self, offset: u64, src: &[u8]);
}

これは外部 backend injection の基盤として十分近い。

しかし現在は stable module 自体が crate private であり、public API として外部 crate から実装することを前提としていない。

さらに DbMemory が以下に固定されている。

VirtualMemory<DefaultMemoryImpl>

そのため単純に Memory trait を re-export するだけでは不十分である。


9. P1 API 設計案

既存 2.x API を壊さず、additive に拡張することを推奨する。

9.1 避ける設計

以下のように既存 alias をいきなり generic 化する変更は避ける。

pub type DbMemory<M> = VirtualMemory<M>;

理由:

  • Db::init(DbMemory) の既存 source compatibility を壊しやすい。
  • thread-local memory registry が現在 concrete type を前提としている。
  • DbHandle を generic 化すると public API 全体へ型 parameter が波及する。

9.2 推奨: type-erased backend を追加する

新しい public abstraction を追加する。

概念例:

pub trait StableMemoryBackend: 'static {
    fn identity(&self) -> StableMemoryIdentity;
    fn size(&self) -> u64;
    fn grow(&self, pages: u64) -> i64;
    fn read(&self, offset: u64, dst: &mut [u8]);
    fn write(&self, offset: u64, src: &[u8]);
}

identity は clone された memory handle でも同一領域として判定できなければならない。

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct StableMemoryIdentity {
    backend: u64,
    memory: u64,
}

具体的な値の意味は API contract として固定しすぎず、同一 process / canister instance 内での重複登録判定に利用する。

その上で type erased handle を用意する。

#[derive(Clone)]
pub struct ExternalDbMemory {
    inner: Rc<dyn StableMemoryBackend>,
}

新 API:

impl DbHandle {
    pub fn init_external(
        memory: ExternalDbMemory,
    ) -> Result<Self, DbError>;
}

既存 API は維持する。

impl Db {
    pub fn init(memory: DbMemory) -> Result<(), DbError>;
}

impl DbHandle {
    pub fn init(memory: DbMemory) -> Result<Self, DbError>;
}

10. 内部 memory registry の変更案

現在は概念的に以下である。

thread_local! {
    static DB_MEMORY: RefCell<Vec<(ContextId, DbMemory)>> = ...;
}

これを private abstraction へ変更する。

trait RegisteredMemory {
    fn identity(&self) -> StableMemoryIdentity;
    fn size(&self) -> u64;
    fn grow(&self, pages: u64) -> i64;
    fn read(&self, offset: u64, dst: &mut [u8]);
    fn write(&self, offset: u64, src: &[u8]);
}

registry は以下のように type erase する。

thread_local! {
    static DB_MEMORY: RefCell<
        Vec<(ContextId, Rc<dyn RegisteredMemory>)>
    > = const { RefCell::new(Vec::new()) };
}

これにより以下の両方を同じ VFS backend として利用できる。

VirtualMemory<DefaultMemoryImpl>
ExternalDbMemory

SQLite/VFS 上位層からは byte-addressed memory であることだけを要求する。


11. NICP callback backend の例

nicp-sqlite-ffi 側で以下の Rust type を実装できるようにする。

struct NicpMemory {
    memory_id: u8,
}

impl StableMemoryBackend for NicpMemory {
    fn identity(&self) -> StableMemoryIdentity {
        StableMemoryIdentity::new(
            NICP_BACKEND_ID,
            self.memory_id as u64,
        )
    }

    fn size(&self) -> u64 {
        unsafe { nicp_vm_size(self.memory_id) }
    }

    fn grow(&self, pages: u64) -> i64 {
        unsafe { nicp_vm_grow(self.memory_id, pages) }
    }

    fn read(&self, offset: u64, dst: &mut [u8]) {
        unsafe {
            nicp_vm_read(
                self.memory_id,
                offset,
                dst.as_mut_ptr(),
                dst.len(),
            )
        }
    }

    fn write(&self, offset: u64, src: &[u8]) {
        unsafe {
            nicp_vm_write(
                self.memory_id,
                offset,
                src.as_ptr(),
                src.len(),
            )
        }
    }
}

この設計なら ic-sqlite-vfs 自体は NICP を知らない。

ic-sqlite-vfs
     │
     │ StableMemoryBackend
     ▼
nicp-sqlite-ffi
     │
     │ C ABI callback
     ▼
nicp_cdk VirtualMemory

12. ただし P1 は後回しにするべき理由

P1 は memory layer の内部設計変更を伴うため、SQLite の NICP PoC と同時に実装すべきではない。

推奨する初期構成は以下である。

Rust side

MemoryManager<DefaultMemoryImpl>
       │
       ├── MemoryId(120) -> SQLite
       ├── MemoryId(1)   -> NICP stable area
       └── MemoryId(2)   -> NICP stable area

Rust wrapper から Nim へ次を公開する。

uint64_t nicp_vm_size(uint8_t memory_id);
int64_t  nicp_vm_grow(uint8_t memory_id, uint64_t pages);
int32_t  nicp_vm_read(
    uint8_t memory_id,
    uint64_t offset,
    uint8_t *dst,
    uint32_t len
);
int32_t  nicp_vm_write(
    uint8_t memory_id,
    uint64_t offset,
    const uint8_t *src,
    uint32_t len
);

この方式なら ic-sqlite-vfs の MemoryManager が唯一の manager になる。

NICP の stable structures はこの VM API 上へ移行できる。

利点

  • upstream 変更が最小。
  • MemoryManager layout を二重実装しなくてよい。
  • allocation metadata の ownership が一箇所になる。
  • SQLite PoC と stable-storage redesign を分離できる。

欠点

  • NICP の Stable Memory core が Rust 実装へ一部依存する。
  • SQLite を使わない project でも同じ MemoryManager を利用したい場合、別 package 化が必要になる。

したがって長期的に NICP 自身が manager を所有したい場合にのみ P1 を進める。


13. C ABI は ic-sqlite-vfs に追加しない

以下のような API を ic-sqlite-vfs 本体へ直接追加することは推奨しない。

#[no_mangle]
pub extern "C" fn ic_sqlite_execute(...) -> i32 {
    ...
}

理由:

  1. Rust crate としての責務を越える。
  2. ABI versioning が crate の public Rust API と結合する。
  3. Nim 以外の language binding 要件が入り始める。
  4. row/value/error memory ownership が core crate を複雑化する。
  5. C ABI の最適形は利用言語によって異なる。

C ABI は以下に置く。

nim-ic-sqlite/
└── rust/
    └── nicp-sqlite-ffi/

14. staticlib も ic-sqlite-vfs に追加しない

以下の変更も原則不要である。

[lib]
crate-type = ["cdylib", "rlib", "staticlib"]

理由:

ic-sqlite-vfs は Rust library dependency として維持し、最終 linking boundary を wrapper crate にする方が責務が明確だからである。

ic-sqlite-vfs = Rust API crate
nicp-sqlite-ffi = foreign-language ABI crate

最終 static archive:

libnicp_sqlite_ffi.a

のみを Nim/clang linker へ渡す。


15. ic-cdk dependency

ic-sqlite-vfs の core DB/VFS 自体は direct ic0.stable64_* import を利用している。

Canister Candid API は canister-api feature によって分離されている。

NICP wrapper では以下を使用しない。

features = ["canister-api"]

つまり ic-sqlite-vfs の reference canister API を final Wasm に export する必要はない。

将来的な改善として、ic-cdk dependency 自体を optional にして canister-api にだけ紐付けられるかは検討可能だが、NICP integration の必須変更ではない

候補:

[features]
canister-api = ["dep:ic-cdk", "dep:candid", "dep:serde"]

[dependencies]
ic-cdk = { version = "...", optional = true }

ただし core API で candid/serde type を使用している箇所がないことを確認した上で行う。

これは binary size と dependency surface を減らす改善であり、P2 とする。


16. Lifecycle API は変更しない

ic-sqlite-vfs README の典型利用では以下を要求する。

canister_init
    -> MemoryManager init
    -> Db::init
    -> Db::migrate

canister_post_upgrade
    -> MemoryManager init
    -> Db::init
    -> Db::migrate

しかし lifecycle export は利用 framework の責務である。

Rust Canister:

ic-cdk macros

Nim Canister:

nicp_cdk lifecycle pragmas

したがって ic-sqlite-vfs へ lifecycle hook を追加しない。


17. Async / inter-canister API も変更しない

現在の同期 closure API は維持すべきである。

Db::update(|connection| {
    ...
})

SQLite transaction 中の await を禁止する設計は Internet Computer の execution model と整合する。

Nim binding 側でも同じ制約を表現する。

db.update:
  db.execute(...)

この block を async procedure にしない。

ic-sqlite-vfs 側へ async API を追加する必要はない。


18. 推奨する変更セット

Phase A: upstream 最小変更

対象: ic-sqlite-vfs

1. wasm32-wasip1 sqlite-precompiled support
2. vendor path を wasm32-wasip1 に整合
3. build script 修正
4. Cargo package include 修正
5. CI build matrix 追加
6. docs 更新

この時点では memory API を変更しない。

Release impact

原則 additive change のため minor release で対応可能。

例:

2.1.0

19. Phase B: NICP integration PoC

対象: nim-ic-sqlite

ic-sqlite-vfs を dependency とする。

[lib]
crate-type = ["staticlib"]

[dependencies.ic-sqlite-vfs]
version = "2.1"
default-features = false
features = ["sqlite-precompiled"]

C ABI:

init
execute
query
migration
error

を wrapper crate に実装する。

この段階では ic-sqlite-vfs MemoryManager が stable memory allocation を所有する。


20. Phase C: Stable Memory architecture の評価

PoC 後に以下を判断する。

Option 1

Rust MemoryManager を NICP ecosystem 共通 allocator とする

この場合 ic-sqlite-vfs の追加変更は不要。

Option 2

NICP が MemoryManager を所有する

この場合 P1 の external backend API を ic-sqlite-vfs upstream に提案する。


21. P1 の upstream PR を分離する

P0 と P1 は同じ PR にしない。

推奨 PR 構成:

PR 1

Support precompiled SQLite on wasm32-wasip1

内容:

  • build.rs
  • vendor path
  • precompile script
  • Cargo include
  • documentation
  • CI

PR 2

Allow database memory backends to be supplied externally

内容:

  • memory abstraction
  • type erasure
  • external backend API
  • identity semantics
  • compatibility tests

理由:

P0 は小さく明確で upstream に受け入れられやすい。
P1 は API design と compatibility discussion が必要になる。


22. P0 Acceptance Criteria

以下をすべて満たした時点で完了とする。

  • wasm32-wasip1 + sqlite-precompiled が build できる。
  • wasm32-wasip1 + sqlite-bundled が build できる。
  • precompiled archive の directory 名と actual target が一致する。
  • crates.io package に wasm32-wasip1/libsqlite3.a が含まれる。
  • existing Rust canister build を壊さない。
  • existing stable-memory layout を変更しない。
  • DB image compatibility を変更しない。
  • VFS semantics を変更しない。
  • CI に target regression test がある。

23. P1 Acceptance Criteria

P1 を実施する場合は以下を満たす。

  • existing Db::init(DbMemory) API がそのまま動く。
  • existing DbHandle::init(DbMemory) API がそのまま動く。
  • external byte-addressed memory backend から DB を初期化できる。
  • cloned handle に対して identity が安定する。
  • same memory の二重 registration を拒否できる。
  • multiple database handles が動く。
  • update rollback semantics が変わらない。
  • query read-only semantics が変わらない。
  • stable image layout が変わらない。
  • MemoryManager 0.7 compatibility が変わらない。
  • native unit test backend も引き続き動く。

24. テスト設計

24.1 Build test

cargo check host
cargo test host
cargo build wasm32-wasip1 sqlite-precompiled
cargo build wasm32-wasip1 sqlite-bundled

24.2 staticlib consumer fixture

NICP そのものを ic-sqlite-vfs CI dependency にする必要はない。

代わりに小さな fixture crate を用意する。

tests/fixtures/staticlib-consumer/
├── Cargo.toml
└── src/lib.rs
[lib]
crate-type = ["staticlib"]
use ic_sqlite_vfs::{Db, DefaultMemoryImpl, MemoryId, MemoryManager};

#[unsafe(no_mangle)]
pub extern "C" fn smoke_init() -> i32 {
    let manager = MemoryManager::init(DefaultMemoryImpl::default());
    let memory = manager.get(MemoryId::new(120));

    match Db::init(memory) {
        Ok(_) => 0,
        Err(_) => -1,
    }
}

これを wasm32-wasip1 staticlib として build する。

これにより foreign-language consumer と同じ linkage path を upstream CI で検証できる。


25. 互換性

Stable layout

P0 では一切変更しない。

raw stable memory
  -> MemoryManager 0.7-compatible layout
      -> VirtualMemory
          -> ic-sqlite-vfs superblock
          -> SQLite image

precompiled target support は binary build artifact の問題であり stable data format の問題ではない。

したがって upgrade compatibility に影響を与えないことを必須条件とする。


26. リスク

Risk 1: WASI libc symbol duplication

Nim/C/Rust を一つの Wasm へ static-link するため、Rust と clang が別々の libc/object を持ち込むと duplicate symbol が発生する可能性がある。

これは主として NICP / wrapper build layer の問題であり、ic-sqlite-vfs core API の変更で解決すべきではない。

必要に応じて Rust wrapper の build flags で external WASI libc を共有する。


Risk 2: precompiled archive の ABI mismatch

libsqlite3.awasm32-wasip1 用として配布するなら、実際の compile target、WASI SDK、SQLite compile flags を reproducible にする必要がある。

最低限以下を記録する。

SQLite version
WASI SDK version
clang version
compile target
build-flags.txt hash

Risk 3: external memory API が過剰設計になる

NICP の initial integration だけなら external backend API は不要である。

先に導入すると、upstream public API と stable memory identity semantics を不必要に複雑化する可能性がある。

したがって P1 は PoC 後に実際の ownership requirement が確定してから実施する。


27. 推奨最終方針

ic-sqlite-vfs に今すぐ入れる変更

ic-sqlite-vfs
└── wasm32-wasip1 support
    ├── sqlite-precompiled
    ├── correct vendor path
    ├── build script
    ├── package include
    ├── docs
    └── CI

nim-ic-sqlite に入れるもの

nim-ic-sqlite
├── C ABI
├── Rust staticlib wrapper
├── Db/Statement/Row/Value Nim API
├── migrations binding
├── error conversion
└── MemoryManager lifecycle glue

nicp_cdk に入れるもの

nicp_cdk
├── Rust staticlib build integration
├── wasm32-wasip1 toolchain support
├── init/preUpgrade/postUpgrade
└── Stable Memory abstraction redesign

28. 最終判断

ic-sqlite-vfs 本体へ大規模な NICP 対応を入れるべきではない。

最も妥当な境界は以下である。

                    generic
                       │
                       ▼
               ic-sqlite-vfs
                       │
                  Rust API only
                       │
                       ▼
               nicp-sqlite-ffi
                       │
                    C ABI
                       │
                       ▼
                 nim-ic-sqlite
                       │
                    Nim API
                       │
                       ▼
                    nicp_cdk

短期的には、ic-sqlite-vfs の必須修正を wasm32-wasip1 の正式 build support に限定する

Stable Memory ownership については、まず Rust 側 MemoryManager を唯一の manager として PoC と upgrade persistence を成立させる。

その後、NICP が manager ownership を持つ必要性が明確になった場合のみ、ic-sqlite-vfs外部 Stable Memory backend injection を追加する。

この順序にすることで、

  • upstream 改修を小さくできる
  • SQLite と NICP の問題を切り分けられる
  • stable format compatibility risk を抑えられる
  • nim-ic-sqlite の開発を先行できる

という利点がある。


29. 参照した現状コード

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions