use std::rc::Rc;
use miden_client::account::AccountId;
use miden_client::note::{BlockNumber, NoteId};
use miden_client::store::{InputNoteCursor, InputNoteState, NoteFilter, OutputNoteState};
use miden_client::utils::Serializable;
use rusqlite::types::{ToSqlOutput, Value};
use crate::blob_array;
type NoteQueryParams = Vec<ToSqlOutput<'static>>;
fn in_rarray_condition(
column: &str,
values: Rc<Vec<Value>>,
params: &mut NoteQueryParams,
) -> String {
params.push(ToSqlOutput::Array(values));
format!("({column} IN rarray(?))")
}
fn state_condition(discriminants: &[u8]) -> String {
let list = discriminants.iter().map(u8::to_string).collect::<Vec<_>>().join(", ");
format!("(state_discriminant IN ({list}))")
}
const OUTPUT_NOTES_BASE_QUERY: &str = "SELECT \
note.recipient_digest AS recipient_digest, \
note.assets AS assets, \
note.metadata AS metadata, \
note.expected_height AS expected_height, \
note.state AS state, \
note.attachments AS attachments, \
script.serialized_note_script AS serialized_note_script \
from output_notes AS note \
LEFT OUTER JOIN notes_scripts AS script ON note.script_root = script.script_root";
pub(super) fn note_filter_to_query_output_notes(filter: &NoteFilter) -> (String, NoteQueryParams) {
let (condition, params) = note_filter_output_notes_condition(filter);
let query = format!("{OUTPUT_NOTES_BASE_QUERY} WHERE {condition}");
(query, params)
}
fn note_filter_output_notes_condition(filter: &NoteFilter) -> (String, NoteQueryParams) {
let mut params = Vec::new();
let condition = match filter {
NoteFilter::All => "(1 = 1)".to_string(),
NoteFilter::Committed => state_condition(&[
OutputNoteState::STATE_COMMITTED_PARTIAL,
OutputNoteState::STATE_COMMITTED_FULL,
]),
NoteFilter::Consumed => state_condition(&[OutputNoteState::STATE_CONSUMED]),
NoteFilter::Expected => state_condition(&[
OutputNoteState::STATE_EXPECTED_PARTIAL,
OutputNoteState::STATE_EXPECTED_FULL,
]),
NoteFilter::Processing | NoteFilter::Unverified => "(1 = 0)".to_string(),
NoteFilter::ScriptRoots(script_roots) => {
in_rarray_condition("note.script_root", blob_array(script_roots), &mut params)
},
NoteFilter::Unique(note_id) => {
in_rarray_condition("note.note_id", blob_array([note_id.as_word()]), &mut params)
},
NoteFilter::List(note_ids) => in_rarray_condition(
"note.note_id",
blob_array(note_ids.iter().map(NoteId::as_word)),
&mut params,
),
NoteFilter::DetailsCommitments(commitments) => {
in_rarray_condition("note.details_commitment", blob_array(commitments), &mut params)
},
NoteFilter::Nullifiers(nullifiers) => {
in_rarray_condition("note.nullifier", blob_array(nullifiers), &mut params)
},
NoteFilter::Unspent => state_condition(&[
OutputNoteState::STATE_EXPECTED_PARTIAL,
OutputNoteState::STATE_EXPECTED_FULL,
OutputNoteState::STATE_COMMITTED_PARTIAL,
OutputNoteState::STATE_COMMITTED_FULL,
]),
};
(condition, params)
}
const INPUT_NOTES_BASE_QUERY: &str = "SELECT \
note.assets AS assets, \
note.serial_number AS serial_number, \
note.inputs AS inputs, \
script.serialized_note_script AS serialized_note_script, \
note.state AS state, \
note.created_at AS created_at, \
note.attachments AS attachments \
from input_notes AS note \
LEFT OUTER JOIN notes_scripts AS script ON note.script_root = script.script_root";
pub(super) fn note_filter_to_query_input_notes(filter: &NoteFilter) -> (String, NoteQueryParams) {
let (condition, params) = note_filter_input_notes_condition(filter);
let query = if matches!(filter, NoteFilter::Consumed) {
format!(
"{INPUT_NOTES_BASE_QUERY} WHERE {condition} \
ORDER BY note.consumed_block_height ASC, \
note.consumed_tx_order IS NULL, note.consumed_tx_order ASC, \
note.details_commitment ASC"
)
} else {
format!("{INPUT_NOTES_BASE_QUERY} WHERE {condition}")
};
(query, params)
}
pub(super) fn note_filter_to_query_input_note_after(
filter: &NoteFilter,
consumer: AccountId,
block_start: Option<BlockNumber>,
block_end: Option<BlockNumber>,
cursor: Option<InputNoteCursor>,
) -> (String, NoteQueryParams) {
let (mut condition, mut params) = note_filter_input_notes_condition(filter);
params.push(ToSqlOutput::from(consumer.to_bytes()));
condition.push_str(" AND note.consumer_account_id = ?");
condition.push_str(" AND note.consumed_tx_order IS NOT NULL");
let cursor = cursor
.filter(|cursor| block_start.is_none_or(|start| cursor.consumed_block_height() >= start));
match cursor {
Some(cursor) => {
condition.push_str(
" AND (note.consumed_block_height, note.consumed_tx_order, \
note.details_commitment) > (?, ?, ?)",
);
params.push(ToSqlOutput::from(cursor.consumed_block_height().as_u32()));
params.push(ToSqlOutput::from(cursor.consumed_tx_order()));
params.push(ToSqlOutput::from(cursor.details_commitment().to_bytes()));
},
None => {
if let Some(start) = block_start {
condition.push_str(" AND note.consumed_block_height >= ?");
params.push(ToSqlOutput::from(start.as_u32()));
}
},
}
if let Some(end) = block_end {
condition.push_str(" AND note.consumed_block_height <= ?");
params.push(ToSqlOutput::from(end.as_u32()));
}
let query = format!(
"{INPUT_NOTES_BASE_QUERY} WHERE {condition} \
ORDER BY note.consumed_block_height ASC, note.consumed_tx_order ASC, \
note.details_commitment ASC \
LIMIT 1"
);
(query, params)
}
pub(super) fn note_filter_input_notes_condition(filter: &NoteFilter) -> (String, NoteQueryParams) {
let mut params = Vec::new();
let condition = match filter {
NoteFilter::All => "(1 = 1)".to_string(),
NoteFilter::Committed => state_condition(&[InputNoteState::STATE_COMMITTED]),
NoteFilter::Consumed => state_condition(&[
InputNoteState::STATE_CONSUMED_AUTHENTICATED_LOCAL,
InputNoteState::STATE_CONSUMED_UNAUTHENTICATED_LOCAL,
InputNoteState::STATE_CONSUMED_EXTERNAL,
]),
NoteFilter::Expected => state_condition(&[InputNoteState::STATE_EXPECTED]),
NoteFilter::Processing => state_condition(&[
InputNoteState::STATE_PROCESSING_AUTHENTICATED,
InputNoteState::STATE_PROCESSING_UNAUTHENTICATED,
]),
NoteFilter::Unique(note_id) => {
in_rarray_condition("note.note_id", blob_array([note_id.as_word()]), &mut params)
},
NoteFilter::List(note_ids) => in_rarray_condition(
"note.note_id",
blob_array(note_ids.iter().map(NoteId::as_word)),
&mut params,
),
NoteFilter::DetailsCommitments(commitments) => {
in_rarray_condition("note.details_commitment", blob_array(commitments), &mut params)
},
NoteFilter::Nullifiers(nullifiers) => {
in_rarray_condition("note.nullifier", blob_array(nullifiers), &mut params)
},
NoteFilter::ScriptRoots(script_roots) => {
in_rarray_condition("note.script_root", blob_array(script_roots), &mut params)
},
NoteFilter::Unverified => state_condition(&[InputNoteState::STATE_UNVERIFIED]),
NoteFilter::Unspent => state_condition(&InputNoteState::UNSPENT_STATES),
};
(condition, params)
}