This commit is contained in:
2026-05-05 05:03:11 +02:00
parent 3e994995d7
commit f2c227e08f
132 changed files with 5767 additions and 4461 deletions

View File

@@ -15,7 +15,7 @@ pub async fn insert_analysis_signal(
"cannot serialize analysis signal payload: {}",
error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -49,10 +49,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_analysis_signals on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -92,7 +92,7 @@ LIMIT ?
"cannot list analysis signals on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -103,8 +103,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -58,10 +58,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_chain_instructions on sqlite: {}",
error
)));
}
},
};
Ok(insert_result.last_insert_rowid())
}
return Ok(insert_result.last_insert_rowid());
},
}
}
@@ -103,7 +103,7 @@ ORDER BY instruction_index ASC, inner_instruction_index ASC, id ASC
"cannot list kb_chain_instructions for transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -114,8 +114,8 @@ ORDER BY instruction_index ASC, inner_instruction_index ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -141,8 +141,8 @@ WHERE transaction_id = ?
transaction_id, error
)));
}
Ok(())
}
return Ok(());
},
}
}
@@ -163,9 +163,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
async fn make_transaction(database: &crate::KbDatabase) -> i64 {
@@ -179,9 +179,9 @@ mod tests {
None,
r#"{"transaction":{"message":{"instructions":[]}}}"#.to_string(),
);
crate::upsert_chain_transaction(database, &dto)
return crate::upsert_chain_transaction(database, &dto)
.await
.expect("chain transaction upsert must succeed")
.expect("chain transaction upsert must succeed");
}
#[tokio::test]

View File

@@ -15,7 +15,7 @@ pub async fn upsert_chain_slot(
"cannot convert chain slot '{}' to i64: {}",
dto.slot, error
)));
}
},
};
let parent_slot = match dto.parent_slot {
Some(parent_slot) => {
@@ -27,9 +27,9 @@ pub async fn upsert_chain_slot(
"cannot convert chain parent_slot '{}' to i64: {}",
parent_slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -63,8 +63,8 @@ ON CONFLICT(slot) DO UPDATE SET
error
)));
}
Ok(dto.slot)
}
return Ok(dto.slot);
},
}
}
@@ -81,7 +81,7 @@ pub async fn get_chain_slot(
"cannot convert requested chain slot '{}' to i64: {}",
slot, error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -108,19 +108,19 @@ LIMIT 1
"cannot fetch kb_chain_slots for slot '{}' on sqlite: {}",
slot, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbChainSlotDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -157,7 +157,7 @@ LIMIT ?
"cannot list kb_chain_slots on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -168,8 +168,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -190,9 +190,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]

View File

@@ -17,9 +17,9 @@ pub async fn upsert_chain_transaction(
"cannot convert chain transaction slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -80,13 +80,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_chain_transactions id for signature '{}' on sqlite: {}",
dto.signature, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_chain_transactions id for signature '{}' on sqlite: {}",
dto.signature, error
)));
},
}
}
},
}
}
@@ -126,19 +128,19 @@ LIMIT 1
"cannot fetch kb_chain_transactions for signature '{}' on sqlite: {}",
signature, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbChainTransactionDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -181,7 +183,7 @@ LIMIT ?
"cannot list kb_chain_transactions on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -192,8 +194,49 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
/// Lists persisted chain transaction signatures for local pipeline replay.
pub async fn list_chain_transaction_signatures_for_replay(
database: &crate::KbDatabase,
limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let effective_limit = match limit {
Some(limit) => {
if limit <= 0 {
10_000
} else {
limit
}
},
None => 10_000,
};
let query_result = sqlx::query_scalar::<sqlx::Sqlite, std::string::String>(
r#"
SELECT signature
FROM kb_chain_transactions
ORDER BY id ASC
LIMIT ?
"#,
)
.bind(effective_limit)
.fetch_all(pool)
.await;
match query_result {
Ok(signatures) => return Ok(signatures),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot list kb_chain_transactions signatures for local replay on sqlite: {}",
error
)));
},
}
},
}
}
@@ -214,9 +257,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]
@@ -249,10 +292,7 @@ mod tests {
assert_eq!(fetched.signature, "sig-chain-transaction-1");
assert_eq!(fetched.slot, Some(515151));
assert_eq!(fetched.block_time_unix, Some(1_700_000_001));
assert_eq!(
fetched.source_endpoint_name,
Some("helius_primary_http".to_string())
);
assert_eq!(fetched.source_endpoint_name, Some("helius_primary_http".to_string()));
assert_eq!(fetched.version_text, Some("0".to_string()));
assert_eq!(fetched.meta_json, Some(r#"{"fee":5000}"#.to_string()));
let listed = crate::list_recent_chain_transactions(&database, 10)

View File

@@ -29,13 +29,15 @@ ON CONFLICT(key) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
"cannot upsert kb_db_metadata on sqlite: {}",
error
))),
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_db_metadata on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -67,19 +69,19 @@ LIMIT 1
"cannot read kb_db_metadata '{}' on sqlite: {}",
key, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDbMetadataDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -108,7 +110,7 @@ ORDER BY key ASC
"cannot list kb_db_metadata on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -119,8 +121,8 @@ ORDER BY key ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -146,9 +148,7 @@ mod tests {
.await
.expect("database init must succeed");
let dto = crate::KbDbMetadataDto::new("schema_version".to_string(), "0.5.0".to_string());
crate::upsert_db_metadata(&database, &dto)
.await
.expect("upsert must succeed");
crate::upsert_db_metadata(&database, &dto).await.expect("upsert must succeed");
let fetched = crate::get_db_metadata(&database, "schema_version")
.await
.expect("fetch must succeed");
@@ -156,9 +156,7 @@ mod tests {
let fetched = fetched.expect("metadata must exist");
assert_eq!(fetched.key, "schema_version");
assert_eq!(fetched.value, "0.5.0");
let listed = crate::list_db_metadata(&database)
.await
.expect("list must succeed");
let listed = crate::list_db_metadata(&database).await.expect("list must succeed");
assert!(!listed.is_empty());
}
}

View File

@@ -35,10 +35,10 @@ VALUES (?, ?, ?, ?, ?)
"cannot insert kb_db_runtime_events on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -76,7 +76,7 @@ LIMIT ?
"cannot list runtime events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -87,8 +87,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -56,13 +56,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_dexes id for code '{}' on sqlite: {}",
dto.code, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_dexes id for code '{}' on sqlite: {}",
dto.code, error
)));
},
}
}
},
}
}
@@ -99,19 +101,19 @@ LIMIT 1
"cannot read kb_dexes '{}' on sqlite: {}",
code, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDexDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -145,7 +147,7 @@ ORDER BY code ASC
"cannot list kb_dexes on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -156,8 +158,8 @@ ORDER BY code ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -184,13 +186,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -200,9 +196,7 @@ mod tests {
.expect("get dex must succeed");
assert!(dex.is_some());
assert_eq!(dex.expect("dex must exist").name, "Raydium");
let dexes = crate::list_dexes(&database)
.await
.expect("list dexes must succeed");
let dexes = crate::list_dexes(&database).await.expect("list dexes must succeed");
assert_eq!(dexes.len(), 1);
}
}

View File

@@ -77,13 +77,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_dex_decoded_events id on sqlite: {}",
error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_dex_decoded_events id on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -135,19 +137,19 @@ LIMIT 1
"cannot fetch kb_dex_decoded_events on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDexDecodedEventDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -189,7 +191,7 @@ ORDER BY id ASC
"cannot list kb_dex_decoded_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -200,8 +202,48 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
/// Returns the latest Pump.fun create payload associated with a token mint.
pub async fn get_latest_pump_fun_create_payload_by_mint(
database: &crate::KbDatabase,
mint: &str,
) -> Result<std::option::Option<std::string::String>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let payload_result = sqlx::query_scalar::<sqlx::Sqlite, std::string::String>(
r#"
SELECT payload_json
FROM kb_dex_decoded_events
WHERE protocol_name = 'pump_fun'
AND event_kind IN ('pump_fun.create', 'pump_fun.create_v2_token')
AND (
token_a_mint = ?
OR json_extract(payload_json, '$.mint') = ?
OR json_extract(payload_json, '$.tokenMint') = ?
)
ORDER BY id DESC
LIMIT 1
"#,
)
.bind(mint)
.bind(mint)
.bind(mint)
.fetch_optional(pool)
.await;
match payload_result {
Ok(payload_option) => return Ok(payload_option),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot read latest pump.fun create payload for mint '{}' on sqlite: {}",
mint, error
)));
},
}
},
}
}
@@ -228,7 +270,7 @@ mod tests {
};
let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
match database_result {
Ok(database) => database,
Ok(database) => return database,
Err(error) => panic!("database init must succeed: {}", error),
}
}

View File

@@ -46,13 +46,15 @@ ON CONFLICT(name) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
"cannot upsert kb_known_http_endpoints on sqlite: {}",
error
))),
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_known_http_endpoints on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -88,19 +90,19 @@ LIMIT 1
"cannot read known http endpoint '{}' on sqlite: {}",
name, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbKnownHttpEndpointDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -133,7 +135,7 @@ ORDER BY name ASC
"cannot list known http endpoints on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -144,8 +146,8 @@ ORDER BY name ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -187,9 +189,7 @@ mod tests {
let fetched = fetched.expect("endpoint must exist");
assert_eq!(fetched.provider, "helius");
assert_eq!(fetched.roles.len(), 2);
let listed = crate::list_known_http_endpoints(&database)
.await
.expect("list must succeed");
let listed = crate::list_known_http_endpoints(&database).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -12,7 +12,6 @@ pub async fn upsert_known_ws_endpoint(
Ok(entity) => entity,
Err(error) => return Err(error),
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
@@ -46,13 +45,15 @@ ON CONFLICT(name) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
"cannot upsert kb_known_ws_endpoints on sqlite: {}",
error
))),
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_known_ws_endpoints on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -88,19 +89,19 @@ LIMIT 1
"cannot read known ws endpoint '{}' on sqlite: {}",
name, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbKnownWsEndpointDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -133,7 +134,7 @@ ORDER BY name ASC
"cannot list known ws endpoints on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -144,8 +145,8 @@ ORDER BY name ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -187,9 +188,7 @@ mod tests {
let fetched = fetched.expect("endpoint must exist");
assert_eq!(fetched.provider, "solana");
assert_eq!(fetched.roles.len(), 2);
let listed = crate::list_known_ws_endpoints(&database)
.await
.expect("list must succeed");
let listed = crate::list_known_ws_endpoints(&database).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -68,13 +68,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_attributions id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_attributions id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
)));
},
}
}
},
}
}
@@ -85,9 +87,8 @@ pub async fn get_launch_attribution_by_decoded_event_id(
) -> Result<std::option::Option<crate::KbLaunchAttributionDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -105,10 +106,10 @@ FROM kb_launch_attributions
WHERE decoded_event_id = ?
LIMIT 1
"#,
)
.bind(decoded_event_id)
.fetch_optional(pool)
.await;
)
.bind(decoded_event_id)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -116,13 +117,13 @@ LIMIT 1
"cannot read kb_launch_attributions by decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchAttributionDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbLaunchAttributionDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -133,9 +134,8 @@ pub async fn list_launch_attributions_by_pool_id(
) -> Result<std::vec::Vec<crate::KbLaunchAttributionDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -153,10 +153,10 @@ FROM kb_launch_attributions
WHERE pool_id = ?
ORDER BY attributed_at ASC, id ASC
"#,
)
.bind(pool_id)
.fetch_all(pool)
.await;
)
.bind(pool_id)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -164,7 +164,7 @@ ORDER BY attributed_at ASC, id ASC
"cannot list kb_launch_attributions by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -175,7 +175,7 @@ ORDER BY attributed_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -53,13 +53,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surfaces id for code '{}' on sqlite: {}",
dto.code, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surfaces id for code '{}' on sqlite: {}",
dto.code, error
)));
},
}
}
},
}
}
@@ -70,9 +72,8 @@ pub async fn get_launch_surface_by_code(
) -> Result<std::option::Option<crate::KbLaunchSurfaceDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
SELECT
id,
code,
@@ -85,10 +86,10 @@ FROM kb_launch_surfaces
WHERE code = ?
LIMIT 1
"#,
)
.bind(code)
.fetch_optional(pool)
.await;
)
.bind(code)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -96,13 +97,13 @@ LIMIT 1
"cannot read kb_launch_surfaces '{}' on sqlite: {}",
code, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchSurfaceDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbLaunchSurfaceDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -112,9 +113,8 @@ pub async fn list_launch_surfaces(
) -> Result<std::vec::Vec<crate::KbLaunchSurfaceDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
SELECT
id,
code,
@@ -126,9 +126,9 @@ SELECT
FROM kb_launch_surfaces
ORDER BY code ASC
"#,
)
.fetch_all(pool)
.await;
)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -136,7 +136,7 @@ ORDER BY code ASC
"cannot list kb_launch_surfaces on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -147,7 +147,7 @@ ORDER BY code ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -50,13 +50,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surface_keys id for '{}:{}' on sqlite: {}",
dto.match_kind, dto.match_value, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surface_keys id for '{}:{}' on sqlite: {}",
dto.match_kind, dto.match_value, error
)));
},
}
}
},
}
}
@@ -68,9 +70,8 @@ pub async fn get_launch_surface_key_by_match(
) -> Result<std::option::Option<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -82,11 +83,11 @@ FROM kb_launch_surface_keys
WHERE match_kind = ? AND match_value = ?
LIMIT 1
"#,
)
.bind(match_kind)
.bind(match_value)
.fetch_optional(pool)
.await;
)
.bind(match_kind)
.bind(match_value)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -94,13 +95,13 @@ LIMIT 1
"cannot read kb_launch_surface_keys '{}:{}' on sqlite: {}",
match_kind, match_value, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -111,9 +112,8 @@ pub async fn list_launch_surface_keys_by_surface_id(
) -> Result<std::vec::Vec<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
SELECT
id,
launch_surface_id,
@@ -125,10 +125,10 @@ FROM kb_launch_surface_keys
WHERE launch_surface_id = ?
ORDER BY match_kind ASC, match_value ASC
"#,
)
.bind(launch_surface_id)
.fetch_all(pool)
.await;
)
.bind(launch_surface_id)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -136,7 +136,7 @@ ORDER BY match_kind ASC, match_value ASC
"cannot list kb_launch_surface_keys by surface_id '{}' on sqlite: {}",
launch_surface_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -147,7 +147,7 @@ ORDER BY match_kind ASC, match_value ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -17,9 +17,9 @@ pub async fn upsert_liquidity_event(
"cannot convert liquidity event slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -96,13 +96,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_liquidity_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_liquidity_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -150,7 +152,7 @@ LIMIT ?
"cannot list kb_liquidity_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -161,7 +163,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -63,13 +63,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match select_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_observed_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_observed_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
)));
},
}
}
},
}
}
@@ -108,19 +110,19 @@ LIMIT 1
"cannot read observed token '{}' on sqlite: {}",
mint, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbObservedTokenDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -162,7 +164,7 @@ LIMIT ?
"cannot list observed tokens on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -173,8 +175,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -222,9 +224,7 @@ mod tests {
assert_eq!(fetched.symbol.as_deref(), Some("WSOL"));
assert_eq!(fetched.decimals, Some(9));
assert_eq!(fetched.status, crate::KbObservedTokenStatus::Active);
let listed = crate::list_observed_tokens(&database, 10)
.await
.expect("list must succeed");
let listed = crate::list_observed_tokens(&database, 10).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -15,7 +15,7 @@ pub async fn insert_onchain_observation(
"cannot serialize on-chain observation payload: {}",
error
)));
}
},
};
let slot_i64 = match dto.slot {
Some(slot) => {
@@ -27,9 +27,9 @@ pub async fn insert_onchain_observation(
"cannot convert on-chain observation slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -64,10 +64,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_onchain_observations on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -107,7 +107,7 @@ LIMIT ?
"cannot list on-chain observations on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -118,8 +118,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -25,7 +25,11 @@ ON CONFLICT(pool_id) DO UPDATE SET
dex_id = excluded.dex_id,
base_token_id = excluded.base_token_id,
quote_token_id = excluded.quote_token_id,
symbol = excluded.symbol,
symbol = CASE
WHEN excluded.symbol IS NOT NULL AND trim(excluded.symbol) <> ''
THEN excluded.symbol
ELSE kb_pairs.symbol
END,
updated_at = excluded.updated_at
"#,
)
@@ -56,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pairs id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pairs id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
)));
},
}
}
},
}
}
@@ -99,19 +105,54 @@ LIMIT 1
"cannot read kb_pairs by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPairDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
/// Updates the display symbol of one normalized pair row.
pub async fn update_pair_symbol(
database: &crate::KbDatabase,
pair_id: i64,
symbol: std::option::Option<&str>,
) -> Result<bool, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
r#"
UPDATE kb_pairs
SET
symbol = ?,
updated_at = ?
WHERE id = ?
"#,
)
.bind(symbol)
.bind(chrono::Utc::now().to_rfc3339())
.bind(pair_id)
.execute(pool)
.await;
match query_result {
Ok(done) => return Ok(done.rows_affected() > 0),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot update kb_pairs symbol for id '{}' on sqlite: {}",
pair_id, error
)));
},
}
},
}
}
@@ -145,7 +186,7 @@ ORDER BY id ASC
"cannot list kb_pairs on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -156,8 +197,8 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -184,13 +225,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -248,13 +283,8 @@ mod tests {
.await
.expect("get pair must succeed");
assert!(pair.is_some());
assert_eq!(
pair.expect("pair must exist").symbol.as_deref(),
Some("BASE/WSOL")
);
let pairs = crate::list_pairs(&database)
.await
.expect("list pairs must succeed");
assert_eq!(pair.expect("pair must exist").symbol.as_deref(), Some("BASE/WSOL"));
let pairs = crate::list_pairs(&database).await.expect("list pairs must succeed");
assert_eq!(pairs.len(), 1);
}
}

View File

@@ -15,7 +15,7 @@ pub async fn upsert_pair_analytic_signal(
"cannot serialize pair analytic signal payload: {}",
error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -77,13 +77,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_analytic_signals id on sqlite: {}",
error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_analytic_signals id on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -97,9 +99,8 @@ pub async fn get_pair_analytic_signal_by_key(
) -> Result<std::option::Option<crate::KbPairAnalyticSignalDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
SELECT
id,
pair_id,
@@ -117,13 +118,13 @@ FROM kb_pair_analytic_signals
WHERE pair_id = ? AND signal_kind = ? AND timeframe_seconds = ? AND bucket_start_unix = ?
LIMIT 1
"#,
)
.bind(pair_id)
.bind(signal_kind)
.bind(timeframe_seconds)
.bind(bucket_start_unix)
.fetch_optional(pool)
.await;
)
.bind(pair_id)
.bind(signal_kind)
.bind(timeframe_seconds)
.bind(bucket_start_unix)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
@@ -131,13 +132,13 @@ LIMIT 1
"cannot read kb_pair_analytic_signals by key on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairAnalyticSignalDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPairAnalyticSignalDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -148,9 +149,8 @@ pub async fn list_pair_analytic_signals_by_pair_id(
) -> Result<std::vec::Vec<crate::KbPairAnalyticSignalDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
SELECT
id,
pair_id,
@@ -168,10 +168,10 @@ FROM kb_pair_analytic_signals
WHERE pair_id = ?
ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
"#,
)
.bind(pair_id)
.fetch_all(pool)
.await;
)
.bind(pair_id)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
@@ -179,7 +179,7 @@ ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
"cannot list kb_pair_analytic_signals by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -190,7 +190,7 @@ ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -87,13 +87,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_candles id for pair_id '{}' timeframe '{}' bucket '{}' on sqlite: {}",
dto.pair_id, dto.timeframe_seconds, dto.bucket_start_unix, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_candles id for pair_id '{}' timeframe '{}' bucket '{}' on sqlite: {}",
dto.pair_id, dto.timeframe_seconds, dto.bucket_start_unix, error
)));
},
}
}
},
}
}
@@ -144,13 +146,13 @@ LIMIT 1
"cannot read kb_pair_candles by key on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairCandleDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPairCandleDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -199,7 +201,7 @@ ORDER BY bucket_start_unix ASC, id ASC
"cannot list kb_pair_candles by pair_id '{}' timeframe '{}' on sqlite: {}",
pair_id, timeframe_seconds, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -210,7 +212,7 @@ ORDER BY bucket_start_unix ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -74,13 +74,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_metrics id for pair_id '{}' on sqlite: {}",
dto.pair_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_metrics id for pair_id '{}' on sqlite: {}",
dto.pair_id, error
)));
},
}
}
},
}
}
@@ -123,13 +125,13 @@ LIMIT 1
"cannot read kb_pair_metrics by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairMetricDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPairMetricDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -169,7 +171,7 @@ ORDER BY pair_id ASC
"cannot list kb_pair_metrics on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -180,7 +182,7 @@ ORDER BY pair_id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -53,13 +53,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pools id for address '{}' on sqlite: {}",
dto.address, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pools id for address '{}' on sqlite: {}",
dto.address, error
)));
},
}
}
},
}
}
@@ -95,19 +97,19 @@ LIMIT 1
"cannot read kb_pools '{}' on sqlite: {}",
address, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPoolDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -140,7 +142,7 @@ ORDER BY id ASC
"cannot list kb_pools on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -151,8 +153,8 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -179,13 +181,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -206,13 +202,8 @@ mod tests {
.await
.expect("get pool must succeed");
assert!(pool.is_some());
assert_eq!(
pool.expect("pool must exist").pool_kind,
crate::KbPoolKind::Amm
);
let pools = crate::list_pools(&database)
.await
.expect("list pools must succeed");
assert_eq!(pool.expect("pool must exist").pool_kind, crate::KbPoolKind::Amm);
let pools = crate::list_pools(&database).await.expect("list pools must succeed");
assert_eq!(pools.len(), 1);
}
}

View File

@@ -66,13 +66,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_listings id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_listings id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
)));
},
}
}
},
}
}
@@ -112,19 +114,19 @@ LIMIT 1
"cannot read kb_pool_listings by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPoolListingDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
@@ -161,7 +163,7 @@ ORDER BY detected_at ASC, id ASC
"cannot list kb_pool_listings on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -172,8 +174,8 @@ ORDER BY detected_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -200,13 +202,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");

View File

@@ -69,13 +69,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_origins id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_origins id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
)));
},
}
}
},
}
}
@@ -119,13 +121,13 @@ LIMIT 1
"cannot read kb_pool_origins by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPoolOriginDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbPoolOriginDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -166,7 +168,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_pool_origins on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -177,7 +179,7 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -56,13 +56,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_tokens id on sqlite: {}",
error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_tokens id on sqlite: {}",
error
)));
},
}
}
},
}
}
@@ -99,7 +101,7 @@ ORDER BY token_order ASC, id ASC
"cannot list kb_pool_tokens for pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -110,8 +112,8 @@ ORDER BY token_order ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -138,13 +140,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");

View File

@@ -17,9 +17,9 @@ pub async fn upsert_swap(
"cannot convert swap slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -93,13 +93,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_swaps id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_swaps id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -146,7 +148,7 @@ LIMIT ?
"cannot list kb_swaps on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -157,7 +159,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
)));
},
}
}
},
}
}
@@ -104,22 +106,71 @@ LIMIT 1
"cannot read kb_tokens '{}' on sqlite: {}",
mint, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbTokenDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
}
None => Ok(None),
},
None => return Ok(None),
}
}
},
}
}
/// Reads one normalized token row by internal id.
pub async fn get_token_by_id(
database: &crate::KbDatabase,
token_id: i64,
) -> Result<std::option::Option<crate::KbTokenDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE id = ?
LIMIT 1
"#,
)
.bind(token_id)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot read kb_tokens id '{}' on sqlite: {}",
token_id, error
)));
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbTokenDto::try_from(entity);
match dto_result {
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
/// Lists all normalized token rows ordered by mint.
pub async fn list_tokens(
@@ -152,7 +203,7 @@ ORDER BY mint ASC, id ASC
"cannot list kb_tokens on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -163,7 +214,161 @@ ORDER BY mint ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
}
/// Lists token rows whose display or mint metadata is incomplete.
pub async fn list_tokens_missing_metadata(
database: &crate::KbDatabase,
limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<crate::KbTokenDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let entities_result = match limit {
Some(limit) => {
sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE symbol IS NULL
OR trim(symbol) = ''
OR name IS NULL
OR trim(name) = ''
OR decimals IS NULL
OR token_program IS NULL
OR trim(token_program) = ''
ORDER BY updated_at ASC, id ASC
LIMIT ?
"#,
)
.bind(limit)
.fetch_all(pool)
.await
},
None => {
sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE symbol IS NULL
OR trim(symbol) = ''
OR name IS NULL
OR trim(name) = ''
OR decimals IS NULL
OR token_program IS NULL
OR trim(token_program) = ''
ORDER BY updated_at ASC, id ASC
"#,
)
.fetch_all(pool)
.await
},
};
let entities = match entities_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot list kb_tokens missing metadata on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
let dto_result = crate::KbTokenDto::try_from(entity);
let dto = match dto_result {
Ok(dto) => dto,
Err(error) => return Err(error),
};
dtos.push(dto);
}
return Ok(dtos);
},
}
}
#[cfg(test)]
mod tests {
async fn make_database() -> std::sync::Arc<crate::KbDatabase> {
let tempdir_result = tempfile::tempdir();
let tempdir = match tempdir_result {
Ok(tempdir) => tempdir,
Err(error) => panic!("tempdir must succeed: {}", error),
};
let database_path = tempdir.path().join("token_query.sqlite3");
let config = crate::KbDatabaseConfig {
enabled: true,
backend: crate::KbDatabaseBackend::Sqlite,
sqlite: crate::KbSqliteDatabaseConfig {
path: database_path.to_string_lossy().to_string(),
create_if_missing: true,
busy_timeout_ms: 5000,
max_connections: 1,
auto_initialize_schema: true,
use_wal: true,
},
};
let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
let database = match database_result {
Ok(database) => database,
Err(error) => panic!("database init must succeed: {}", error),
};
return std::sync::Arc::new(database);
}
#[tokio::test]
async fn list_tokens_missing_metadata_only_returns_incomplete_rows() {
let database = make_database().await;
let incomplete = crate::KbTokenDto::new(
"IncompleteMint111".to_string(),
None,
None,
None,
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let complete = crate::KbTokenDto::new(
"CompleteMint111".to_string(),
Some("CMP".to_string()),
Some("Complete".to_string()),
Some(6),
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let incomplete_result = crate::upsert_token(database.as_ref(), &incomplete).await;
if let Err(error) = incomplete_result {
panic!("incomplete token upsert must succeed: {}", error);
}
let complete_result = crate::upsert_token(database.as_ref(), &complete).await;
if let Err(error) = complete_result {
panic!("complete token upsert must succeed: {}", error);
}
let missing_result = crate::list_tokens_missing_metadata(database.as_ref(), None).await;
let missing = match missing_result {
Ok(missing) => missing,
Err(error) => panic!("missing metadata list must succeed: {}", error),
};
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].mint, "IncompleteMint111");
}
}

View File

@@ -17,9 +17,9 @@ pub async fn upsert_token_burn_event(
"cannot convert token burn event slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_token_burn_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_token_burn_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -126,7 +128,7 @@ LIMIT ?
"cannot list kb_token_burn_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -137,8 +139,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -165,13 +167,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -299,9 +295,7 @@ mod tests {
assert!(liquidity_id > 0);
assert!(mint_id > 0);
assert!(burn_id > 0);
let swaps = crate::list_recent_swaps(&database, 10)
.await
.expect("swaps list must succeed");
let swaps = crate::list_recent_swaps(&database, 10).await.expect("swaps list must succeed");
let liquidity_events = crate::list_recent_liquidity_events(&database, 10)
.await
.expect("liquidity list must succeed");

View File

@@ -17,9 +17,9 @@ pub async fn upsert_token_mint_event(
"cannot convert token mint event slot '{}' to i64: {}",
slot, error
)));
}
},
}
}
},
None => None,
};
match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_token_mint_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_token_mint_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
)));
},
}
}
},
}
}
@@ -126,7 +128,7 @@ LIMIT ?
"cannot list kb_token_mint_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -137,7 +139,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -90,13 +90,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_trade_events id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_trade_events id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
)));
},
}
}
},
}
}
@@ -144,13 +146,13 @@ LIMIT 1
"cannot read kb_trade_events by decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbTradeEventDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbTradeEventDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -198,7 +200,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_trade_events by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -209,8 +211,8 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -258,7 +260,7 @@ ORDER BY id ASC
"cannot list kb_trade_events by transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -269,15 +271,15 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
fn kb_trade_side_to_string(value: crate::KbSwapTradeSide) -> &'static str {
match value {
crate::KbSwapTradeSide::BuyBase => "BuyBase",
crate::KbSwapTradeSide::SellBase => "SellBase",
crate::KbSwapTradeSide::Unknown => "Unknown",
crate::KbSwapTradeSide::BuyBase => return "BuyBase",
crate::KbSwapTradeSide::SellBase => return "SellBase",
crate::KbSwapTradeSide::Unknown => return "Unknown",
}
}

View File

@@ -47,13 +47,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_wallets id for address '{}' on sqlite: {}",
dto.address, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallets id for address '{}' on sqlite: {}",
dto.address, error
)));
},
}
}
},
}
}
@@ -87,13 +89,13 @@ LIMIT 1
"cannot read kb_wallets '{}' on sqlite: {}",
address, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbWalletDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -124,7 +126,7 @@ ORDER BY address ASC
"cannot list kb_wallets on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -135,7 +137,7 @@ ORDER BY address ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -76,13 +76,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_holdings id for wallet_id '{}' token_id '{}' on sqlite: {}",
dto.wallet_id, dto.token_id, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_holdings id for wallet_id '{}' token_id '{}' on sqlite: {}",
dto.wallet_id, dto.token_id, error
)));
},
}
}
},
}
}
@@ -128,13 +130,13 @@ LIMIT 1
"cannot read kb_wallet_holdings by wallet_id '{}' token_id '{}' on sqlite: {}",
wallet_id, token_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletHoldingDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbWalletHoldingDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -178,7 +180,7 @@ ORDER BY token_id ASC, id ASC
"cannot list kb_wallet_holdings by wallet_id '{}' on sqlite: {}",
wallet_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -189,7 +191,7 @@ ORDER BY token_id ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_participations id for unique_key '{}' on sqlite: {}",
dto.unique_key, error
))),
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_participations id for unique_key '{}' on sqlite: {}",
dto.unique_key, error
)));
},
}
}
},
}
}
@@ -107,13 +109,13 @@ LIMIT 1
"cannot read kb_wallet_participations by unique_key '{}' on sqlite: {}",
unique_key, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletParticipationDto::try_from(entity).map(Some),
None => Ok(None),
Some(entity) => return crate::KbWalletParticipationDto::try_from(entity).map(Some),
None => return Ok(None),
}
}
},
}
}
@@ -154,7 +156,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_wallet_participations by wallet_id '{}' on sqlite: {}",
wallet_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -165,8 +167,8 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -207,7 +209,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_wallet_participations by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -218,7 +220,7 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}