|
| 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