Skip to content

Commit 88a8919

Browse files
committed
feat: Add SQLite ODB backend example using rusqlite 0.37
1 parent 04b79b0 commit 88a8919

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

Cargo.lock

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ openssl-probe = { version = "0.1", optional = true }
2929
[dev-dependencies]
3030
clap = { version = "4.4.13", features = ["derive"] }
3131
time = { version = "0.3.37", features = ["formatting"] }
32+
rusqlite = { version = "0.37.0", features = ["bundled"] }
3233
tempfile = "3.1.0"
3334
url = "2.5.4"
3435

examples/odb_backend_sqlite.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! # ODB backend implementation: SQLite
2+
//! The following is a port of libgit2-backends' `sqlite/sqlite.c` file.
3+
4+
use git2::odb_backend::{OdbBackend, OdbBackendAllocation, OdbBackendContext, SupportedOperations};
5+
use git2::{Error, ErrorClass, ErrorCode, ObjectType, Oid};
6+
use libgit2_sys as raw;
7+
use rusqlite::Error as RusqliteError;
8+
use rusqlite::{params, OptionalExtension};
9+
use std::convert::Infallible;
10+
11+
pub struct SqliteBackend {
12+
conn: rusqlite::Connection,
13+
}
14+
15+
const ST_READ: &str = "SELECT type, size, data FROM 'git2_odb' WHERE oid = ?;";
16+
const ST_READ_HEADER: &str = "SELECT type, size FROM 'git2_odb' WHERE `oid` = ?;";
17+
const ST_WRITE: &str = "INSERT OR IGNORE INTO 'git2_odb' VALUES (?, ?, ?, ?)";
18+
19+
impl SqliteBackend {
20+
pub fn new(conn: rusqlite::Connection) -> rusqlite::Result<Self> {
21+
// Check if we need to create the git2_odb table
22+
if conn.table_exists(None, "git2_odb")? {
23+
// Table exists, do nothing
24+
} else {
25+
conn.execute("CREATE TABLE 'git2_odb' ('oid' CHARACTER(20) PRIMARY KET NOT NULL, 'type' INTEGER NOT NULL, 'size' INTEGER NOT NULL, 'data' BLOB)", params![])?;
26+
}
27+
28+
conn.prepare_cached(ST_READ)?;
29+
conn.prepare_cached(ST_READ_HEADER)?;
30+
conn.prepare_cached(ST_WRITE)?;
31+
32+
Ok(Self { conn })
33+
}
34+
}
35+
36+
impl OdbBackend for SqliteBackend {
37+
type Writepack = Infallible;
38+
type ReadStream = Infallible;
39+
type WriteStream = Infallible;
40+
41+
fn supported_operations(&self) -> SupportedOperations {
42+
SupportedOperations::READ
43+
| SupportedOperations::READ_HEADER
44+
| SupportedOperations::WRITE
45+
| SupportedOperations::EXISTS
46+
}
47+
48+
fn read(
49+
&mut self,
50+
ctx: &OdbBackendContext,
51+
oid: Oid,
52+
object_type: &mut ObjectType,
53+
data: &mut OdbBackendAllocation,
54+
) -> Result<(), Error> {
55+
let row = self
56+
.conn
57+
.prepare_cached(ST_READ)
58+
.map_err(map_sqlite_err)?
59+
.query_one(params![oid.as_bytes()], |row| {
60+
let object_type: raw::git_object_t = row.get(0)?;
61+
let size: usize = row.get(1)?;
62+
let data: Box<[u8]> = row.get(2)?;
63+
Ok((ObjectType::from_raw(object_type).unwrap(), size, data))
64+
})
65+
.map_err(map_sqlite_err)?;
66+
*object_type = row.0;
67+
*data = ctx.try_alloc(row.1)?;
68+
data.as_mut().copy_from_slice(&row.2);
69+
Ok(())
70+
}
71+
72+
fn read_header(
73+
&mut self,
74+
_ctx: &OdbBackendContext,
75+
oid: Oid,
76+
length: &mut usize,
77+
object_type: &mut ObjectType,
78+
) -> Result<(), Error> {
79+
let row = self
80+
.conn
81+
.prepare_cached(ST_READ_HEADER)
82+
.map_err(map_sqlite_err)?
83+
.query_one(params![oid.as_bytes()], |row| {
84+
let object_type: raw::git_object_t = row.get(0)?;
85+
let size: usize = row.get(1)?;
86+
Ok((ObjectType::from_raw(object_type).unwrap(), size))
87+
})
88+
.map_err(map_sqlite_err)?;
89+
*object_type = row.0;
90+
*length = row.1;
91+
Ok(())
92+
}
93+
94+
fn write(
95+
&mut self,
96+
_ctx: &OdbBackendContext,
97+
oid: Oid,
98+
object_type: ObjectType,
99+
data: &[u8],
100+
) -> Result<(), Error> {
101+
self.conn
102+
.prepare_cached(ST_WRITE)
103+
.map_err(map_sqlite_err)?
104+
.execute(params![
105+
oid.as_bytes(),
106+
object_type.raw(),
107+
oid.as_bytes().len(),
108+
data
109+
])
110+
.map_err(map_sqlite_err)?;
111+
Ok(())
112+
}
113+
114+
fn exists(&mut self, _ctx: &OdbBackendContext, oid: Oid) -> Result<bool, Error> {
115+
let row = self
116+
.conn
117+
.prepare_cached(ST_READ_HEADER)
118+
.map_err(map_sqlite_err)?
119+
.query_one(params![oid.as_bytes()], |_| Ok(()))
120+
.optional()
121+
.map_err(map_sqlite_err)?;
122+
Ok(row.is_some())
123+
}
124+
}
125+
126+
fn map_sqlite_err(err: RusqliteError) -> Error {
127+
match err {
128+
RusqliteError::QueryReturnedNoRows => {
129+
Error::new(ErrorCode::NotFound, ErrorClass::None, "not found")
130+
}
131+
_ => Error::new(ErrorCode::GenericError, ErrorClass::Object, err.to_string()),
132+
}
133+
}
134+
135+
fn main() {
136+
todo!("Demonstrate how to use SqliteBackend")
137+
}

0 commit comments

Comments
 (0)