mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-05 19:59:09 -07:00
query: init
This commit is contained in:
@@ -16,3 +16,9 @@ brk_parser = { workspace = true }
|
||||
brk_query = { workspace = true }
|
||||
brk_server = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
[[bin]]
|
||||
name = "brk"
|
||||
path = "src/main.rs"
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use std::{path::Path, thread::sleep, time::Duration};
|
||||
|
||||
use brk_computer::Computer;
|
||||
use brk_exit::Exit;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::rpc::{self, RpcApi};
|
||||
use brk_query::{Index, Params as QueryParams, Query};
|
||||
use brk_server::tokio;
|
||||
use clap::{Parser, Subcommand};
|
||||
use log::info;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about)]
|
||||
@@ -8,33 +17,87 @@ struct Cli {
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
Run(RunArgs),
|
||||
Query(QueryArgs),
|
||||
Query(QueryParams),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[derive(Parser, Debug)]
|
||||
struct RunArgs {
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
struct QueryArgs {
|
||||
name: Option<String>,
|
||||
}
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
brk_logger::init(Some(Path::new(".log")));
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// You can check for the existence of subcommands, and if found use their
|
||||
// matches just as you would the top level cmd
|
||||
let outputs_dir = Path::new("../../_outputs");
|
||||
|
||||
let mut indexer = Indexer::import(&outputs_dir.join("indexed"))?;
|
||||
|
||||
let mut computer = Computer::import(&outputs_dir.join("computed"))?;
|
||||
|
||||
match &cli.command {
|
||||
Commands::Run(name) => {
|
||||
println!("'myapp add' was used, name is: {:?}", name.name);
|
||||
Commands::Run(args) => {
|
||||
let data_dir = Path::new("../../../bitcoin");
|
||||
let rpc = Box::leak(Box::new(rpc::Client::new(
|
||||
"http://localhost:8332",
|
||||
rpc::Auth::CookieFile(Path::new(data_dir).join(".cookie")),
|
||||
)?));
|
||||
let exit = Exit::new();
|
||||
|
||||
let parser = brk_parser::Parser::new(data_dir, rpc);
|
||||
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(async {
|
||||
let served_indexer = indexer.clone();
|
||||
let served_computer = computer.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
brk_server::main(served_indexer, served_computer).await.unwrap();
|
||||
});
|
||||
|
||||
loop {
|
||||
let block_count = rpc.get_block_count()?;
|
||||
|
||||
info!("{block_count} blocks found.");
|
||||
|
||||
let starting_indexes = indexer.index(&parser, rpc, &exit)?;
|
||||
|
||||
computer.compute(&mut indexer, starting_indexes, &exit)?;
|
||||
|
||||
info!("Waiting for new blocks...");
|
||||
|
||||
while block_count == rpc.get_block_count()? {
|
||||
sleep(Duration::from_secs(1))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
Commands::Query(name) => {
|
||||
println!("'myapp add' was used, name is: {:?}", name.name);
|
||||
Commands::Query(args) => {
|
||||
let query = Query::build(&indexer, &computer);
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
query.search(
|
||||
Index::try_from(args.index.as_str())?,
|
||||
&args.values.iter().flat_map(|v| v.split(",")).collect::<Vec<_>>(),
|
||||
args.from,
|
||||
args.to,
|
||||
args.format
|
||||
)?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub use brk_parser::*;
|
||||
use bitcoin::{Transaction, TxIn, TxOut};
|
||||
use brk_exit::Exit;
|
||||
use color_eyre::eyre::{ContextCompat, eyre};
|
||||
use log::info;
|
||||
use log::{debug, info};
|
||||
use rayon::prelude::*;
|
||||
mod indexes;
|
||||
mod stores;
|
||||
@@ -41,7 +41,7 @@ impl Indexer {
|
||||
pub fn import(indexes_dir: &Path) -> color_eyre::Result<Self> {
|
||||
setrlimit()?;
|
||||
|
||||
info!("Importing indexes...");
|
||||
debug!("Importing indexes...");
|
||||
|
||||
let vecs = Vecs::import(&indexes_dir.join("vecs"))?;
|
||||
|
||||
|
||||
@@ -9,4 +9,9 @@ repository.workspace = true
|
||||
[dependencies]
|
||||
brk_computer = { workspace = true }
|
||||
brk_indexer = { workspace = true }
|
||||
brk_vec = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
derive_deref = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use clap::ValueEnum;
|
||||
use color_eyre::eyre::eyre;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)]
|
||||
pub enum Format {
|
||||
JSON,
|
||||
CSV,
|
||||
TSV,
|
||||
JSON,
|
||||
}
|
||||
|
||||
impl TryFrom<Option<String>> for Format {
|
||||
86
crates/brk_query/src/index.rs
Normal file
86
crates/brk_query/src/index.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Index {
|
||||
Addressindex,
|
||||
Dateindex,
|
||||
Height,
|
||||
P2PK33index,
|
||||
P2PK65index,
|
||||
P2PKHindex,
|
||||
P2SHindex,
|
||||
P2TRindex,
|
||||
P2WPKHindex,
|
||||
P2WSHindex,
|
||||
Txindex,
|
||||
Txinindex,
|
||||
Txoutindex,
|
||||
}
|
||||
|
||||
impl Index {
|
||||
pub fn all() -> [Self; 13] {
|
||||
[
|
||||
Self::Addressindex,
|
||||
Self::Dateindex,
|
||||
Self::Height,
|
||||
Self::P2PK33index,
|
||||
Self::P2PK65index,
|
||||
Self::P2PKHindex,
|
||||
Self::P2SHindex,
|
||||
Self::P2TRindex,
|
||||
Self::P2WPKHindex,
|
||||
Self::P2WSHindex,
|
||||
Self::Txindex,
|
||||
Self::Txinindex,
|
||||
Self::Txoutindex,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn ids(&self) -> &[&str] {
|
||||
match self {
|
||||
Self::Dateindex => &["d", "date", "dateindex"],
|
||||
Self::Height => &["h", "height"],
|
||||
Self::Txindex => &["txi", "txindex"],
|
||||
Self::Txinindex => &["txini", "txinindex"],
|
||||
Self::Txoutindex => &["txouti", "txoutindex"],
|
||||
Self::Addressindex => &["addri", "addressindex"],
|
||||
Self::P2PK33index => &["p2pk33i", "p2pk33index"],
|
||||
Self::P2PK65index => &["p2pk65i", "p2pk65index"],
|
||||
Self::P2PKHindex => &["p2pkhi", "p2pkhindex"],
|
||||
Self::P2SHindex => &["p2shi", "p2shindex"],
|
||||
Self::P2TRindex => &["p2tri", "p2trindex"],
|
||||
Self::P2WPKHindex => &["p2wpkhi", "p2wpkhindex"],
|
||||
Self::P2WSHindex => &["p2wshi", "p2wshindex"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Index {
|
||||
type Error = color_eyre::Report;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
v if (Self::Dateindex).ids().contains(&v) => Self::Dateindex,
|
||||
v if (Self::Height).ids().contains(&v) => Self::Height,
|
||||
v if (Self::Txindex).ids().contains(&v) => Self::Txindex,
|
||||
v if (Self::Txinindex).ids().contains(&v) => Self::Txinindex,
|
||||
v if (Self::Txoutindex).ids().contains(&v) => Self::Txoutindex,
|
||||
v if (Self::Addressindex).ids().contains(&v) => Self::Addressindex,
|
||||
v if (Self::P2PK33index).ids().contains(&v) => Self::P2PK33index,
|
||||
v if (Self::P2PK65index).ids().contains(&v) => Self::P2PK65index,
|
||||
v if (Self::P2PKHindex).ids().contains(&v) => Self::P2PKHindex,
|
||||
v if (Self::P2SHindex).ids().contains(&v) => Self::P2SHindex,
|
||||
v if (Self::P2TRindex).ids().contains(&v) => Self::P2TRindex,
|
||||
v if (Self::P2WPKHindex).ids().contains(&v) => Self::P2WPKHindex,
|
||||
v if (Self::P2WSHindex).ids().contains(&v) => Self::P2WSHindex,
|
||||
_ => return Err(eyre!("Bad index")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Index {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,159 @@
|
||||
#![doc = "\n## Example\n\n```rust"]
|
||||
#![doc = include_str!("main.rs")]
|
||||
#![doc = "```"]
|
||||
|
||||
mod format;
|
||||
mod index;
|
||||
mod params;
|
||||
mod tree;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use brk_computer::Computer;
|
||||
use brk_indexer::Indexer;
|
||||
pub use format::Format;
|
||||
pub use index::Index;
|
||||
pub use params::Params;
|
||||
use serde::Serialize;
|
||||
use tree::VecIdToIndexToVec;
|
||||
|
||||
pub struct Query<'a> {
|
||||
pub vecid_to_index_to_vec: VecIdToIndexToVec<'a>,
|
||||
indexer: &'a Indexer,
|
||||
computer: &'a Computer,
|
||||
}
|
||||
|
||||
impl<'a> Query<'a> {
|
||||
pub fn build(indexer: &'a Indexer, computer: &'a Computer) -> Self {
|
||||
let mut vecs = VecIdToIndexToVec::default();
|
||||
|
||||
indexer.vecs.as_any_vecs().into_iter().for_each(|vec| vecs.insert(vec));
|
||||
computer.vecs.as_any_vecs().into_iter().for_each(|vec| vecs.insert(vec));
|
||||
|
||||
Self {
|
||||
vecid_to_index_to_vec: vecs,
|
||||
indexer,
|
||||
computer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search(
|
||||
&self,
|
||||
index: Index,
|
||||
values: &[&str],
|
||||
from: Option<i64>,
|
||||
to: Option<i64>,
|
||||
format: Option<Format>,
|
||||
) -> color_eyre::Result<QueryResponse> {
|
||||
let ids = values
|
||||
.iter()
|
||||
.map(|s| {
|
||||
(
|
||||
s.to_owned(),
|
||||
self.vecid_to_index_to_vec.get(&s.to_lowercase().replace("_", "-")),
|
||||
)
|
||||
})
|
||||
.filter(|(_, opt)| opt.is_some())
|
||||
.map(|(id, vec)| (id, vec.unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if ids.is_empty() {
|
||||
return Ok(QueryResponse::default(format));
|
||||
}
|
||||
|
||||
let mut values = ids
|
||||
.iter()
|
||||
.flat_map(|(_, i_to_v)| i_to_v.get(&index))
|
||||
.map(|vec| -> brk_vec::Result<Vec<serde_json::Value>> { vec.collect_range_values(from, to) })
|
||||
.collect::<brk_vec::Result<Vec<_>>>()?;
|
||||
|
||||
if values.is_empty() {
|
||||
return Ok(QueryResponse::default(format));
|
||||
}
|
||||
|
||||
let ids_last_i = ids.len() - 1;
|
||||
|
||||
Ok(match format {
|
||||
Some(Format::CSV) | Some(Format::TSV) => {
|
||||
let delimiter = if format == Some(Format::CSV) { ',' } else { '\t' };
|
||||
|
||||
let mut text = ids
|
||||
.into_iter()
|
||||
.map(|(id, _)| id)
|
||||
.collect::<Vec<_>>()
|
||||
.join(&delimiter.to_string());
|
||||
|
||||
text.push('\n');
|
||||
|
||||
let values_len = values.first().unwrap().len();
|
||||
|
||||
(0..values_len).for_each(|i| {
|
||||
let mut line = "".to_string();
|
||||
values.iter().enumerate().for_each(|(id_i, v)| {
|
||||
line += &v.get(i).unwrap().to_string();
|
||||
if id_i == ids_last_i {
|
||||
line.push('\n');
|
||||
} else {
|
||||
line.push(delimiter);
|
||||
}
|
||||
});
|
||||
text += &line;
|
||||
});
|
||||
|
||||
if format == Some(Format::CSV) {
|
||||
QueryResponse::CSV(text)
|
||||
} else {
|
||||
QueryResponse::TSV(text)
|
||||
}
|
||||
}
|
||||
Some(Format::JSON) | None => {
|
||||
if values.len() == 1 {
|
||||
let mut values = values.pop().unwrap();
|
||||
if values.len() == 1 {
|
||||
let value = values.pop().unwrap();
|
||||
QueryResponse::Json(Value::Single(value))
|
||||
} else {
|
||||
QueryResponse::Json(Value::List(values))
|
||||
}
|
||||
} else {
|
||||
QueryResponse::Json(Value::Matrix(values))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryResponse {
|
||||
Json(Value),
|
||||
CSV(String),
|
||||
TSV(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
Matrix(Vec<Vec<serde_json::Value>>),
|
||||
List(Vec<serde_json::Value>),
|
||||
Single(serde_json::Value),
|
||||
}
|
||||
|
||||
impl QueryResponse {
|
||||
fn default(format: Option<Format>) -> Self {
|
||||
match format {
|
||||
Some(Format::CSV) => QueryResponse::CSV("".to_string()),
|
||||
Some(Format::TSV) => QueryResponse::TSV("".to_string()),
|
||||
_ => QueryResponse::Json(Value::Single(serde_json::Value::Null)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for QueryResponse {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Json(value) => write!(f, "{}", serde_json::to_string_pretty(value).unwrap()),
|
||||
Self::CSV(string) => write!(f, "{}", string),
|
||||
Self::TSV(string) => write!(f, "{}", string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
use std::path::Path;
|
||||
|
||||
use brk_computer::Computer;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_query::{Index, Query};
|
||||
|
||||
pub fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
let outputs_dir = Path::new("../../_outputs");
|
||||
|
||||
let indexer = Indexer::import(&outputs_dir.join("indexed"))?;
|
||||
|
||||
let computer = Computer::import(&outputs_dir.join("computed"))?;
|
||||
|
||||
let query = Query::build(&indexer, &computer);
|
||||
|
||||
dbg!(query.search(Index::Height, &["date"], Some(-1), None, None)?);
|
||||
dbg!(query.search(Index::Height, &["date"], Some(-10), None, None)?);
|
||||
dbg!(query.search(Index::Height, &["date", "timestamp"], Some(-10), None, None)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
18
crates/brk_query/src/params.rs
Normal file
18
crates/brk_query/src/params.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use clap::Parser;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::Format;
|
||||
|
||||
#[derive(Debug, Deserialize, Parser)]
|
||||
pub struct Params {
|
||||
#[clap(short, long)]
|
||||
pub index: String,
|
||||
#[clap(short, long, value_delimiter = ' ', num_args = 1..)]
|
||||
pub values: Vec<String>,
|
||||
#[clap(short, long, allow_hyphen_values = true)]
|
||||
pub from: Option<i64>,
|
||||
#[clap(short, long, allow_hyphen_values = true)]
|
||||
pub to: Option<i64>,
|
||||
#[clap(long)]
|
||||
pub format: Option<Format>,
|
||||
}
|
||||
38
crates/brk_query/src/tree.rs
Normal file
38
crates/brk_query/src/tree.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use brk_vec::AnyStorableVec;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use super::index::Index;
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
pub struct VecIdToIndexToVec<'a>(BTreeMap<String, IndexToVec<'a>>);
|
||||
|
||||
impl<'a> VecIdToIndexToVec<'a> {
|
||||
// Not the most performant or type safe but only built once so that's okay
|
||||
pub fn insert(&mut self, vec: &'a dyn AnyStorableVec) {
|
||||
let file_name = vec.file_name();
|
||||
let split = file_name.split("_to_").collect::<Vec<_>>();
|
||||
if split.len() != 2 {
|
||||
panic!();
|
||||
}
|
||||
let str = vec.index_type_to_string().split("::").last().unwrap().to_lowercase();
|
||||
let index = Index::try_from(str.as_str())
|
||||
.inspect_err(|_| {
|
||||
dbg!(str);
|
||||
})
|
||||
.unwrap();
|
||||
if split[0] != index.to_string().to_lowercase() {
|
||||
dbg!(split[0], index.to_string());
|
||||
panic!();
|
||||
}
|
||||
let key = split[1].to_string().replace("_", "-");
|
||||
let prev = self.entry(key).or_default().insert(index, vec);
|
||||
if prev.is_some() {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
pub struct IndexToVec<'a>(BTreeMap<Index, &'a dyn AnyStorableVec>);
|
||||
@@ -13,6 +13,7 @@ brk_exit = { workspace = true }
|
||||
brk_indexer = { workspace = true }
|
||||
brk_logger = { workspace = true }
|
||||
brk_parser = { workspace = true }
|
||||
brk_query = { workspace = true }
|
||||
brk_vec = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
derive_deref = { workspace = true }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use axum::{routing::get, Router};
|
||||
use axum::{Router, routing::get};
|
||||
|
||||
use super::AppState;
|
||||
|
||||
mod explorer;
|
||||
mod vecs;
|
||||
|
||||
pub use vecs::VecIdToIndexToVec;
|
||||
pub use vecs::DTS;
|
||||
|
||||
pub trait ApiRoutes {
|
||||
fn add_api_routes(self) -> Self;
|
||||
|
||||
50
crates/brk_server/src/api/vecs/dts.rs
Normal file
50
crates/brk_server/src/api/vecs/dts.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::{fs, io};
|
||||
|
||||
use brk_query::{Index, Query};
|
||||
|
||||
use crate::WEBSITE_DEV_PATH;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub trait DTS {
|
||||
fn generate_dts_file(&self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl DTS for Query<'static> {
|
||||
fn generate_dts_file(&self) -> io::Result<()> {
|
||||
if !fs::exists(WEBSITE_DEV_PATH)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = format!("{WEBSITE_DEV_PATH}/scripts/types/vecid-to-indexes.d.ts");
|
||||
|
||||
let mut contents = Index::all()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i_of_i, i)| format!("type {} = {};", i, i_of_i))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
contents += "\n\ninterface VecIdToIndexes {\n";
|
||||
|
||||
self.vecid_to_index_to_vec.iter().for_each(|(id, index_to_vec)| {
|
||||
let indexes = index_to_vec
|
||||
.keys()
|
||||
.map(|i| i.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
contents += &format!(
|
||||
" {}: [{indexes}]\n",
|
||||
if id.contains("-") {
|
||||
format!("\"{id}\"")
|
||||
} else {
|
||||
id.to_owned()
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
contents.push('}');
|
||||
|
||||
fs::write(path, contents)
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Index {
|
||||
Addressindex,
|
||||
Dateindex,
|
||||
Height,
|
||||
P2PK33index,
|
||||
P2PK65index,
|
||||
P2PKHindex,
|
||||
P2SHindex,
|
||||
P2TRindex,
|
||||
P2WPKHindex,
|
||||
P2WSHindex,
|
||||
Txindex,
|
||||
Txinindex,
|
||||
Txoutindex,
|
||||
}
|
||||
|
||||
impl Index {
|
||||
pub fn all() -> [Self; 13] {
|
||||
[
|
||||
Self::Addressindex,
|
||||
Self::Dateindex,
|
||||
Self::Height,
|
||||
Self::P2PK33index,
|
||||
Self::P2PK65index,
|
||||
Self::P2PKHindex,
|
||||
Self::P2SHindex,
|
||||
Self::P2TRindex,
|
||||
Self::P2WPKHindex,
|
||||
Self::P2WSHindex,
|
||||
Self::Txindex,
|
||||
Self::Txinindex,
|
||||
Self::Txoutindex,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Index {
|
||||
type Error = ();
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
"d" | "date" | "dateindex" => Self::Dateindex,
|
||||
"h" | "height" => Self::Height,
|
||||
"txi" | "txindex" => Self::Txindex,
|
||||
"txini" | "txinindex" => Self::Txinindex,
|
||||
"txouti" | "txoutindex" => Self::Txoutindex,
|
||||
"addri" | "addressindex" => Self::Addressindex,
|
||||
"p2pk33i" | "p2pk33index" => Self::P2PK33index,
|
||||
"p2pk65i" | "p2pk65index" => Self::P2PK65index,
|
||||
"p2pkhi" | "p2pkhindex" => Self::P2PKHindex,
|
||||
"p2shi" | "p2shindex" => Self::P2SHindex,
|
||||
"p2tri" | "p2trindex" => Self::P2TRindex,
|
||||
"p2wpkhi" | "p2wpkhindex" => Self::P2WPKHindex,
|
||||
"p2wshi" | "p2wshindex" => Self::P2WSHindex,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Index {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,11 @@ use std::time::Instant;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
extract::{Query as AxumQuery, State},
|
||||
http::{HeaderMap, StatusCode, Uri},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use brk_query::{Format, Index, Params};
|
||||
use color_eyre::eyre::eyre;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -13,20 +14,14 @@ use crate::{log_result, traits::HeaderMapExtended};
|
||||
|
||||
use super::AppState;
|
||||
|
||||
mod format;
|
||||
mod index;
|
||||
mod query;
|
||||
mod tree;
|
||||
mod dts;
|
||||
|
||||
use format::Format;
|
||||
use index::Index;
|
||||
use query::QueryS;
|
||||
pub use tree::*;
|
||||
pub use dts::*;
|
||||
|
||||
pub async fn handler(
|
||||
headers: HeaderMap,
|
||||
uri: Uri,
|
||||
query: Query<QueryS>,
|
||||
query: AxumQuery<Params>,
|
||||
State(app_state): State<AppState>,
|
||||
) -> Response {
|
||||
let instant = Instant::now();
|
||||
@@ -49,12 +44,16 @@ pub async fn handler(
|
||||
|
||||
fn req_to_response_res(
|
||||
headers: HeaderMap,
|
||||
Query(QueryS { format, from, i, to, v }): Query<QueryS>,
|
||||
AppState { vecs, .. }: AppState,
|
||||
AxumQuery(Params {
|
||||
format,
|
||||
from,
|
||||
index,
|
||||
to,
|
||||
values,
|
||||
}): AxumQuery<Params>,
|
||||
AppState { query, .. }: AppState,
|
||||
) -> color_eyre::Result<Response> {
|
||||
let format = Format::try_from(format).ok();
|
||||
|
||||
let indexes = i
|
||||
let indexes = index
|
||||
.to_lowercase()
|
||||
.split(",")
|
||||
.flat_map(|s| Index::try_from(s).ok())
|
||||
@@ -66,10 +65,14 @@ fn req_to_response_res(
|
||||
return Err(eyre!("Unknown index"));
|
||||
}
|
||||
|
||||
let ids = v
|
||||
.to_lowercase()
|
||||
.split(",")
|
||||
.map(|s| (s.to_owned(), vecs.get(&s.replace("_", "-"))))
|
||||
let ids = values
|
||||
.into_iter()
|
||||
.map(|v| v.to_lowercase())
|
||||
.flat_map(|v| v.split(",").map(|v| v.to_owned()).collect::<Vec<_>>())
|
||||
.map(|s| {
|
||||
let opt = query.vecid_to_index_to_vec.get(&s.replace("_", "-"));
|
||||
(s, opt)
|
||||
})
|
||||
.filter(|(_, opt)| opt.is_some())
|
||||
.map(|(id, vec)| (id, vec.unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct QueryS {
|
||||
pub i: String,
|
||||
pub v: String,
|
||||
pub from: Option<i64>,
|
||||
pub to: Option<i64>,
|
||||
pub format: Option<String>,
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
use std::{collections::BTreeMap, fs, io};
|
||||
|
||||
use brk_vec::AnyStorableVec;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use crate::WEBSITE_DEV_PATH;
|
||||
|
||||
use super::index::Index;
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
pub struct VecIdToIndexToVec(BTreeMap<String, IndexToVec>);
|
||||
|
||||
impl VecIdToIndexToVec {
|
||||
// Not the most performant or type safe but only built once so that's okay
|
||||
pub fn insert(&mut self, vec: &'static dyn AnyStorableVec) {
|
||||
let file_name = vec.file_name();
|
||||
let split = file_name.split("_to_").collect::<Vec<_>>();
|
||||
if split.len() != 2 {
|
||||
panic!();
|
||||
}
|
||||
let str = vec.index_type_to_string().split("::").last().unwrap().to_lowercase();
|
||||
let index = Index::try_from(str.as_str())
|
||||
.inspect_err(|_| {
|
||||
dbg!(str);
|
||||
})
|
||||
.unwrap();
|
||||
if split[0] != index.to_string().to_lowercase() {
|
||||
dbg!(split[0], index.to_string());
|
||||
panic!();
|
||||
}
|
||||
let key = split[1].to_string().replace("_", "-");
|
||||
let prev = self.entry(key).or_default().insert(index, vec);
|
||||
if prev.is_some() {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_dts_file(&self) -> io::Result<()> {
|
||||
if !fs::exists(WEBSITE_DEV_PATH)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = format!("{WEBSITE_DEV_PATH}/scripts/types/vecid-to-indexes.d.ts");
|
||||
|
||||
let mut contents = Index::all()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i_of_i, i)| format!("type {} = {};", i, i_of_i))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
contents += "\n\ninterface VecIdToIndexes {\n";
|
||||
|
||||
self.iter().for_each(|(id, index_to_vec)| {
|
||||
let indexes = index_to_vec
|
||||
.keys()
|
||||
.map(|i| i.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
contents += &format!(
|
||||
" {}: [{indexes}]\n",
|
||||
if id.contains("-") {
|
||||
format!("\"{id}\"")
|
||||
} else {
|
||||
id.to_owned()
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
contents.push('}');
|
||||
|
||||
fs::write(path, contents)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
pub struct IndexToVec(BTreeMap<Index, &'static dyn AnyStorableVec>);
|
||||
@@ -5,13 +5,15 @@
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use api::{ApiRoutes, VecIdToIndexToVec};
|
||||
use api::{ApiRoutes, DTS};
|
||||
use axum::{Json, Router, http::StatusCode, routing::get, serve};
|
||||
use brk_computer::Computer;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_query::Query;
|
||||
use color_eyre::owo_colors::OwoColorize;
|
||||
use files::FilesRoutes;
|
||||
use log::{error, info};
|
||||
pub use tokio;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
@@ -21,9 +23,7 @@ mod traits;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
vecs: &'static VecIdToIndexToVec,
|
||||
indexer: &'static Indexer,
|
||||
computer: &'static Computer,
|
||||
query: &'static Query<'static>,
|
||||
}
|
||||
|
||||
pub const WEBSITE_DEV_PATH: &str = "../../websites/kibo.money/";
|
||||
@@ -31,18 +31,11 @@ pub const WEBSITE_DEV_PATH: &str = "../../websites/kibo.money/";
|
||||
pub async fn main(indexer: Indexer, computer: Computer) -> color_eyre::Result<()> {
|
||||
let indexer = Box::leak(Box::new(indexer));
|
||||
let computer = Box::leak(Box::new(computer));
|
||||
let vecs = Box::leak(Box::new(VecIdToIndexToVec::default()));
|
||||
let query = Box::leak(Box::new(Query::build(indexer, computer)));
|
||||
|
||||
indexer.vecs.as_any_vecs().into_iter().for_each(|vec| vecs.insert(vec));
|
||||
computer.vecs.as_any_vecs().into_iter().for_each(|vec| vecs.insert(vec));
|
||||
query.generate_dts_file()?;
|
||||
|
||||
vecs.generate_dts_file()?;
|
||||
|
||||
let state = AppState {
|
||||
vecs,
|
||||
indexer,
|
||||
computer,
|
||||
};
|
||||
let state = AppState { query };
|
||||
|
||||
let compression_layer = CompressionLayer::new().br(true).deflate(true).gzip(true).zstd(true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user