Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sqlite store impl #287

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 14 additions & 29 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:

jobs:
build_and_test:
name: cargo ${{ matrix.cargo_flags }}
name: cargo clippy + test
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
Expand All @@ -31,14 +31,23 @@ jobs:

- name: Install protobuf
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler
sudo apt-get update -qq
sudo apt-get install -yqq protobuf-compiler

- name: Configure CI cache
uses: Swatinem/rust-cache@v2

- name: Build
run: cargo build --all-targets
- name: Prepare .sqlx files
working-directory: presage-store-sqlite
env:
DATABASE_URL: sqlite:presage.sqlite
run: |
cargo install --locked sqlx-cli
yes | cargo sqlx database reset
cargo sqlx prepare

- name: Clippy
run: cargo clippy --all-targets

- name: Test
run: cargo test --all-targets
Expand All @@ -61,27 +70,3 @@ jobs:

- name: Check code format
run: cargo fmt -- --check

clippy:
name: clippy
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
components: clippy

- name: Install protobuf
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler

- name: Setup CI cache
uses: Swatinem/rust-cache@v2

- name: Run clippy lints
run: cargo clippy
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
8 changes: 7 additions & 1 deletion presage-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ edition = "2021"
authors = ["Gabriel Féron <[email protected]>"]
license = "AGPL-3.0-only"

[features]
default = ["sqlite"]
sled = ["presage-store-sled"]
sqlite = ["presage-store-sqlite"]

[dependencies]
presage = { path = "../presage" }
presage-store-sled = { path = "../presage-store-sled" }
presage-store-sled = { path = "../presage-store-sled", optional = true }
presage-store-sqlite = { path = "../presage-store-sqlite", optional = true }

anyhow = { version = "1.0", features = ["backtrace"] }
base64 = "0.22"
Expand Down
16 changes: 11 additions & 5 deletions presage-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ use presage::{
store::{Store, Thread},
Manager,
};
use presage_store_sled::MigrationConflictStrategy;
use presage_store_sled::SledStore;
use tempfile::Builder;
use tokio::task;
use tokio::{
Expand Down Expand Up @@ -223,14 +221,22 @@ async fn main() -> anyhow::Result<()> {
.config_dir()
.into()
});
debug!(db_path =% db_path.display(), "opening config database");
let config_store = SledStore::open_with_passphrase(
debug!(dir =% db_path.display(), "opening database in dir");

#[cfg(feature = "sled")]
let config_store = presage_store_sled::SledStore::open_with_passphrase(
db_path,
args.passphrase,
MigrationConflictStrategy::Raise,
presage_store_sled::MigrationConflictStrategy::Raise,
OnNewIdentity::Trust,
)
.await?;

#[cfg(feature = "sqlite")]
let config_store =
presage_store_sqlite::SqliteStore::open(db_path.join("db.sqlite"), OnNewIdentity::Trust)
.await?;

run(args.subcommand, config_store).await
}

Expand Down
2 changes: 1 addition & 1 deletion presage-store-sled/src/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl ContentsStore for SledStore {
Ok(())
}

async fn save_contact(&mut self, contact: &Contact) -> Result<(), SledStoreError> {
async fn save_contact(&mut self, contact: Contact) -> Result<(), SledStoreError> {
self.insert(SLED_TREE_CONTACTS, contact.uuid, contact)?;
debug!("saved contact");
Ok(())
Expand Down
1 change: 1 addition & 0 deletions presage-store-sqlite/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL="sqlite:presage.sqlite"
2 changes: 2 additions & 0 deletions presage-store-sqlite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.sqlx/*
presage.sqlite
8 changes: 7 additions & 1 deletion presage-store-sqlite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ edition = "2021"

[dependencies]
async-trait = "0.1.83"
bytes = "1.8.0"
chrono = "0.4.38"
hex = "0.4.3"
postcard = { version = "1.0.10", features = ["alloc"] }
presage = { path = "../presage" }
presage-store-cipher = { path = "../presage-store-cipher", optional = true }
prost = "0.13.3"

sqlx = { version = "0.8.2", features = ["sqlite"] }
sqlx = { version = "0.8.2", features = ["sqlite", "uuid", "runtime-tokio"] }
thiserror = "1.0.65"
tracing = "0.1.40"
uuid = { version = "1.11.0", features = ["v4"] }
164 changes: 164 additions & 0 deletions presage-store-sqlite/migrations/20241024072558_Initial_data_model.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
CREATE TABLE config(
key TEXT PRIMARY KEY NOT NULL ON CONFLICT REPLACE,
value BLOB NOT NULL
);

CREATE TABLE sessions (
address VARCHAR(36) NOT NULL,
device_id INTEGER NOT NULL,
record BLOB NOT NULL,
identity TEXT CHECK(identity IN ('aci', 'pni')) NOT NULL DEFAULT 'aci',

PRIMARY KEY(address, device_id, identity) ON CONFLICT REPLACE
);

CREATE TABLE identities (
address VARCHAR(36) NOT NULL,
record BLOB NOT NULL,
identity TEXT CHECK(identity IN ('aci', 'pni')) NOT NULL DEFAULT 'aci',

-- TODO: Signal adds a lot more fields here that I don't yet care about.

PRIMARY KEY(address, identity) ON CONFLICT REPLACE
);

CREATE TABLE prekeys (
id INTEGER NOT NULL,
record BLOB NOT NULL,
identity TEXT CHECK(identity IN ('aci', 'pni')) NOT NULL,

PRIMARY KEY(id, identity) ON CONFLICT REPLACE
);

CREATE TABLE signed_prekeys (
id INTEGER,
record BLOB NOT NULL,
identity TEXT CHECK(identity IN ('aci', 'pni')) NOT NULL DEFAULT 'aci',

PRIMARY KEY(id, identity) ON CONFLICT REPLACE
);

CREATE TABLE kyber_prekeys (
id INTEGER,
record BLOB NOT NULL,
is_last_resort BOOLEAN DEFAULT FALSE NOT NULL,
identity TEXT CHECK(identity IN ('aci', 'pni')) NOT NULL,

PRIMARY KEY(id, identity) ON CONFLICT REPLACE
);

CREATE TABLE sender_keys (
address VARCHAR(36),
device INTEGER NOT NULL,
distribution_id TEXT NOT NULL,
record BLOB NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
identity TEXT CHECK(identity IN ('aci', 'pni')) NOT NULL DEFAULT 'aci',

PRIMARY KEY(address, device, distribution_id) ON CONFLICT REPLACE
);

-- Groups
CREATE TABLE groups(
id INTEGER PRIMARY KEY AUTOINCREMENT,
master_key BLOB NOT NULL,
title TEXT NOT NULL,
revision INTEGER NOT NULL DEFAULT 0,
invite_link_password BLOB,
access_required BLOB,
avatar TEXT NOT NULL,
description TEXT,
members BLOB NOT NULL,
pending_members BLOB NOT NULL,
requesting_members BLOB NOT NULL
);

CREATE TABLE group_avatars(
id INTEGER PRIMARY KEY AUTOINCREMENT,
bytes BLOB NOT NULL,

FOREIGN KEY(id) REFERENCES groups(id) ON DELETE CASCADE
);

CREATE TABLE contacts(
uuid VARCHAR(36) NOT NULL,
-- E.164 numbers should never be longer than 15 chars (excl. international prefix)
phone_number VARCHAR(20),
name TEXT NOT NULL,
color VARCHAR(32),
profile_key BLOB NOT NULL,
expire_timer INTEGER NOT NULL,
expire_timer_version INTEGER NOT NULL DEFAULT 2,
inbox_position INTEGER NOT NULL,
archived BOOLEAN NOT NULL,
avatar BLOB,

PRIMARY KEY(uuid) ON CONFLICT REPLACE
);

CREATE TABLE contacts_verification_state(
destination_aci VARCHAR(36) NOT NULL,
identity_key BLOB NOT NULL,
is_verified BOOLEAN,

FOREIGN KEY(destination_aci) REFERENCES contacts(uuid) ON UPDATE CASCADE,
PRIMARY KEY(destination_aci) ON CONFLICT REPLACE
);

CREATE TABLE profile_keys(
uuid VARCHAR(36) NOT NULL,
key BLOB NOT NULL,

PRIMARY KEY(uuid) ON CONFLICT REPLACE
);

CREATE TABLE profiles(
uuid VARCHAR(36) NOT NULL,
given_name TEXT,
family_name TEXT,
about TEXT,
about_emoji TEXT,
avatar TEXT,

FOREIGN KEY(uuid) REFERENCES profile_keys(uuid) ON UPDATE CASCADE
PRIMARY KEY(uuid) ON CONFLICT REPLACE
);

CREATE TABLE profile_avatars(
uuid VARCHAR(36) NOT NULL,
bytes BLOB NOT NULL,

FOREIGN KEY(uuid) REFERENCES profile_keys(uuid) ON UPDATE CASCADE
);

-- Threads
CREATE TABLE threads (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
group_id BLOB DEFAULT NULL,
recipient_id VARCHAR(36) DEFAULT NULL,

FOREIGN KEY(id) REFERENCES groups(id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX threads_target ON threads(group_id, recipient_id);

CREATE TABLE thread_messages(
ts INTEGER NOT NULL,
thread_id INTEGER NOT NULL,

sender_service_id TEXT NOT NULL,
sender_device_id INTEGER NOT NULL,
destination_service_id TEXT NOT NULL,
needs_receipt BOOLEAN NOT NULL,
unidentified_sender BOOLEAN NOT NULL,

content_body BLOB NOT NULL,

PRIMARY KEY(ts, thread_id) ON CONFLICT REPLACE,
FOREIGN KEY(thread_id) REFERENCES threads(id) ON UPDATE CASCADE
);

CREATE TABLE sticker_packs(
id BLOB PRIMARY KEY NOT NULL,
key BLOB NOT NULL,
manifest BLOB NOT NULL
);
Loading
Loading