Skip to content

Commit

Permalink
sqlite: add api endpoint to check fts integrity
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonish committed May 9, 2024
1 parent c5554f0 commit a47ce83
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 32 deletions.
20 changes: 10 additions & 10 deletions src/commands/sqlite/fts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use super::{FtsArgs, FtsCommand};
use crate::sqlite::{
importer::extract_values, init_event_db, util::fts_create, ConnectionBuilder, SqliteExt,
importer::extract_values, init_event_db, util::{self, fts_create}, ConnectionBuilder, SqliteExt,
};
use anyhow::Result;
use rusqlite::{params, Transaction};
Expand Down Expand Up @@ -70,16 +70,16 @@ fn fts_check(filename: &str) -> Result<()> {
return Ok(());
}
info!("FTS is enabled, checking integrity");
if conn
.execute(
"insert into fts(fts, rank) values ('integrity-check', 1)",
[],
)
.is_err()
{
bail!("FTS data corrupt");

match util::fts_check(&conn) {
Ok(_) => {
info!("FTS data OK");
}
Err(err) => {
bail!("FTS data is NOT OK: {:?}", err);
}
}
info!("FTS data OK");

Ok(())
}

Expand Down
82 changes: 61 additions & 21 deletions src/server/api/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,38 @@
use crate::{
eventrepo::EventRepo,
server::{main::SessionExtractor, ServerContext},
sqlite::info::Info,
sqlite::{self, info::Info},
};
use axum::{response::IntoResponse, Extension, Json};
use serde::Serialize;
use std::sync::Arc;
use tracing::info;

use super::ApiError;

#[derive(Default, Serialize)]
struct InfoResponse {
auto_vacuum: u8,
journal_mode: String,
synchronous: u8,
fts_enabled: bool,
page_size: i64,
page_count: i64,
freelist_count: i64,
min_event_id: u64,
max_event_id: u64,
event_count_estimate: u64,
data_size: u64,
schema_version: u64,
min_timestamp: Option<String>,
max_timestamp: Option<String>,
}

pub(crate) async fn info(
context: Extension<Arc<ServerContext>>,
_session: SessionExtractor,
) -> Result<impl IntoResponse, ApiError> {
if let EventRepo::SQLite(sqlite) = &context.datastore {
#[derive(Default, Serialize)]
struct Response {
auto_vacuum: u8,
journal_mode: String,
synchronous: u8,
fts_enabled: bool,
page_size: i64,
page_count: i64,
freelist_count: i64,
min_event_id: u64,
max_event_id: u64,
event_count_estimate: u64,
data_size: u64,
schema_version: u64,
min_timestamp: Option<String>,
max_timestamp: Option<String>,
}

let min_row_id = sqlite.min_row_id().await?;
let max_row_id = sqlite.max_row_id().await?;
let event_count_estimate = max_row_id - min_row_id;
Expand All @@ -46,8 +47,8 @@ pub(crate) async fn info(
.pool
.get()
.await?
.interact(move |conn| -> Result<InfoResponse, rusqlite::Error> {
let mut response = InfoResponse {
.interact(move |conn| -> Result<Response, rusqlite::Error> {
let mut response = Response {
min_timestamp: min_timestamp.map(|ts| ts.to_string()),
max_timestamp: max_timestamp.map(|ts| ts.to_string()),
..Default::default()
Expand All @@ -74,3 +75,42 @@ pub(crate) async fn info(

Ok(().into_response())
}

pub(crate) async fn fts_check(
context: Extension<Arc<ServerContext>>,
_session: SessionExtractor,
) -> Result<impl IntoResponse, ApiError> {
if let EventRepo::SQLite(sqlite) = &context.datastore {
#[derive(Debug, Serialize)]
struct Response {
ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
}

info!("Running SQLite FTS integrity check from API");

let response = sqlite
.pool
.get()
.await?
.interact(move |conn| -> Result<Response, rusqlite::Error> {
let response = match sqlite::util::fts_check(conn) {
Ok(_) => Response {
ok: true,
error: None,
},
Err(err) => Response {
ok: false,
error: Some(format!("{:?}", err)),
},
};

Ok(response)
})
.await??;
return Ok(Json(response).into_response());
}

Ok(().into_response())
}
1 change: 1 addition & 0 deletions src/server/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ pub(crate) fn build_axum_service(
.route("/api/1/sensors", get(api::stats::get_sensor_names))
.route("/api/1/groupby", get(api::groupby::group_by))
.route("/api/1/sqlite/info", get(api::sqlite::info))
.route("/api/1/sqlite/fts/check", get(api::sqlite::fts_check))
.nest("/api/1/stats", api::stats::router())
.layer(DefaultBodyLimit::max(1024 * 1024 * 32))
.layer(Extension(context.clone()))
Expand Down
8 changes: 7 additions & 1 deletion src/sqlite/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use rusqlite::Transaction;

pub fn fts_create(tx: &Transaction) -> Result<(), rusqlite::Error> {
pub(crate) fn fts_create(tx: &Transaction) -> Result<(), rusqlite::Error> {
tx.execute(
"create virtual table fts
using fts5(timestamp unindexed, source_values, content=events, content_rowid=rowid)",
Expand All @@ -20,3 +20,9 @@ pub fn fts_create(tx: &Transaction) -> Result<(), rusqlite::Error> {

Ok(())
}

pub(crate) fn fts_check(conn: &rusqlite::Connection) -> Result<(), rusqlite::Error> {
let mut stmt = conn.prepare("insert into fts(fts, rank) values ('integrity-check', 1)")?;
let _ = stmt.execute([])?;
Ok(())
}

0 comments on commit a47ce83

Please sign in to comment.