v0.5.3-pre.003
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/postgres/query.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! PostgreSQL query modules.
|
||||
|
||||
@@ -9,13 +9,13 @@ mod decode_pipeline_queries;
|
||||
mod health_queries;
|
||||
mod raw_queries;
|
||||
mod replay_candidate_queries;
|
||||
mod schema_queries;
|
||||
mod table_diagnostics_queries;
|
||||
|
||||
pub(crate) use self::core_extraction_queries::is_core_extraction_current;
|
||||
pub(crate) use self::core_extraction_queries::list_raw_transactions_for_core_extraction;
|
||||
pub(crate) use self::core_extraction_queries::mark_core_extraction_failed;
|
||||
pub(crate) use self::core_extraction_queries::persist_core_extraction;
|
||||
pub(crate) use self::core_queries::apply_core_store_schema;
|
||||
pub(crate) use self::core_queries::insert_core_account_keys;
|
||||
pub(crate) use self::core_queries::insert_core_balance_changes;
|
||||
pub(crate) use self::core_queries::insert_core_inner_instructions;
|
||||
@@ -26,21 +26,17 @@ pub(crate) use self::core_queries::list_core_instruction_replay_inputs;
|
||||
pub(crate) use self::core_queries::list_core_instructions_for_replay;
|
||||
pub(crate) use self::core_queries::list_decode_replay_inputs;
|
||||
pub(crate) use self::core_queries::update_core_instruction_lifecycle;
|
||||
pub(crate) use self::decode_pipeline_queries::apply_decode_store_schema;
|
||||
pub(crate) use self::decode_pipeline_queries::is_decode_current;
|
||||
pub(crate) use self::decode_pipeline_queries::list_decode_coverage_summary;
|
||||
pub(crate) use self::decode_pipeline_queries::list_decode_inputs;
|
||||
pub(crate) use self::decode_pipeline_queries::list_materialized_events;
|
||||
pub(crate) use self::decode_pipeline_queries::list_materialized_outputs;
|
||||
pub(crate) use self::decode_pipeline_queries::mark_decode_failed;
|
||||
pub(crate) use self::decode_pipeline_queries::persist_decode_coverage_declarations;
|
||||
pub(crate) use self::decode_pipeline_queries::persist_decode_result;
|
||||
pub(crate) use self::decode_pipeline_queries::persist_materialization_result;
|
||||
pub(crate) use self::health_queries::load_current_schema;
|
||||
pub(crate) use self::health_queries::load_latest_migration_version;
|
||||
pub(crate) use self::health_queries::load_migration_table_name;
|
||||
pub(crate) use self::health_queries::load_server_version;
|
||||
pub(crate) use self::health_queries::run_health_check;
|
||||
pub(crate) use self::raw_queries::apply_raw_store_schema;
|
||||
pub(crate) use self::raw_queries::has_raw_transaction_signature;
|
||||
pub(crate) use self::raw_queries::has_transaction_observation_key;
|
||||
pub(crate) use self::raw_queries::insert_raw_transaction;
|
||||
@@ -49,5 +45,7 @@ pub(crate) use self::raw_queries::update_raw_payload_lifecycle;
|
||||
pub(crate) use self::replay_candidate_queries::list_replay_entity_summaries;
|
||||
pub(crate) use self::replay_candidate_queries::list_replay_program_summaries;
|
||||
pub(crate) use self::replay_candidate_queries::list_replay_transaction_candidates;
|
||||
pub(crate) use self::schema_queries::apply_store_schema;
|
||||
pub(crate) use self::schema_queries::load_expected_index_counts;
|
||||
pub(crate) use self::table_diagnostics_queries::load_table_statistics;
|
||||
pub(crate) use self::table_diagnostics_queries::table_exists;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/postgres/query/core_extraction_queries.rs
|
||||
// version: 6
|
||||
// version: 8
|
||||
|
||||
//! PostgreSQL queries for atomic canonical transaction to core extraction.
|
||||
|
||||
@@ -9,25 +9,21 @@ pub(crate) async fn list_raw_transactions_for_core_extraction(
|
||||
pool: &sqlx::PgPool,
|
||||
filter: &crate::CoreExtractionSelectionFilter,
|
||||
) -> ks_core::Result<std::vec::Vec<crate::RawTransactionRow>> {
|
||||
let min_slot_result =
|
||||
crate::postgres::query::core_extraction_queries::optional_sql_slot(filter.min_slot);
|
||||
let min_slot_result = optional_sql_slot(filter.min_slot);
|
||||
let min_slot = match min_slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let max_slot_result =
|
||||
crate::postgres::query::core_extraction_queries::optional_sql_slot(filter.max_slot);
|
||||
let max_slot_result = optional_sql_slot(filter.max_slot);
|
||||
let max_slot = match max_slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let processing_state = filter
|
||||
.processing_state
|
||||
.map(crate::postgres::query::core_extraction_queries::raw_processing_state_to_sql);
|
||||
let processing_state = filter.processing_state.map(raw_processing_state_to_sql);
|
||||
let limit = i64::from(filter.limit);
|
||||
let signatures_are_empty = filter.signatures.is_empty();
|
||||
let query_result = sqlx::query(
|
||||
"SELECT raw.id, raw.signature, raw.slot, raw.canonical_json, raw.canonical_json_hash, raw.canonical_format_version, raw.retention_state, raw.processing_state, raw.created_at, raw.updated_at FROM kb_sol_raw_transactions raw WHERE ($1 OR raw.signature = ANY($2)) AND ($3::BIGINT IS NULL OR raw.slot >= $3) AND ($4::BIGINT IS NULL OR raw.slot <= $4) AND ($5::TEXT IS NULL OR raw.processing_state = $5) AND ($6::TEXT IS NULL OR EXISTS (SELECT 1 FROM kb_sol_core_instructions instruction WHERE instruction.signature = raw.signature AND instruction.program_id = $6)) ORDER BY raw.slot ASC, raw.signature ASC LIMIT $7",
|
||||
"SELECT raw.id, raw.signature, raw.slot, raw.block_time, raw.canonical_json, raw.canonical_json_hash, raw.canonical_format_version, raw.retention_state, raw.processing_state, raw.created_at, raw.updated_at FROM k_sol_raw_transactions raw WHERE ($1 OR raw.signature = ANY($2)) AND ($3::BIGINT IS NULL OR raw.slot >= $3) AND ($4::BIGINT IS NULL OR raw.slot <= $4) AND ($5::TEXT IS NULL OR raw.processing_state = $5) AND ($6::TEXT IS NULL OR EXISTS (SELECT 1 FROM k_sol_core_instructions instruction WHERE instruction.signature = raw.signature AND instruction.program_id = $6)) ORDER BY raw.slot ASC, raw.signature ASC LIMIT $7",
|
||||
)
|
||||
.bind(signatures_are_empty)
|
||||
.bind(&filter.signatures)
|
||||
@@ -48,8 +44,7 @@ pub(crate) async fn list_raw_transactions_for_core_extraction(
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mapped_result =
|
||||
crate::postgres::query::core_extraction_queries::map_raw_transaction_row(&row);
|
||||
let mapped_result = map_raw_transaction_row(&row);
|
||||
let mapped = match mapped_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -64,7 +59,7 @@ pub(crate) async fn is_core_extraction_current(
|
||||
identity: &crate::ProcessingLedgerIdentity,
|
||||
) -> ks_core::Result<bool> {
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, bool>(
|
||||
"SELECT EXISTS(SELECT 1 FROM kb_sol_ops_processing_ledger WHERE stage = $1 AND processor_name = $2 AND processor_version = $3 AND input_key = $4 AND input_hash = $5 AND status = 'succeeded')",
|
||||
"SELECT EXISTS(SELECT 1 FROM k_sol_ops_processing_ledger WHERE stage = $1 AND processor_name = $2 AND processor_version = $3 AND input_key = $4 AND input_hash = $5 AND status = 'succeeded')",
|
||||
)
|
||||
.bind(identity.stage.as_str())
|
||||
.bind(identity.processor_name.as_str())
|
||||
@@ -100,7 +95,7 @@ pub(crate) async fn persist_core_extraction(
|
||||
},
|
||||
};
|
||||
let existing_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT id FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1",
|
||||
"SELECT id FROM k_sol_core_transactions WHERE signature = $1 LIMIT 1",
|
||||
)
|
||||
.bind(bundle.transaction.signature.as_str())
|
||||
.fetch_optional(&mut *transaction)
|
||||
@@ -114,11 +109,10 @@ pub(crate) async fn persist_core_extraction(
|
||||
},
|
||||
};
|
||||
if existing.is_some() {
|
||||
let delete_result =
|
||||
sqlx::query("DELETE FROM kb_sol_core_transactions WHERE signature = $1")
|
||||
.bind(bundle.transaction.signature.as_str())
|
||||
.execute(&mut *transaction)
|
||||
.await;
|
||||
let delete_result = sqlx::query("DELETE FROM k_sol_core_transactions WHERE signature = $1")
|
||||
.bind(bundle.transaction.signature.as_str())
|
||||
.execute(&mut *transaction)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = delete_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres core extraction replacement delete failed: {error}"
|
||||
@@ -132,56 +126,51 @@ pub(crate) async fn persist_core_extraction(
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let account_result =
|
||||
crate::postgres::query::core_extraction_queries::insert_account_keys_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
&bundle.account_keys,
|
||||
)
|
||||
.await;
|
||||
insert_account_keys_in_transaction(&mut transaction, transaction_id, &bundle.account_keys)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = account_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let instruction_result =
|
||||
crate::postgres::query::core_extraction_queries::insert_instructions_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
&bundle.instructions,
|
||||
)
|
||||
.await;
|
||||
insert_instructions_in_transaction(&mut transaction, transaction_id, &bundle.instructions)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = instruction_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let inner_result =
|
||||
crate::postgres::query::core_extraction_queries::insert_inner_instructions_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
&bundle.inner_instructions,
|
||||
)
|
||||
.await;
|
||||
let inner_result = insert_inner_instructions_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
&bundle.inner_instructions,
|
||||
)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = inner_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let log_result = crate::postgres::query::core_extraction_queries::insert_logs_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
&bundle.logs,
|
||||
)
|
||||
.await;
|
||||
let log_result =
|
||||
insert_logs_in_transaction(&mut transaction, transaction_id, &bundle.logs).await;
|
||||
if let std::result::Result::Err(error) = log_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let balance_result =
|
||||
crate::postgres::query::core_extraction_queries::insert_balance_changes_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
&bundle.balance_changes,
|
||||
)
|
||||
.await;
|
||||
let balance_result = insert_balance_changes_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
&bundle.balance_changes,
|
||||
)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = balance_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let return_data_result = insert_return_data_in_transaction(
|
||||
&mut transaction,
|
||||
transaction_id,
|
||||
bundle.return_data.as_ref(),
|
||||
)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = return_data_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let raw_update_result = sqlx::query(
|
||||
"UPDATE kb_sol_raw_transactions SET processing_state = 'core_extracted', lifecycle_reason = NULL, updated_at = NOW() WHERE id = $1",
|
||||
"UPDATE k_sol_raw_transactions SET processing_state = 'core_extracted', lifecycle_reason = NULL, updated_at = NOW() WHERE id = $1",
|
||||
)
|
||||
.bind(bundle.raw_transaction_id)
|
||||
.execute(&mut *transaction)
|
||||
@@ -191,15 +180,14 @@ pub(crate) async fn persist_core_extraction(
|
||||
"postgres core extraction raw lifecycle update failed: {error}"
|
||||
)));
|
||||
}
|
||||
let ledger_result =
|
||||
crate::postgres::query::core_extraction_queries::upsert_ledger_terminal_in_transaction(
|
||||
&mut transaction,
|
||||
&bundle.ledger_identity,
|
||||
"succeeded",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
)
|
||||
.await;
|
||||
let ledger_result = upsert_ledger_terminal_in_transaction(
|
||||
&mut transaction,
|
||||
&bundle.ledger_identity,
|
||||
"succeeded",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = ledger_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -209,8 +197,7 @@ pub(crate) async fn persist_core_extraction(
|
||||
"postgres core extraction commit failed: {error}"
|
||||
)));
|
||||
}
|
||||
let inserted_count_result =
|
||||
crate::postgres::query::core_extraction_queries::bundle_inserted_count(bundle);
|
||||
let inserted_count_result = bundle_inserted_count(bundle);
|
||||
let inserted_count = match inserted_count_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -236,7 +223,7 @@ pub(crate) async fn mark_core_extraction_failed(
|
||||
},
|
||||
};
|
||||
let raw_update_result = sqlx::query(
|
||||
"UPDATE kb_sol_raw_transactions SET processing_state = 'failed', lifecycle_reason = $2, updated_at = NOW() WHERE id = $1",
|
||||
"UPDATE k_sol_raw_transactions SET processing_state = 'failed', lifecycle_reason = $2, updated_at = NOW() WHERE id = $1",
|
||||
)
|
||||
.bind(failure.raw_transaction_id)
|
||||
.bind(failure.error_message.as_str())
|
||||
@@ -247,15 +234,14 @@ pub(crate) async fn mark_core_extraction_failed(
|
||||
"postgres core extraction failed lifecycle update failed: {error}"
|
||||
)));
|
||||
}
|
||||
let ledger_result =
|
||||
crate::postgres::query::core_extraction_queries::upsert_ledger_terminal_in_transaction(
|
||||
&mut transaction,
|
||||
&failure.ledger_identity,
|
||||
"failed",
|
||||
std::option::Option::Some(failure.error_code.as_str()),
|
||||
std::option::Option::Some(failure.error_message.as_str()),
|
||||
)
|
||||
.await;
|
||||
let ledger_result = upsert_ledger_terminal_in_transaction(
|
||||
&mut transaction,
|
||||
&failure.ledger_identity,
|
||||
"failed",
|
||||
std::option::Option::Some(failure.error_code.as_str()),
|
||||
std::option::Option::Some(failure.error_message.as_str()),
|
||||
)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = ledger_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -272,17 +258,18 @@ async fn insert_core_transaction_in_transaction(
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
input: &crate::CoreTransactionInsert,
|
||||
) -> ks_core::Result<i64> {
|
||||
let slot_result = crate::postgres::query::core_extraction_queries::sql_slot(input.slot);
|
||||
let slot_result = sql_slot(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_core_transactions (raw_transaction_id, signature, slot, failed, err_json) VALUES ($1, $2, $3, $4, $5) RETURNING id",
|
||||
"INSERT INTO k_sol_core_transactions (raw_transaction_id, signature, slot, block_time, failed, err_json) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id",
|
||||
)
|
||||
.bind(input.raw_transaction_id)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(input.block_time)
|
||||
.bind(input.failed)
|
||||
.bind(input.err_json.clone())
|
||||
.fetch_one(&mut **transaction)
|
||||
@@ -301,7 +288,7 @@ async fn insert_account_keys_in_transaction(
|
||||
inputs: &[crate::CoreAccountKeyInsert],
|
||||
) -> ks_core::Result<()> {
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_extraction_queries::sql_slot(input.slot);
|
||||
let slot_result = sql_slot(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -316,14 +303,14 @@ async fn insert_account_keys_in_transaction(
|
||||
},
|
||||
};
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_core_account_keys (transaction_id, signature, slot, account_index, account_key, source, writable, signer, executable) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
"INSERT INTO k_sol_core_account_keys (transaction_id, signature, slot, account_index, account_key, source, writable, signer, executable) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
)
|
||||
.bind(transaction_id)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(account_index)
|
||||
.bind(input.account_key.as_str())
|
||||
.bind(crate::postgres::query::core_extraction_queries::account_key_source_to_sql(input.source))
|
||||
.bind(account_key_source_to_sql(input.source))
|
||||
.bind(input.writable)
|
||||
.bind(input.signer)
|
||||
.bind(input.executable)
|
||||
@@ -344,19 +331,25 @@ async fn insert_instructions_in_transaction(
|
||||
inputs: &[crate::CoreInstructionInsert],
|
||||
) -> ks_core::Result<()> {
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_extraction_queries::sql_slot(input.slot);
|
||||
let slot_result = sql_slot(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let stack_height_result = sql_stack_height(input.stack_height, "core instruction");
|
||||
let stack_height = match stack_height_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_core_instructions (transaction_id, signature, slot, instruction_path, program_id, accounts_json, payload_json, payload_json_hash, processing_state) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'pending')",
|
||||
"INSERT INTO k_sol_core_instructions (transaction_id, signature, slot, instruction_path, program_id, stack_height, accounts_json, payload_json, payload_json_hash, processing_state) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'pending')",
|
||||
)
|
||||
.bind(transaction_id)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(input.instruction_path.as_str())
|
||||
.bind(input.program_id.as_str())
|
||||
.bind(stack_height)
|
||||
.bind(&input.accounts_json)
|
||||
.bind(&input.payload_json)
|
||||
.bind(input.payload_json_hash.as_deref())
|
||||
@@ -377,13 +370,18 @@ async fn insert_inner_instructions_in_transaction(
|
||||
inputs: &[crate::CoreInnerInstructionInsert],
|
||||
) -> ks_core::Result<()> {
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_extraction_queries::sql_slot(input.slot);
|
||||
let slot_result = sql_slot(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let stack_height_result = sql_stack_height(input.stack_height, "core inner instruction");
|
||||
let stack_height = match stack_height_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_core_inner_instructions (transaction_id, signature, slot, parent_instruction_path, instruction_path, program_id, accounts_json, payload_json, payload_json_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
"INSERT INTO k_sol_core_inner_instructions (transaction_id, signature, slot, parent_instruction_path, instruction_path, program_id, stack_height, accounts_json, payload_json, payload_json_hash, processing_state) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'pending')",
|
||||
)
|
||||
.bind(transaction_id)
|
||||
.bind(input.signature.as_str())
|
||||
@@ -391,6 +389,7 @@ async fn insert_inner_instructions_in_transaction(
|
||||
.bind(input.parent_instruction_path.as_str())
|
||||
.bind(input.instruction_path.as_str())
|
||||
.bind(input.program_id.as_str())
|
||||
.bind(stack_height)
|
||||
.bind(&input.accounts_json)
|
||||
.bind(&input.payload_json)
|
||||
.bind(input.payload_json_hash.as_deref())
|
||||
@@ -405,13 +404,45 @@ async fn insert_inner_instructions_in_transaction(
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
async fn insert_return_data_in_transaction(
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
transaction_id: i64,
|
||||
input: std::option::Option<&crate::CoreReturnDataInsert>,
|
||||
) -> ks_core::Result<()> {
|
||||
let value = match input {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Ok(()),
|
||||
};
|
||||
let slot_result = sql_slot(value.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO k_sol_core_return_data (transaction_id, signature, slot, program_id, data_base64) VALUES ($1, $2, $3, $4, $5)",
|
||||
)
|
||||
.bind(transaction_id)
|
||||
.bind(value.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(value.program_id.as_str())
|
||||
.bind(value.data_base64.as_str())
|
||||
.execute(&mut **transaction)
|
||||
.await;
|
||||
return match query_result {
|
||||
std::result::Result::Ok(_) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres core extraction return-data insert failed: {error}"
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
async fn insert_logs_in_transaction(
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
transaction_id: i64,
|
||||
inputs: &[crate::CoreLogInsert],
|
||||
) -> ks_core::Result<()> {
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_extraction_queries::sql_slot(input.slot);
|
||||
let slot_result = sql_slot(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -426,7 +457,7 @@ async fn insert_logs_in_transaction(
|
||||
},
|
||||
};
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_core_logs (transaction_id, signature, slot, log_index, instruction_path, program_id, log_text, log_text_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
"INSERT INTO k_sol_core_logs (transaction_id, signature, slot, log_index, instruction_path, program_id, log_text, log_text_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
)
|
||||
.bind(transaction_id)
|
||||
.bind(input.signature.as_str())
|
||||
@@ -453,7 +484,7 @@ async fn insert_balance_changes_in_transaction(
|
||||
inputs: &[crate::CoreBalanceChangeInsert],
|
||||
) -> ks_core::Result<()> {
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_extraction_queries::sql_slot(input.slot);
|
||||
let slot_result = sql_slot(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -477,13 +508,13 @@ async fn insert_balance_changes_in_transaction(
|
||||
},
|
||||
};
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_core_balance_changes (transaction_id, signature, slot, balance_change_index, balance_kind, account_index, account_key, mint, owner, pre_balance_json, post_balance_json, delta_json) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
||||
"INSERT INTO k_sol_core_balance_changes (transaction_id, signature, slot, balance_change_index, balance_kind, account_index, account_key, mint, owner, pre_balance_json, post_balance_json, delta_json) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
||||
)
|
||||
.bind(transaction_id)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(balance_index)
|
||||
.bind(crate::postgres::query::core_extraction_queries::balance_change_kind_to_sql(input.balance_kind))
|
||||
.bind(balance_change_kind_to_sql(input.balance_kind))
|
||||
.bind(account_index)
|
||||
.bind(input.account_key.as_deref())
|
||||
.bind(input.mint.as_deref())
|
||||
@@ -510,7 +541,7 @@ async fn upsert_ledger_terminal_in_transaction(
|
||||
error_message: std::option::Option<&str>,
|
||||
) -> ks_core::Result<()> {
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_ops_processing_ledger (stage, processor_name, processor_version, input_key, input_hash, status, attempt_count, started_at, finished_at, error_code, error_message) VALUES ($1, $2, $3, $4, $5, $6, 1, NOW(), NOW(), $7, $8) ON CONFLICT (stage, processor_name, processor_version, input_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, status = EXCLUDED.status, attempt_count = kb_sol_ops_processing_ledger.attempt_count + 1, started_at = NOW(), finished_at = NOW(), error_code = EXCLUDED.error_code, error_message = EXCLUDED.error_message, updated_at = NOW()",
|
||||
"INSERT INTO k_sol_ops_processing_ledger (stage, processor_name, processor_version, input_key, input_hash, status, attempt_count, started_at, finished_at, error_code, error_message) VALUES ($1, $2, $3, $4, $5, $6, 1, NOW(), NOW(), $7, $8) ON CONFLICT (stage, processor_name, processor_version, input_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, status = EXCLUDED.status, attempt_count = k_sol_ops_processing_ledger.attempt_count + 1, started_at = NOW(), finished_at = NOW(), error_code = EXCLUDED.error_code, error_message = EXCLUDED.error_message, updated_at = NOW()",
|
||||
)
|
||||
.bind(identity.stage.as_str())
|
||||
.bind(identity.processor_name.as_str())
|
||||
@@ -551,18 +582,12 @@ fn map_raw_transaction_row(
|
||||
)));
|
||||
},
|
||||
};
|
||||
let retention_result =
|
||||
crate::postgres::query::core_extraction_queries::raw_retention_state_from_sql(
|
||||
retention_text.as_str(),
|
||||
);
|
||||
let retention_result = raw_retention_state_from_sql(retention_text.as_str());
|
||||
let retention_state = match retention_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let processing_result =
|
||||
crate::postgres::query::core_extraction_queries::raw_processing_state_from_sql(
|
||||
processing_text.as_str(),
|
||||
);
|
||||
let processing_result = raw_processing_state_from_sql(processing_text.as_str());
|
||||
let processing_state = match processing_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -591,6 +616,14 @@ fn map_raw_transaction_row(
|
||||
)));
|
||||
},
|
||||
};
|
||||
let block_time = match row.try_get::<std::option::Option<i64>, _>("block_time") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres raw block time mapping failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let canonical_json =
|
||||
match row.try_get::<std::option::Option<serde_json::Value>, _>("canonical_json") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -637,6 +670,7 @@ fn map_raw_transaction_row(
|
||||
id,
|
||||
signature,
|
||||
slot,
|
||||
block_time,
|
||||
canonical_json,
|
||||
canonical_json_hash,
|
||||
canonical_format_version,
|
||||
@@ -655,6 +689,7 @@ fn bundle_inserted_count(bundle: &crate::CoreExtractionBundle) -> ks_core::Resul
|
||||
bundle.inner_instructions.len(),
|
||||
bundle.logs.len(),
|
||||
bundle.balance_changes.len(),
|
||||
usize::from(bundle.return_data.is_some()),
|
||||
] {
|
||||
let value_result = u64::try_from(length);
|
||||
let value = match value_result {
|
||||
@@ -678,10 +713,23 @@ fn bundle_inserted_count(bundle: &crate::CoreExtractionBundle) -> ks_core::Resul
|
||||
return std::result::Result::Ok(total);
|
||||
}
|
||||
|
||||
fn sql_stack_height(
|
||||
value: std::option::Option<u32>,
|
||||
context: &str,
|
||||
) -> ks_core::Result<std::option::Option<i32>> {
|
||||
let conversion_result = value.map(i32::try_from).transpose();
|
||||
return match conversion_result {
|
||||
std::result::Result::Ok(converted) => std::result::Result::Ok(converted),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"{context} stack height does not fit into SQL INTEGER: {error}"
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
fn optional_sql_slot(value: std::option::Option<u64>) -> ks_core::Result<std::option::Option<i64>> {
|
||||
return match value {
|
||||
std::option::Option::Some(slot) => {
|
||||
let converted_result = crate::postgres::query::core_extraction_queries::sql_slot(slot);
|
||||
let converted_result = sql_slot(slot);
|
||||
match converted_result {
|
||||
std::result::Result::Ok(converted) => {
|
||||
std::result::Result::Ok(std::option::Option::Some(converted))
|
||||
@@ -760,16 +808,14 @@ mod tests {
|
||||
#[test]
|
||||
fn processing_state_serializes_to_expected_sql() {
|
||||
assert_eq!(
|
||||
crate::postgres::query::core_extraction_queries::raw_processing_state_to_sql(
|
||||
crate::RawPayloadProcessingState::CoreExtracted,
|
||||
),
|
||||
super::raw_processing_state_to_sql(crate::RawPayloadProcessingState::CoreExtracted,),
|
||||
"core_extracted"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_rejects_values_above_bigint() {
|
||||
let result = crate::postgres::query::core_extraction_queries::sql_slot(u64::MAX);
|
||||
let result = super::sql_slot(u64::MAX);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -790,7 +836,7 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("unexpected connect error: {error}"),
|
||||
};
|
||||
let schema_result = store.initialize_core_store_schema().await;
|
||||
let schema_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = schema_result {
|
||||
panic!("unexpected schema error: {error}");
|
||||
}
|
||||
@@ -798,6 +844,7 @@ mod tests {
|
||||
let raw_input_result = crate::RawTransactionInsert::new(
|
||||
signature.clone(),
|
||||
1,
|
||||
std::option::Option::None,
|
||||
serde_json::json!({"test": "core_extraction_rollback"}),
|
||||
1,
|
||||
);
|
||||
@@ -816,7 +863,7 @@ mod tests {
|
||||
panic!("unexpected raw insert error: {error}");
|
||||
}
|
||||
let raw_id_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT id FROM kb_sol_raw_transactions WHERE signature = $1",
|
||||
"SELECT id FROM k_sol_raw_transactions WHERE signature = $1",
|
||||
)
|
||||
.bind(signature.as_str())
|
||||
.fetch_one(store.pool())
|
||||
@@ -855,8 +902,13 @@ mod tests {
|
||||
signature: &str,
|
||||
duplicate_account_index: bool,
|
||||
) -> crate::CoreExtractionBundle {
|
||||
let transaction_result =
|
||||
crate::CoreTransactionInsert::new(signature, 1, false, std::option::Option::None);
|
||||
let transaction_result = crate::CoreTransactionInsert::new(
|
||||
signature,
|
||||
1,
|
||||
std::option::Option::None,
|
||||
false,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let transaction = match transaction_result {
|
||||
std::result::Result::Ok(value) => value.with_raw_transaction_id(raw_transaction_id),
|
||||
std::result::Result::Err(error) => panic!("unexpected transaction error: {error}"),
|
||||
@@ -915,6 +967,7 @@ mod tests {
|
||||
inner_instructions: std::vec::Vec::new(),
|
||||
logs: std::vec::Vec::new(),
|
||||
balance_changes: std::vec::Vec::new(),
|
||||
return_data: std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -926,7 +979,7 @@ mod tests {
|
||||
expected_ledger_count: i64,
|
||||
) {
|
||||
let core_count_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT COUNT(*)::BIGINT FROM kb_sol_core_transactions WHERE signature = $1",
|
||||
"SELECT COUNT(*)::BIGINT FROM k_sol_core_transactions WHERE signature = $1",
|
||||
)
|
||||
.bind(signature)
|
||||
.fetch_one(store.pool())
|
||||
@@ -936,7 +989,7 @@ mod tests {
|
||||
std::result::Result::Err(error) => panic!("unexpected core count error: {error}"),
|
||||
};
|
||||
let account_count_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT COUNT(*)::BIGINT FROM kb_sol_core_account_keys WHERE signature = $1",
|
||||
"SELECT COUNT(*)::BIGINT FROM k_sol_core_account_keys WHERE signature = $1",
|
||||
)
|
||||
.bind(signature)
|
||||
.fetch_one(store.pool())
|
||||
@@ -946,7 +999,7 @@ mod tests {
|
||||
std::result::Result::Err(error) => panic!("unexpected account count error: {error}"),
|
||||
};
|
||||
let processing_state_result = sqlx::query_scalar::<sqlx::Postgres, std::string::String>(
|
||||
"SELECT processing_state FROM kb_sol_raw_transactions WHERE signature = $1",
|
||||
"SELECT processing_state FROM k_sol_raw_transactions WHERE signature = $1",
|
||||
)
|
||||
.bind(signature)
|
||||
.fetch_one(store.pool())
|
||||
@@ -956,7 +1009,7 @@ mod tests {
|
||||
std::result::Result::Err(error) => panic!("unexpected processing state error: {error}"),
|
||||
};
|
||||
let ledger_count_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT COUNT(*)::BIGINT FROM kb_sol_ops_processing_ledger WHERE stage = 'core_extraction' AND processor_name = 'canonical_to_core' AND processor_version = 'rollback-test' AND input_key = $1",
|
||||
"SELECT COUNT(*)::BIGINT FROM k_sol_ops_processing_ledger WHERE stage = 'core_extraction' AND processor_name = 'canonical_to_core' AND processor_version = 'rollback-test' AND input_key = $1",
|
||||
)
|
||||
.bind(signature)
|
||||
.fetch_one(store.pool())
|
||||
@@ -973,7 +1026,7 @@ mod tests {
|
||||
|
||||
async fn cleanup_rollback_test(store: &crate::PostgresStore, signature: &str) {
|
||||
let ledger_result = sqlx::query(
|
||||
"DELETE FROM kb_sol_ops_processing_ledger WHERE processor_version = 'rollback-test' AND input_key = $1",
|
||||
"DELETE FROM k_sol_ops_processing_ledger WHERE processor_version = 'rollback-test' AND input_key = $1",
|
||||
)
|
||||
.bind(signature)
|
||||
.execute(store.pool())
|
||||
@@ -981,14 +1034,14 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = ledger_result {
|
||||
panic!("unexpected ledger cleanup error: {error}");
|
||||
}
|
||||
let core_result = sqlx::query("DELETE FROM kb_sol_core_transactions WHERE signature = $1")
|
||||
let core_result = sqlx::query("DELETE FROM k_sol_core_transactions WHERE signature = $1")
|
||||
.bind(signature)
|
||||
.execute(store.pool())
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = core_result {
|
||||
panic!("unexpected core cleanup error: {error}");
|
||||
}
|
||||
let raw_result = sqlx::query("DELETE FROM kb_sol_raw_transactions WHERE signature = $1")
|
||||
let raw_result = sqlx::query("DELETE FROM k_sol_raw_transactions WHERE signature = $1")
|
||||
.bind(signature)
|
||||
.execute(store.pool())
|
||||
.await;
|
||||
|
||||
@@ -1,75 +1,34 @@
|
||||
// file: ks-store/src/postgres/query/core_queries.rs
|
||||
// version: 6
|
||||
// version: 9
|
||||
|
||||
//! PostgreSQL queries for normalized Solana core storage.
|
||||
|
||||
use sqlx::Row; // rust-rules: trait-import
|
||||
|
||||
const OUTER_INSTRUCTIONS_CONTEXT_SQL: &str = "SELECT COALESCE(jsonb_agg(jsonb_build_object('instructionIndex', instruction_path::BIGINT, 'instructionPath', instruction_path, 'programId', program_id, 'payloadJson', payload_json, 'payloadHash', payload_json_hash) ORDER BY instruction_path::BIGINT, instruction_path ASC), '[]'::jsonb) FROM kb_sol_core_instructions WHERE signature = $1 AND instruction_path ~ '^[0-9]+$'";
|
||||
|
||||
pub(crate) async fn apply_core_store_schema(pool: &sqlx::PgPool) -> ks_core::Result<()> {
|
||||
let validation_result = crate::validate_core_store_table_names();
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let transaction_result = pool.begin().await;
|
||||
let mut transaction = match transaction_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres core store schema transaction failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let lock_result = sqlx::query("SELECT pg_advisory_xact_lock($1)")
|
||||
.bind(crate::STORE_SCHEMA_ADVISORY_LOCK_ID)
|
||||
.execute(&mut *transaction)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = lock_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres core store schema advisory lock failed: {error}"
|
||||
)));
|
||||
}
|
||||
for statement in crate::core_store_schema_statements() {
|
||||
let execution_result = sqlx::query(statement).execute(&mut *transaction).await;
|
||||
if let std::result::Result::Err(error) = execution_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres core store schema initialization failed: {error}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
let commit_result = transaction.commit().await;
|
||||
if let std::result::Result::Err(error) = commit_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres core store schema commit failed: {error}"
|
||||
)));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
const TOP_LEVEL_INSTRUCTIONS_CONTEXT_SQL: &str = "SELECT COALESCE(jsonb_agg(jsonb_build_object('instructionIndex', instruction_path::BIGINT, 'instructionPath', instruction_path, 'programId', program_id, 'stackHeight', stack_height, 'payloadJson', payload_json, 'payloadHash', payload_json_hash) ORDER BY instruction_path::BIGINT, instruction_path ASC), '[]'::jsonb) FROM k_sol_core_instructions WHERE signature = $1 AND instruction_path ~ '^[0-9]+$'";
|
||||
const INCOMPLETE_DECODE_INPUTS_SQL: &str = "WITH incomplete AS (SELECT signature, MIN(slot) AS first_slot FROM k_sol_core_instructions WHERE (cardinality($1::text[]) = 0 OR signature = ANY($1)) AND ($3::bigint IS NULL OR slot >= $3) AND ($4::bigint IS NULL OR slot <= $4) AND (cardinality($5::text[]) = 0 OR program_id = ANY($5)) AND (processing_state = ANY($2) OR (processing_state = 'ignored' AND lifecycle_reason = 'unsupported')) GROUP BY signature ORDER BY MIN(slot), signature LIMIT $7) SELECT i.id, i.transaction_id, i.signature, i.slot, i.instruction_path, i.program_id, i.stack_height, i.accounts_json, i.payload_json, i.payload_json_hash, i.processing_state, i.created_at, i.updated_at FROM k_sol_core_instructions i JOIN incomplete s ON s.signature = i.signature WHERE (cardinality($5::text[]) = 0 OR i.program_id = ANY($5)) AND (cardinality($6::text[]) = 0 OR i.instruction_path = ANY($6)) ORDER BY i.slot ASC, i.signature ASC, i.instruction_path ASC";
|
||||
|
||||
pub(crate) async fn insert_core_transaction(
|
||||
pool: &sqlx::PgPool,
|
||||
input: &crate::CoreTransactionInsert,
|
||||
) -> ks_core::Result<crate::InsertOutcome> {
|
||||
let slot_result = crate::postgres::query::core_queries::sql_slot_from_u64(input.slot);
|
||||
let slot_result = sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_core_transactions (raw_transaction_id, signature, slot, failed, err_json) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (signature) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_core_transactions (raw_transaction_id, signature, slot, block_time, failed, err_json) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (signature) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.raw_transaction_id)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(input.block_time)
|
||||
.bind(input.failed)
|
||||
.bind(input.err_json.clone())
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
return crate::postgres::query::core_queries::outcome_from_insert_optional(
|
||||
query_result,
|
||||
"postgres core transaction insert failed",
|
||||
);
|
||||
return outcome_from_insert_optional(query_result, "postgres core transaction insert failed");
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_core_account_keys(
|
||||
@@ -79,12 +38,12 @@ pub(crate) async fn insert_core_account_keys(
|
||||
let mut inserted_count = 0_u64;
|
||||
let mut skipped_count = 0_u64;
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_queries::sql_slot_from_u64(input.slot);
|
||||
let slot_result = sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let account_index_result = crate::postgres::query::core_queries::sql_i32_from_u32(
|
||||
let account_index_result = sql_i32_from_u32(
|
||||
input.account_index,
|
||||
"core account index does not fit into SQL INTEGER",
|
||||
);
|
||||
@@ -92,9 +51,9 @@ pub(crate) async fn insert_core_account_keys(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let source = crate::postgres::query::core_queries::account_key_source_to_sql(input.source);
|
||||
let source = account_key_source_to_sql(input.source);
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_core_account_keys (transaction_id, signature, slot, account_index, account_key, source, writable, signer, executable) VALUES ((SELECT id FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (signature, account_index) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_core_account_keys (transaction_id, signature, slot, account_index, account_key, source, writable, signer, executable) VALUES ((SELECT id FROM k_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (signature, account_index) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
@@ -106,10 +65,8 @@ pub(crate) async fn insert_core_account_keys(
|
||||
.bind(input.executable)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
let outcome_result = crate::postgres::query::core_queries::outcome_from_insert_optional(
|
||||
query_result,
|
||||
"postgres core account key insert failed",
|
||||
);
|
||||
let outcome_result =
|
||||
outcome_from_insert_optional(query_result, "postgres core account key insert failed");
|
||||
let outcome = match outcome_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -127,32 +84,36 @@ pub(crate) async fn insert_core_instructions(
|
||||
let mut inserted_count = 0_u64;
|
||||
let mut skipped_count = 0_u64;
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_queries::sql_slot_from_u64(input.slot);
|
||||
let slot_result = sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let processing_state =
|
||||
crate::postgres::query::core_queries::instruction_processing_state_to_sql(
|
||||
input.processing_state,
|
||||
);
|
||||
let stack_height_result = optional_sql_i32_from_u32(
|
||||
input.stack_height,
|
||||
"core instruction stack height does not fit into SQL INTEGER",
|
||||
);
|
||||
let stack_height = match stack_height_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let processing_state = instruction_processing_state_to_sql(input.processing_state);
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_core_instructions (transaction_id, signature, slot, instruction_path, program_id, accounts_json, payload_json, payload_json_hash, processing_state) VALUES ((SELECT id FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (signature, instruction_path) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_core_instructions (transaction_id, signature, slot, instruction_path, program_id, stack_height, accounts_json, payload_json, payload_json_hash, processing_state) VALUES ((SELECT id FROM k_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (signature, instruction_path) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(input.instruction_path.as_str())
|
||||
.bind(input.program_id.as_str())
|
||||
.bind(stack_height)
|
||||
.bind(&input.accounts_json)
|
||||
.bind(&input.payload_json)
|
||||
.bind(input.payload_json_hash.as_deref())
|
||||
.bind(processing_state)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
let outcome_result = crate::postgres::query::core_queries::outcome_from_insert_optional(
|
||||
query_result,
|
||||
"postgres core instruction insert failed",
|
||||
);
|
||||
let outcome_result =
|
||||
outcome_from_insert_optional(query_result, "postgres core instruction insert failed");
|
||||
let outcome = match outcome_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -170,25 +131,36 @@ pub(crate) async fn insert_core_inner_instructions(
|
||||
let mut inserted_count = 0_u64;
|
||||
let mut skipped_count = 0_u64;
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_queries::sql_slot_from_u64(input.slot);
|
||||
let slot_result = sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let stack_height_result = optional_sql_i32_from_u32(
|
||||
input.stack_height,
|
||||
"core inner instruction stack height does not fit into SQL INTEGER",
|
||||
);
|
||||
let stack_height = match stack_height_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let processing_state = instruction_processing_state_to_sql(input.processing_state);
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_core_inner_instructions (transaction_id, signature, slot, parent_instruction_path, instruction_path, program_id, accounts_json, payload_json, payload_json_hash) VALUES ((SELECT id FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (signature, instruction_path) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_core_inner_instructions (transaction_id, signature, slot, parent_instruction_path, instruction_path, program_id, stack_height, accounts_json, payload_json, payload_json_hash, processing_state) VALUES ((SELECT id FROM k_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (signature, instruction_path) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(input.parent_instruction_path.as_str())
|
||||
.bind(input.instruction_path.as_str())
|
||||
.bind(input.program_id.as_str())
|
||||
.bind(stack_height)
|
||||
.bind(&input.accounts_json)
|
||||
.bind(&input.payload_json)
|
||||
.bind(input.payload_json_hash.as_deref())
|
||||
.bind(processing_state)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
let outcome_result = crate::postgres::query::core_queries::outcome_from_insert_optional(
|
||||
let outcome_result = outcome_from_insert_optional(
|
||||
query_result,
|
||||
"postgres core inner instruction insert failed",
|
||||
);
|
||||
@@ -209,21 +181,19 @@ pub(crate) async fn insert_core_logs(
|
||||
let mut inserted_count = 0_u64;
|
||||
let mut skipped_count = 0_u64;
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_queries::sql_slot_from_u64(input.slot);
|
||||
let slot_result = sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let log_index_result = crate::postgres::query::core_queries::sql_i32_from_u32(
|
||||
input.log_index,
|
||||
"core log index does not fit into SQL INTEGER",
|
||||
);
|
||||
let log_index_result =
|
||||
sql_i32_from_u32(input.log_index, "core log index does not fit into SQL INTEGER");
|
||||
let log_index = match log_index_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_core_logs (transaction_id, signature, slot, log_index, instruction_path, program_id, log_text, log_text_hash) VALUES ((SELECT id FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7) ON CONFLICT (signature, log_index) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_core_logs (transaction_id, signature, slot, log_index, instruction_path, program_id, log_text, log_text_hash) VALUES ((SELECT id FROM k_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7) ON CONFLICT (signature, log_index) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
@@ -234,10 +204,8 @@ pub(crate) async fn insert_core_logs(
|
||||
.bind(input.log_text_hash.as_deref())
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
let outcome_result = crate::postgres::query::core_queries::outcome_from_insert_optional(
|
||||
query_result,
|
||||
"postgres core log insert failed",
|
||||
);
|
||||
let outcome_result =
|
||||
outcome_from_insert_optional(query_result, "postgres core log insert failed");
|
||||
let outcome = match outcome_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -255,12 +223,12 @@ pub(crate) async fn insert_core_balance_changes(
|
||||
let mut inserted_count = 0_u64;
|
||||
let mut skipped_count = 0_u64;
|
||||
for input in inputs {
|
||||
let slot_result = crate::postgres::query::core_queries::sql_slot_from_u64(input.slot);
|
||||
let slot_result = sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let balance_change_index_result = crate::postgres::query::core_queries::sql_i32_from_u32(
|
||||
let balance_change_index_result = sql_i32_from_u32(
|
||||
input.balance_change_index,
|
||||
"core balance change index does not fit into SQL INTEGER",
|
||||
);
|
||||
@@ -268,7 +236,7 @@ pub(crate) async fn insert_core_balance_changes(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let account_index_result = crate::postgres::query::core_queries::optional_sql_i32_from_u32(
|
||||
let account_index_result = optional_sql_i32_from_u32(
|
||||
input.account_index,
|
||||
"core balance account index does not fit into SQL INTEGER",
|
||||
);
|
||||
@@ -276,10 +244,9 @@ pub(crate) async fn insert_core_balance_changes(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let balance_kind =
|
||||
crate::postgres::query::core_queries::balance_change_kind_to_sql(input.balance_kind);
|
||||
let balance_kind = balance_change_kind_to_sql(input.balance_kind);
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_core_balance_changes (transaction_id, signature, slot, balance_change_index, balance_kind, account_index, account_key, mint, owner, pre_balance_json, post_balance_json, delta_json) VALUES ((SELECT id FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (signature, balance_change_index) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_core_balance_changes (transaction_id, signature, slot, balance_change_index, balance_kind, account_index, account_key, mint, owner, pre_balance_json, post_balance_json, delta_json) VALUES ((SELECT id FROM k_sol_core_transactions WHERE signature = $1 LIMIT 1), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (signature, balance_change_index) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
@@ -294,7 +261,7 @@ pub(crate) async fn insert_core_balance_changes(
|
||||
.bind(input.delta_json.clone())
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
let outcome_result = crate::postgres::query::core_queries::outcome_from_insert_optional(
|
||||
let outcome_result = outcome_from_insert_optional(
|
||||
query_result,
|
||||
"postgres core balance change insert failed",
|
||||
);
|
||||
@@ -313,31 +280,25 @@ pub(crate) async fn list_core_instructions_for_replay(
|
||||
filter: &crate::CoreInstructionReplayFilter,
|
||||
page_request: &crate::PageRequest,
|
||||
) -> ks_core::Result<std::vec::Vec<crate::CoreInstructionRow>> {
|
||||
let min_slot_result =
|
||||
crate::postgres::query::core_queries::optional_sql_slot_from_u64(filter.min_slot);
|
||||
let min_slot_result = optional_sql_slot_from_u64(filter.min_slot);
|
||||
let min_slot = match min_slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let max_slot_result =
|
||||
crate::postgres::query::core_queries::optional_sql_slot_from_u64(filter.max_slot);
|
||||
let max_slot_result = optional_sql_slot_from_u64(filter.max_slot);
|
||||
let max_slot = match max_slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let offset_result = crate::postgres::query::core_queries::sql_i64_from_u64(
|
||||
page_request.offset,
|
||||
"page offset does not fit into SQL BIGINT",
|
||||
);
|
||||
let offset_result =
|
||||
sql_i64_from_u64(page_request.offset, "page offset does not fit into SQL BIGINT");
|
||||
let offset = match offset_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let processing_state = filter
|
||||
.processing_state
|
||||
.map(crate::postgres::query::core_queries::instruction_processing_state_to_sql);
|
||||
let processing_state = filter.processing_state.map(instruction_processing_state_to_sql);
|
||||
let query_result = sqlx::query(
|
||||
"SELECT id, transaction_id, signature, slot, instruction_path, program_id, accounts_json, payload_json, payload_json_hash, processing_state, created_at, updated_at FROM kb_sol_core_instructions WHERE ($1::TEXT IS NULL OR processing_state = $1) AND ($2::TEXT IS NULL OR program_id = $2) AND ($3::BIGINT IS NULL OR slot >= $3) AND ($4::BIGINT IS NULL OR slot <= $4) ORDER BY slot ASC, id ASC LIMIT $5 OFFSET $6",
|
||||
"SELECT id, transaction_id, signature, slot, instruction_path, program_id, stack_height, accounts_json, payload_json, payload_json_hash, processing_state, created_at, updated_at FROM k_sol_core_instructions WHERE ($1::TEXT IS NULL OR processing_state = $1) AND ($2::TEXT IS NULL OR program_id = $2) AND ($3::BIGINT IS NULL OR slot >= $3) AND ($4::BIGINT IS NULL OR slot <= $4) ORDER BY slot ASC, id ASC LIMIT $5 OFFSET $6",
|
||||
)
|
||||
.bind(processing_state)
|
||||
.bind(filter.program_id.as_deref())
|
||||
@@ -357,7 +318,7 @@ pub(crate) async fn list_core_instructions_for_replay(
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mapped_result = crate::postgres::query::core_queries::row_to_core_instruction_row(&row);
|
||||
let mapped_result = row_to_core_instruction_row(&row);
|
||||
match mapped_result {
|
||||
std::result::Result::Ok(value) => output.push(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -379,11 +340,7 @@ pub(crate) async fn list_core_instruction_replay_inputs(
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(instructions.len());
|
||||
for instruction in instructions {
|
||||
let input_result = crate::postgres::query::core_queries::load_replay_input_for_instruction(
|
||||
pool,
|
||||
&instruction,
|
||||
)
|
||||
.await;
|
||||
let input_result = load_replay_input_for_instruction(pool, &instruction).await;
|
||||
match input_result {
|
||||
std::result::Result::Ok(value) => output.push(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -396,14 +353,12 @@ pub(crate) async fn list_decode_replay_inputs(
|
||||
pool: &sqlx::PgPool,
|
||||
filter: &crate::DecodeSelectionFilter,
|
||||
) -> ks_core::Result<std::vec::Vec<crate::MdCoreInstructionReplayInput>> {
|
||||
let min_slot_result =
|
||||
crate::postgres::query::core_queries::optional_sql_slot_from_u64(filter.min_slot);
|
||||
let min_slot_result = optional_sql_slot_from_u64(filter.min_slot);
|
||||
let min_slot = match min_slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let max_slot_result =
|
||||
crate::postgres::query::core_queries::optional_sql_slot_from_u64(filter.max_slot);
|
||||
let max_slot_result = optional_sql_slot_from_u64(filter.max_slot);
|
||||
let max_slot = match max_slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -413,28 +368,23 @@ pub(crate) async fn list_decode_replay_inputs(
|
||||
.processing_states
|
||||
.iter()
|
||||
.map(|state| {
|
||||
return crate::postgres::query::core_queries::instruction_processing_state_to_sql(
|
||||
*state,
|
||||
)
|
||||
.to_string();
|
||||
return instruction_processing_state_to_sql(*state).to_string();
|
||||
})
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let rows_result = if filter.incomplete_signatures {
|
||||
sqlx::query(
|
||||
"WITH incomplete AS (SELECT signature, MIN(slot) AS first_slot FROM kb_sol_core_instructions WHERE (cardinality($1::text[]) = 0 OR signature = ANY($1)) AND ($3::bigint IS NULL OR slot >= $3) AND ($4::bigint IS NULL OR slot <= $4) AND (cardinality($5::text[]) = 0 OR program_id = ANY($5)) AND (processing_state = ANY($2) OR (processing_state = 'ignored' AND lifecycle_reason = 'unsupported')) GROUP BY signature ORDER BY MIN(slot), signature LIMIT $7) SELECT i.id, i.transaction_id, i.signature, i.slot, i.instruction_path, i.program_id, i.accounts_json, i.payload_json, i.payload_json_hash, i.processing_state, i.created_at, i.updated_at FROM kb_sol_core_instructions i JOIN incomplete s ON s.signature = i.signature WHERE (cardinality($5::text[]) = 0 OR i.program_id = ANY($5)) AND (cardinality($6::text[]) = 0 OR i.instruction_path = ANY($6)) ORDER BY i.slot ASC, i.signature ASC, i.instruction_path ASC",
|
||||
)
|
||||
.bind(&filter.signatures)
|
||||
.bind(&processing_states)
|
||||
.bind(min_slot)
|
||||
.bind(max_slot)
|
||||
.bind(&filter.program_ids)
|
||||
.bind(&filter.instruction_paths)
|
||||
.bind(limit)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
sqlx::query(INCOMPLETE_DECODE_INPUTS_SQL)
|
||||
.bind(&filter.signatures)
|
||||
.bind(&processing_states)
|
||||
.bind(min_slot)
|
||||
.bind(max_slot)
|
||||
.bind(&filter.program_ids)
|
||||
.bind(&filter.instruction_paths)
|
||||
.bind(limit)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
} else {
|
||||
sqlx::query(
|
||||
"SELECT id, transaction_id, signature, slot, instruction_path, program_id, accounts_json, payload_json, payload_json_hash, processing_state, created_at, updated_at FROM kb_sol_core_instructions WHERE (cardinality($1::text[]) = 0 OR signature = ANY($1)) AND (cardinality($2::text[]) = 0 OR processing_state = ANY($2)) AND ($3::bigint IS NULL OR slot >= $3) AND ($4::bigint IS NULL OR slot <= $4) AND (cardinality($5::text[]) = 0 OR program_id = ANY($5)) AND (cardinality($6::text[]) = 0 OR instruction_path = ANY($6)) ORDER BY slot ASC, signature ASC, instruction_path ASC LIMIT $7",
|
||||
"SELECT id, transaction_id, signature, slot, instruction_path, program_id, stack_height, accounts_json, payload_json, payload_json_hash, processing_state, created_at, updated_at FROM k_sol_core_instructions WHERE (cardinality($1::text[]) = 0 OR signature = ANY($1)) AND (cardinality($2::text[]) = 0 OR processing_state = ANY($2)) AND ($3::bigint IS NULL OR slot >= $3) AND ($4::bigint IS NULL OR slot <= $4) AND (cardinality($5::text[]) = 0 OR program_id = ANY($5)) AND (cardinality($6::text[]) = 0 OR instruction_path = ANY($6)) ORDER BY slot ASC, signature ASC, instruction_path ASC LIMIT $7",
|
||||
)
|
||||
.bind(&filter.signatures)
|
||||
.bind(&processing_states)
|
||||
@@ -456,17 +406,12 @@ pub(crate) async fn list_decode_replay_inputs(
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let instruction_result =
|
||||
crate::postgres::query::core_queries::row_to_core_instruction_row(&row);
|
||||
let instruction_result = row_to_core_instruction_row(&row);
|
||||
let instruction = match instruction_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let input_result = crate::postgres::query::core_queries::load_replay_input_for_instruction(
|
||||
pool,
|
||||
&instruction,
|
||||
)
|
||||
.await;
|
||||
let input_result = load_replay_input_for_instruction(pool, &instruction).await;
|
||||
match input_result {
|
||||
std::result::Result::Ok(value) => output.push(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -479,12 +424,9 @@ pub(crate) async fn update_core_instruction_lifecycle(
|
||||
pool: &sqlx::PgPool,
|
||||
mark: &crate::CoreInstructionLifecycleMark,
|
||||
) -> ks_core::Result<crate::InsertOutcome> {
|
||||
let processing_state =
|
||||
crate::postgres::query::core_queries::instruction_processing_state_to_sql(
|
||||
mark.processing_state,
|
||||
);
|
||||
let processing_state = instruction_processing_state_to_sql(mark.processing_state);
|
||||
let query_result = sqlx::query(
|
||||
"UPDATE kb_sol_core_instructions SET processing_state = $1, processor_name = $2, processor_version = $3, lifecycle_reason = $4, updated_at = NOW() WHERE signature = $5 AND instruction_path = $6",
|
||||
"UPDATE k_sol_core_instructions SET processing_state = $1, processor_name = $2, processor_version = $3, lifecycle_reason = $4, updated_at = NOW() WHERE signature = $5 AND instruction_path = $6",
|
||||
)
|
||||
.bind(processing_state)
|
||||
.bind(mark.processor_name.as_deref())
|
||||
@@ -494,7 +436,7 @@ pub(crate) async fn update_core_instruction_lifecycle(
|
||||
.bind(mark.instruction_path.as_str())
|
||||
.execute(pool)
|
||||
.await;
|
||||
return crate::postgres::query::core_queries::outcome_from_update_result(
|
||||
return outcome_from_update_result(
|
||||
query_result,
|
||||
"postgres core instruction lifecycle update failed",
|
||||
);
|
||||
@@ -549,10 +491,7 @@ fn balance_change_kind_to_sql(kind: crate::CoreBalanceChangeKind) -> &'static st
|
||||
}
|
||||
|
||||
fn sql_slot_from_u64(slot: u64) -> ks_core::Result<i64> {
|
||||
return crate::postgres::query::core_queries::sql_i64_from_u64(
|
||||
slot,
|
||||
"solana slot does not fit into SQL BIGINT",
|
||||
);
|
||||
return sql_i64_from_u64(slot, "solana slot does not fit into SQL BIGINT");
|
||||
}
|
||||
|
||||
fn optional_sql_slot_from_u64(
|
||||
@@ -560,7 +499,7 @@ fn optional_sql_slot_from_u64(
|
||||
) -> ks_core::Result<std::option::Option<i64>> {
|
||||
return match slot {
|
||||
std::option::Option::Some(value) => {
|
||||
let conversion_result = crate::postgres::query::core_queries::sql_slot_from_u64(value);
|
||||
let conversion_result = sql_slot_from_u64(value);
|
||||
match conversion_result {
|
||||
std::result::Result::Ok(converted) => {
|
||||
std::result::Result::Ok(std::option::Option::Some(converted))
|
||||
@@ -586,8 +525,7 @@ fn optional_sql_i32_from_u32(
|
||||
) -> ks_core::Result<std::option::Option<i32>> {
|
||||
return match value {
|
||||
std::option::Option::Some(inner) => {
|
||||
let conversion_result =
|
||||
crate::postgres::query::core_queries::sql_i32_from_u32(inner, message);
|
||||
let conversion_result = sql_i32_from_u32(inner, message);
|
||||
match conversion_result {
|
||||
std::result::Result::Ok(converted) => {
|
||||
std::result::Result::Ok(std::option::Option::Some(converted))
|
||||
@@ -599,6 +537,26 @@ fn optional_sql_i32_from_u32(
|
||||
};
|
||||
}
|
||||
|
||||
fn optional_u32_from_sql_i32(
|
||||
value: std::option::Option<i32>,
|
||||
message: &str,
|
||||
) -> ks_core::Result<std::option::Option<u32>> {
|
||||
return match value {
|
||||
std::option::Option::Some(inner) => {
|
||||
let conversion_result = u32::try_from(inner);
|
||||
match conversion_result {
|
||||
std::result::Result::Ok(converted) => {
|
||||
std::result::Result::Ok(std::option::Option::Some(converted))
|
||||
},
|
||||
std::result::Result::Err(_error) => {
|
||||
std::result::Result::Err(ks_core::Error::db(message))
|
||||
},
|
||||
}
|
||||
},
|
||||
std::option::Option::None => std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
}
|
||||
|
||||
fn sql_i64_from_u64(value: u64, message: &str) -> ks_core::Result<i64> {
|
||||
let conversion_result = i64::try_from(value);
|
||||
return match conversion_result {
|
||||
@@ -663,59 +621,56 @@ fn row_to_core_instruction_row(
|
||||
)));
|
||||
},
|
||||
};
|
||||
let state_result = crate::postgres::query::core_queries::instruction_processing_state_from_sql(
|
||||
state_text.as_str(),
|
||||
);
|
||||
let state_result = instruction_processing_state_from_sql(state_text.as_str());
|
||||
let processing_state = match state_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let id = match crate::postgres::query::core_queries::read_row_value(row, "id") {
|
||||
let id = match read_row_value(row, "id") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let transaction_id =
|
||||
match crate::postgres::query::core_queries::read_row_value(row, "transaction_id") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let signature = match crate::postgres::query::core_queries::read_row_value(row, "signature") {
|
||||
let transaction_id = match read_row_value(row, "transaction_id") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let slot = match crate::postgres::query::core_queries::read_row_value(row, "slot") {
|
||||
let signature = match read_row_value(row, "signature") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let instruction_path =
|
||||
match crate::postgres::query::core_queries::read_row_value(row, "instruction_path") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let program_id = match crate::postgres::query::core_queries::read_row_value(row, "program_id") {
|
||||
let slot = match read_row_value(row, "slot") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let accounts_json =
|
||||
match crate::postgres::query::core_queries::read_row_value(row, "accounts_json") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payload_json =
|
||||
match crate::postgres::query::core_queries::read_row_value(row, "payload_json") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payload_json_hash =
|
||||
match crate::postgres::query::core_queries::read_row_value(row, "payload_json_hash") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let created_at = match crate::postgres::query::core_queries::read_row_value(row, "created_at") {
|
||||
let instruction_path = match read_row_value(row, "instruction_path") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let updated_at = match crate::postgres::query::core_queries::read_row_value(row, "updated_at") {
|
||||
let program_id = match read_row_value(row, "program_id") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let stack_height = match read_row_value(row, "stack_height") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let accounts_json = match read_row_value(row, "accounts_json") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payload_json = match read_row_value(row, "payload_json") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payload_json_hash = match read_row_value(row, "payload_json_hash") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let created_at = match read_row_value(row, "created_at") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let updated_at = match read_row_value(row, "updated_at") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -726,6 +681,7 @@ fn row_to_core_instruction_row(
|
||||
slot,
|
||||
instruction_path,
|
||||
program_id,
|
||||
stack_height,
|
||||
accounts_json,
|
||||
payload_json,
|
||||
payload_json_hash,
|
||||
@@ -753,8 +709,8 @@ async fn load_replay_input_for_instruction(
|
||||
instruction: &crate::CoreInstructionRow,
|
||||
) -> ks_core::Result<crate::MdCoreInstructionReplayInput> {
|
||||
let transaction_context_result =
|
||||
sqlx::query_as::<sqlx::Postgres, (bool, std::option::Option<serde_json::Value>)>(
|
||||
"SELECT failed, err_json FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1",
|
||||
sqlx::query_as::<sqlx::Postgres, (bool, std::option::Option<serde_json::Value>, std::option::Option<i64>)>(
|
||||
"SELECT failed, err_json, block_time FROM k_sol_core_transactions WHERE signature = $1 LIMIT 1",
|
||||
)
|
||||
.bind(instruction.signature.as_str())
|
||||
.fetch_one(pool)
|
||||
@@ -767,63 +723,59 @@ async fn load_replay_input_for_instruction(
|
||||
)));
|
||||
},
|
||||
};
|
||||
let account_keys_json_result = crate::postgres::query::core_queries::load_account_keys_json(
|
||||
pool,
|
||||
instruction.signature.as_str(),
|
||||
)
|
||||
.await;
|
||||
let account_keys_json_result =
|
||||
load_account_keys_json(pool, instruction.signature.as_str()).await;
|
||||
let account_keys_json = match account_keys_json_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let outer_instructions_json_result =
|
||||
crate::postgres::query::core_queries::load_outer_instructions_json(
|
||||
pool,
|
||||
instruction.signature.as_str(),
|
||||
)
|
||||
.await;
|
||||
let outer_instructions_json = match outer_instructions_json_result {
|
||||
let top_level_instructions_json_result =
|
||||
load_top_level_instructions_json(pool, instruction.signature.as_str()).await;
|
||||
let top_level_instructions_json = match top_level_instructions_json_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let inner_instructions_json_result =
|
||||
crate::postgres::query::core_queries::load_inner_instructions_json(
|
||||
pool,
|
||||
instruction.signature.as_str(),
|
||||
instruction.instruction_path.as_str(),
|
||||
)
|
||||
.await;
|
||||
let inner_instructions_json_result = load_inner_instructions_json(
|
||||
pool,
|
||||
instruction.signature.as_str(),
|
||||
instruction.instruction_path.as_str(),
|
||||
)
|
||||
.await;
|
||||
let inner_instructions_json = match inner_instructions_json_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logs_json_result =
|
||||
crate::postgres::query::core_queries::load_logs_json(pool, instruction.signature.as_str())
|
||||
.await;
|
||||
let logs_json_result = load_logs_json(pool, instruction.signature.as_str()).await;
|
||||
let logs_json = match logs_json_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let balance_changes_json_result =
|
||||
crate::postgres::query::core_queries::load_balance_changes_json(
|
||||
pool,
|
||||
instruction.signature.as_str(),
|
||||
)
|
||||
.await;
|
||||
load_balance_changes_json(pool, instruction.signature.as_str()).await;
|
||||
let balance_changes_json = match balance_changes_json_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let replay_input_key = format!("{}:{}", instruction.signature, instruction.instruction_path);
|
||||
let slot_result = crate::postgres::query::core_queries::u64_from_sql_i64(
|
||||
instruction.slot,
|
||||
"core instruction slot is negative",
|
||||
let return_data_result = load_return_data(pool, instruction.signature.as_str()).await;
|
||||
let return_data = match return_data_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let stack_height_result = optional_u32_from_sql_i32(
|
||||
instruction.stack_height,
|
||||
"core instruction stack height is negative",
|
||||
);
|
||||
let stack_height = match stack_height_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let replay_input_key = format!("{}:{}", instruction.signature, instruction.instruction_path);
|
||||
let slot_result = u64_from_sql_i64(instruction.slot, "core instruction slot is negative");
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::MdCoreInstructionReplayInput::new(
|
||||
let input_result = crate::MdCoreInstructionReplayInput::new(
|
||||
replay_input_key,
|
||||
instruction.signature.clone(),
|
||||
slot,
|
||||
@@ -835,35 +787,45 @@ async fn load_replay_input_for_instruction(
|
||||
instruction.accounts_json.clone(),
|
||||
instruction.payload_json.clone(),
|
||||
instruction.payload_json_hash.clone(),
|
||||
outer_instructions_json,
|
||||
top_level_instructions_json,
|
||||
inner_instructions_json,
|
||||
logs_json,
|
||||
balance_changes_json,
|
||||
);
|
||||
let input = match input_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return input.with_core_context(
|
||||
transaction_context.2,
|
||||
std::option::Option::None,
|
||||
stack_height,
|
||||
return_data,
|
||||
);
|
||||
}
|
||||
|
||||
async fn load_account_keys_json(
|
||||
pool: &sqlx::PgPool,
|
||||
signature: &str,
|
||||
) -> ks_core::Result<serde_json::Value> {
|
||||
return crate::postgres::query::core_queries::load_json_aggregate(
|
||||
return load_json_aggregate(
|
||||
pool,
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('accountIndex', account_index, 'accountKey', account_key, 'source', source, 'writable', writable, 'signer', signer, 'executable', executable) ORDER BY account_index), '[]'::jsonb) FROM kb_sol_core_account_keys WHERE signature = $1",
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('accountIndex', account_index, 'accountKey', account_key, 'source', source, 'writable', writable, 'signer', signer, 'executable', executable) ORDER BY account_index), '[]'::jsonb) FROM k_sol_core_account_keys WHERE signature = $1",
|
||||
signature,
|
||||
"postgres core account keys context query failed",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn load_outer_instructions_json(
|
||||
async fn load_top_level_instructions_json(
|
||||
pool: &sqlx::PgPool,
|
||||
signature: &str,
|
||||
) -> ks_core::Result<serde_json::Value> {
|
||||
return crate::postgres::query::core_queries::load_json_aggregate(
|
||||
return load_json_aggregate(
|
||||
pool,
|
||||
crate::postgres::query::core_queries::OUTER_INSTRUCTIONS_CONTEXT_SQL,
|
||||
TOP_LEVEL_INSTRUCTIONS_CONTEXT_SQL,
|
||||
signature,
|
||||
"postgres core outer instructions context query failed",
|
||||
"postgres core top-level instructions context query failed",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -875,7 +837,7 @@ async fn load_inner_instructions_json(
|
||||
) -> ks_core::Result<serde_json::Value> {
|
||||
let like_pattern = format!("{instruction_path}/%");
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, serde_json::Value>(
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('parentInstructionPath', parent_instruction_path, 'instructionPath', instruction_path, 'programId', program_id, 'accounts', accounts_json, 'payload', payload_json) ORDER BY instruction_path), '[]'::jsonb) FROM kb_sol_core_inner_instructions WHERE signature = $1 AND (parent_instruction_path = $2 OR instruction_path LIKE $3)",
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('parentInstructionPath', parent_instruction_path, 'instructionPath', instruction_path, 'programId', program_id, 'stackHeight', stack_height, 'accounts', accounts_json, 'payload', payload_json, 'payloadHash', payload_json_hash) ORDER BY instruction_path), '[]'::jsonb) FROM k_sol_core_inner_instructions WHERE signature = $1 AND (parent_instruction_path = $2 OR instruction_path LIKE $3)",
|
||||
)
|
||||
.bind(signature)
|
||||
.bind(instruction_path)
|
||||
@@ -890,13 +852,39 @@ async fn load_inner_instructions_json(
|
||||
};
|
||||
}
|
||||
|
||||
async fn load_return_data(
|
||||
pool: &sqlx::PgPool,
|
||||
signature: &str,
|
||||
) -> ks_core::Result<std::option::Option<ks_lib::MdCanonicalReturnData>> {
|
||||
let query_result = sqlx::query_as::<sqlx::Postgres, (std::string::String, std::string::String)>(
|
||||
"SELECT program_id, data_base64 FROM k_sol_core_return_data WHERE signature = $1 LIMIT 1",
|
||||
)
|
||||
.bind(signature)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
return match query_result {
|
||||
std::result::Result::Ok(std::option::Option::Some((program_id, data_base64))) => {
|
||||
std::result::Result::Ok(std::option::Option::Some(ks_lib::MdCanonicalReturnData {
|
||||
program_id,
|
||||
data_base64,
|
||||
}))
|
||||
},
|
||||
std::result::Result::Ok(std::option::Option::None) => {
|
||||
std::result::Result::Ok(std::option::Option::None)
|
||||
},
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres core return-data context query failed: {error}"
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
async fn load_logs_json(
|
||||
pool: &sqlx::PgPool,
|
||||
signature: &str,
|
||||
) -> ks_core::Result<serde_json::Value> {
|
||||
return crate::postgres::query::core_queries::load_json_aggregate(
|
||||
return load_json_aggregate(
|
||||
pool,
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('logIndex', log_index, 'instructionPath', instruction_path, 'programId', program_id, 'text', log_text) ORDER BY log_index), '[]'::jsonb) FROM kb_sol_core_logs WHERE signature = $1",
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('logIndex', log_index, 'instructionPath', instruction_path, 'programId', program_id, 'text', log_text) ORDER BY log_index), '[]'::jsonb) FROM k_sol_core_logs WHERE signature = $1",
|
||||
signature,
|
||||
"postgres core logs context query failed",
|
||||
)
|
||||
@@ -907,9 +895,9 @@ async fn load_balance_changes_json(
|
||||
pool: &sqlx::PgPool,
|
||||
signature: &str,
|
||||
) -> ks_core::Result<serde_json::Value> {
|
||||
return crate::postgres::query::core_queries::load_json_aggregate(
|
||||
return load_json_aggregate(
|
||||
pool,
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('balanceChangeIndex', balance_change_index, 'balanceKind', balance_kind, 'accountIndex', account_index, 'accountKey', account_key, 'mint', mint, 'owner', owner, 'pre', pre_balance_json, 'post', post_balance_json, 'delta', delta_json) ORDER BY balance_change_index), '[]'::jsonb) FROM kb_sol_core_balance_changes WHERE signature = $1",
|
||||
"SELECT COALESCE(jsonb_agg(jsonb_build_object('balanceChangeIndex', balance_change_index, 'balanceKind', balance_kind, 'accountIndex', account_index, 'accountKey', account_key, 'mint', mint, 'owner', owner, 'pre', pre_balance_json, 'post', post_balance_json, 'delta', delta_json) ORDER BY balance_change_index), '[]'::jsonb) FROM k_sol_core_balance_changes WHERE signature = $1",
|
||||
signature,
|
||||
"postgres core balance changes context query failed",
|
||||
)
|
||||
@@ -937,9 +925,32 @@ async fn load_json_aggregate(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn outer_instruction_context_query_is_read_only_and_numeric() {
|
||||
let normalized = crate::postgres::query::core_queries::OUTER_INSTRUCTIONS_CONTEXT_SQL
|
||||
.to_ascii_lowercase();
|
||||
fn incomplete_decode_input_query_projects_every_core_instruction_row_column() {
|
||||
for column in [
|
||||
"i.id",
|
||||
"i.transaction_id",
|
||||
"i.signature",
|
||||
"i.slot",
|
||||
"i.instruction_path",
|
||||
"i.program_id",
|
||||
"i.stack_height",
|
||||
"i.accounts_json",
|
||||
"i.payload_json",
|
||||
"i.payload_json_hash",
|
||||
"i.processing_state",
|
||||
"i.created_at",
|
||||
"i.updated_at",
|
||||
] {
|
||||
assert!(
|
||||
super::INCOMPLETE_DECODE_INPUTS_SQL.contains(column),
|
||||
"incomplete decode input query is missing {column}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_instruction_context_query_is_read_only_and_numeric() {
|
||||
let normalized = super::TOP_LEVEL_INSTRUCTIONS_CONTEXT_SQL.to_ascii_lowercase();
|
||||
assert!(normalized.starts_with("select "));
|
||||
assert!(normalized.contains("order by instruction_path::bigint, instruction_path asc"));
|
||||
assert!(!normalized.contains("create "));
|
||||
@@ -949,7 +960,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn instruction_processing_state_serializes_to_lower_snake_case() {
|
||||
let value = crate::postgres::query::core_queries::instruction_processing_state_to_sql(
|
||||
let value = super::instruction_processing_state_to_sql(
|
||||
crate::CoreInstructionProcessingState::ReplayRequested,
|
||||
);
|
||||
assert_eq!(value, "replay_requested");
|
||||
@@ -957,23 +968,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn account_key_source_serializes_to_lower_snake_case() {
|
||||
let value = crate::postgres::query::core_queries::account_key_source_to_sql(
|
||||
crate::CoreAccountKeySource::LoadedWritable,
|
||||
);
|
||||
let value = super::account_key_source_to_sql(crate::CoreAccountKeySource::LoadedWritable);
|
||||
assert_eq!(value, "loaded_writable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_change_kind_serializes_to_lower_snake_case() {
|
||||
let value = crate::postgres::query::core_queries::balance_change_kind_to_sql(
|
||||
crate::CoreBalanceChangeKind::NativeLamports,
|
||||
);
|
||||
let value = super::balance_change_kind_to_sql(crate::CoreBalanceChangeKind::NativeLamports);
|
||||
assert_eq!(value, "native_lamports");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_slot_rejects_values_above_bigint() {
|
||||
let result = crate::postgres::query::core_queries::sql_slot_from_u64(u64::MAX);
|
||||
let result = super::sql_slot_from_u64(u64::MAX);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -994,7 +1001,7 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("unexpected connect error: {error}"),
|
||||
};
|
||||
let schema_result = store.initialize_core_store_schema().await;
|
||||
let schema_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = schema_result {
|
||||
panic!("unexpected schema error: {error}");
|
||||
}
|
||||
@@ -1059,19 +1066,21 @@ mod tests {
|
||||
});
|
||||
let target = match target {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("expected replay input for outer instruction 2"),
|
||||
std::option::Option::None => {
|
||||
panic!("expected replay input for top-level instruction 2")
|
||||
},
|
||||
};
|
||||
let outer = match target.outer_instructions_json.as_array() {
|
||||
let top_level = match target.top_level_instructions_json.as_array() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("outer instruction context must be an array"),
|
||||
std::option::Option::None => panic!("top-level instruction context must be an array"),
|
||||
};
|
||||
let ordered_indexes = outer
|
||||
let ordered_indexes = top_level
|
||||
.iter()
|
||||
.filter_map(|value| return value.get("instructionIndex"))
|
||||
.filter_map(serde_json::Value::as_i64)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
assert_eq!(ordered_indexes, vec![0, 2, 10]);
|
||||
assert!(outer.iter().any(|value| {
|
||||
assert!(top_level.iter().any(|value| {
|
||||
return value.get("instructionPath").and_then(serde_json::Value::as_str)
|
||||
== std::option::Option::Some("2");
|
||||
}));
|
||||
@@ -1109,8 +1118,13 @@ mod tests {
|
||||
}
|
||||
|
||||
fn test_core_transaction(signature: &str) -> crate::CoreTransactionInsert {
|
||||
let result =
|
||||
crate::CoreTransactionInsert::new(signature, 1, false, std::option::Option::None);
|
||||
let result = crate::CoreTransactionInsert::new(
|
||||
signature,
|
||||
1,
|
||||
std::option::Option::None,
|
||||
false,
|
||||
std::option::Option::None,
|
||||
);
|
||||
match result {
|
||||
std::result::Result::Ok(value) => return value,
|
||||
std::result::Result::Err(error) => {
|
||||
@@ -1144,6 +1158,7 @@ mod tests {
|
||||
1,
|
||||
instruction_path,
|
||||
"program",
|
||||
std::option::Option::None,
|
||||
serde_json::json!([0]),
|
||||
serde_json::json!({"dataBase64": "YWJj"}),
|
||||
);
|
||||
@@ -1162,6 +1177,7 @@ mod tests {
|
||||
"0",
|
||||
"0/0",
|
||||
"inner_program",
|
||||
std::option::Option::None,
|
||||
serde_json::json!([0]),
|
||||
serde_json::json!({"data": "inner"}),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: ks-store/src/postgres/query/decode_pipeline_queries.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
//! PostgreSQL queries for contextual decode, coverage and materialization persistence.
|
||||
|
||||
use sqlx::Row; // rust-rules: trait-import
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct MaterializedEventDatabaseRow {
|
||||
struct MaterializedOutputDatabaseRow {
|
||||
processor_name: std::string::String,
|
||||
processor_version: std::string::String,
|
||||
input_key: std::string::String,
|
||||
@@ -22,48 +22,6 @@ struct MaterializedEventDatabaseRow {
|
||||
updated_at: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_decode_store_schema(pool: &sqlx::PgPool) -> ks_core::Result<()> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "apply_decode_store_schema", statement_count = crate::decode_store_schema_statements().len(), "apply PostgreSQL decode store schema");
|
||||
let validation_result = crate::validate_decode_store_table_names();
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let transaction_result = pool.begin().await;
|
||||
let mut transaction = match transaction_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres decode store schema transaction failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let lock_result = sqlx::query("SELECT pg_advisory_xact_lock($1)")
|
||||
.bind(crate::STORE_SCHEMA_ADVISORY_LOCK_ID)
|
||||
.execute(&mut *transaction)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = lock_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres decode store schema advisory lock failed: {error}"
|
||||
)));
|
||||
}
|
||||
for statement in crate::decode_store_schema_statements() {
|
||||
let execution_result = sqlx::query(statement).execute(&mut *transaction).await;
|
||||
if let std::result::Result::Err(error) = execution_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres decode store schema initialization failed: {error}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
let commit_result = transaction.commit().await;
|
||||
if let std::result::Result::Err(error) = commit_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres decode store schema commit failed: {error}"
|
||||
)));
|
||||
}
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "apply_decode_store_schema", committed = true, "PostgreSQL decode store schema applied");
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
pub(crate) async fn list_decode_inputs(
|
||||
pool: &sqlx::PgPool,
|
||||
filter: &crate::DecodeSelectionFilter,
|
||||
@@ -87,19 +45,19 @@ pub(crate) async fn list_decode_inputs(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn list_materialized_events(
|
||||
pub(crate) async fn list_materialized_outputs(
|
||||
pool: &sqlx::PgPool,
|
||||
filter: &crate::MaterializedEventFilter,
|
||||
) -> ks_core::Result<std::vec::Vec<crate::MaterializedEventQueryRow>> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "list_materialized_events", processor_name = ?filter.processor_name, materialized_family = ?filter.materialized_family, signature_contains = ?filter.signature_contains, limit = filter.limit, "query bounded PostgreSQL materialized events");
|
||||
if filter.limit == 0 || filter.limit > crate::MAX_MATERIALIZED_EVENT_QUERY_ROWS {
|
||||
filter: &crate::MaterializedOutputFilter,
|
||||
) -> ks_core::Result<std::vec::Vec<crate::MaterializedOutputQueryRow>> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "list_materialized_outputs", processor_name = ?filter.processor_name, materialized_family = ?filter.materialized_family, signature_contains = ?filter.signature_contains, limit = filter.limit, "query bounded PostgreSQL materialized outputs");
|
||||
if filter.limit == 0 || filter.limit > crate::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"materialized event query limit must be between 1 and {}",
|
||||
crate::MAX_MATERIALIZED_EVENT_QUERY_ROWS
|
||||
"materialized output query limit must be between 1 and {}",
|
||||
crate::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS
|
||||
)));
|
||||
}
|
||||
let query_result = sqlx::query_as::<sqlx::Postgres, MaterializedEventDatabaseRow>(
|
||||
"SELECT processor_name, processor_version, input_key, output_key, source_event_key, source_decoder_name, source_decoder_version, signature, slot, materialized_family, payload_jsonb AS payload_json, created_at::text AS created_at, updated_at::text AS updated_at FROM kb_sol_mat_events WHERE ($1::text IS NULL OR processor_name = $1) AND ($2::text IS NULL OR materialized_family = $2) AND ($3::text IS NULL OR POSITION(LOWER($3) IN LOWER(signature)) > 0) ORDER BY slot DESC, id DESC LIMIT $4",
|
||||
let query_result = sqlx::query_as::<sqlx::Postgres, MaterializedOutputDatabaseRow>(
|
||||
"SELECT processor_name, processor_version, input_key, output_key, source_event_key, source_decoder_name, source_decoder_version, signature, slot, materialized_family, payload_jsonb AS payload_json, created_at::text AS created_at, updated_at::text AS updated_at FROM k_sol_mat_outputs WHERE ($1::text IS NULL OR processor_name = $1) AND ($2::text IS NULL OR materialized_family = $2) AND ($3::text IS NULL OR POSITION(LOWER($3) IN LOWER(signature)) > 0) ORDER BY slot DESC, id DESC LIMIT $4",
|
||||
)
|
||||
.bind(filter.processor_name.as_deref())
|
||||
.bind(filter.materialized_family.as_deref())
|
||||
@@ -111,7 +69,7 @@ pub(crate) async fn list_materialized_events(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres materialized event query failed: {error}"
|
||||
"postgres materialized output query failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
@@ -122,11 +80,11 @@ pub(crate) async fn list_materialized_events(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres materialized event slot conversion failed: {error}"
|
||||
"postgres materialized output slot conversion failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
output.push(crate::MaterializedEventQueryRow {
|
||||
output.push(crate::MaterializedOutputQueryRow {
|
||||
processor_name: row.processor_name,
|
||||
processor_version: row.processor_version,
|
||||
input_key: row.input_key,
|
||||
@@ -142,7 +100,7 @@ pub(crate) async fn list_materialized_events(
|
||||
updated_at: row.updated_at,
|
||||
});
|
||||
}
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "list_materialized_events", row_count = output.len(), "bounded PostgreSQL materialized events loaded");
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "list_materialized_outputs", row_count = output.len(), "bounded PostgreSQL materialized outputs loaded");
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
@@ -152,7 +110,7 @@ pub(crate) async fn is_decode_current(
|
||||
) -> ks_core::Result<bool> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "is_decode_current", stage = %identity.stage, processor_name = %identity.processor_name, processor_version = %identity.processor_version, input_key = %identity.input_key, input_hash = %identity.input_hash, "query PostgreSQL processing ledger current state");
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, bool>(
|
||||
"SELECT EXISTS(SELECT 1 FROM kb_sol_ops_processing_ledger WHERE stage = $1 AND processor_name = $2 AND processor_version = $3 AND input_key = $4 AND input_hash = $5 AND status = 'succeeded')",
|
||||
"SELECT EXISTS(SELECT 1 FROM k_sol_ops_processing_ledger WHERE stage = $1 AND processor_name = $2 AND processor_version = $3 AND input_key = $4 AND input_hash = $5 AND status = 'succeeded')",
|
||||
)
|
||||
.bind(identity.stage.as_str())
|
||||
.bind(identity.processor_name.as_str())
|
||||
@@ -236,7 +194,7 @@ pub(crate) async fn persist_decode_coverage_declarations(
|
||||
)));
|
||||
}
|
||||
let existing_result = sqlx::query(
|
||||
"SELECT program_id, surface_code, entry_kind, entry_code, discriminator_hex, historical FROM kb_sol_decode_coverage_declarations WHERE processor_name = $1 AND processor_version = $2 FOR UPDATE",
|
||||
"SELECT program_id, surface_code, entry_kind, entry_code, discriminator_hex, historical FROM k_sol_decode_coverage_declarations WHERE processor_name = $1 AND processor_version = $2 FOR UPDATE",
|
||||
)
|
||||
.bind(processor_name)
|
||||
.bind(processor_version)
|
||||
@@ -276,7 +234,7 @@ pub(crate) async fn persist_decode_coverage_declarations(
|
||||
match existing_historical {
|
||||
std::option::Option::None => {
|
||||
let insert_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_decode_coverage_declarations (processor_name, processor_version, program_id, surface_code, entry_kind, entry_code, discriminator_hex, historical) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
"INSERT INTO k_sol_decode_coverage_declarations (processor_name, processor_version, program_id, surface_code, entry_kind, entry_code, discriminator_hex, historical) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
)
|
||||
.bind(declaration.processor_name.as_str())
|
||||
.bind(declaration.processor_version.as_str())
|
||||
@@ -300,7 +258,7 @@ pub(crate) async fn persist_decode_coverage_declarations(
|
||||
},
|
||||
std::option::Option::Some(_historical) => {
|
||||
let update_result = sqlx::query(
|
||||
"UPDATE kb_sol_decode_coverage_declarations SET historical = $1, updated_at = NOW() WHERE processor_name = $2 AND processor_version = $3 AND program_id = $4 AND COALESCE(surface_code, '') = COALESCE($5::text, '') AND entry_kind = $6 AND entry_code = $7 AND COALESCE(discriminator_hex, '') = COALESCE($8::text, '')",
|
||||
"UPDATE k_sol_decode_coverage_declarations SET historical = $1, updated_at = NOW() WHERE processor_name = $2 AND processor_version = $3 AND program_id = $4 AND COALESCE(surface_code, '') = COALESCE($5::text, '') AND entry_kind = $6 AND entry_code = $7 AND COALESCE(discriminator_hex, '') = COALESCE($8::text, '')",
|
||||
)
|
||||
.bind(declaration.historical)
|
||||
.bind(declaration.processor_name.as_str())
|
||||
@@ -323,7 +281,7 @@ pub(crate) async fn persist_decode_coverage_declarations(
|
||||
}
|
||||
for (key, _historical) in existing {
|
||||
let delete_result = sqlx::query(
|
||||
"DELETE FROM kb_sol_decode_coverage_declarations WHERE processor_name = $1 AND processor_version = $2 AND program_id = $3 AND COALESCE(surface_code, '') = COALESCE($4::text, '') AND entry_kind = $5 AND entry_code = $6 AND COALESCE(discriminator_hex, '') = COALESCE($7::text, '')",
|
||||
"DELETE FROM k_sol_decode_coverage_declarations WHERE processor_name = $1 AND processor_version = $2 AND program_id = $3 AND COALESCE(surface_code, '') = COALESCE($4::text, '') AND entry_kind = $5 AND entry_code = $6 AND COALESCE(discriminator_hex, '') = COALESCE($7::text, '')",
|
||||
)
|
||||
.bind(processor_name)
|
||||
.bind(processor_version)
|
||||
@@ -372,7 +330,7 @@ pub(crate) async fn persist_decode_result(
|
||||
},
|
||||
};
|
||||
let delete_events_result = sqlx::query(
|
||||
"DELETE FROM kb_sol_decode_events WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
"DELETE FROM k_sol_decode_events WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
)
|
||||
.bind(bundle.ledger_identity.processor_name.as_str())
|
||||
.bind(bundle.ledger_identity.processor_version.as_str())
|
||||
@@ -385,7 +343,7 @@ pub(crate) async fn persist_decode_result(
|
||||
)));
|
||||
}
|
||||
let delete_coverage_result = sqlx::query(
|
||||
"DELETE FROM kb_sol_decode_coverage_observations WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
"DELETE FROM k_sol_decode_coverage_observations WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
)
|
||||
.bind(bundle.ledger_identity.processor_name.as_str())
|
||||
.bind(bundle.ledger_identity.processor_version.as_str())
|
||||
@@ -407,8 +365,24 @@ pub(crate) async fn persist_decode_result(
|
||||
)));
|
||||
},
|
||||
};
|
||||
let schema_kind = observation
|
||||
.schema_provenance
|
||||
.as_ref()
|
||||
.map(|value| return value.schema_kind.as_str());
|
||||
let schema_id = observation
|
||||
.schema_provenance
|
||||
.as_ref()
|
||||
.map(|value| return value.schema_id.as_str());
|
||||
let schema_version = observation
|
||||
.schema_provenance
|
||||
.as_ref()
|
||||
.and_then(|value| return value.schema_version.as_deref());
|
||||
let schema_hash = observation
|
||||
.schema_provenance
|
||||
.as_ref()
|
||||
.map(|value| return value.schema_hash.as_str());
|
||||
let insert_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_decode_events (processor_name, processor_version, input_key, input_hash, event_key, signature, slot, instruction_path, program_id, protocol_code, surface_code, event_code, event_name, event_family, source_kind, confidence, proof_kind, proof_jsonb, payload_jsonb, transaction_failed, transaction_error_jsonb, observation_committed) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) ON CONFLICT (processor_name, processor_version, input_key, event_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, payload_jsonb = EXCLUDED.payload_jsonb, proof_jsonb = EXCLUDED.proof_jsonb, transaction_failed = EXCLUDED.transaction_failed, transaction_error_jsonb = EXCLUDED.transaction_error_jsonb, observation_committed = EXCLUDED.observation_committed, updated_at = NOW()",
|
||||
"INSERT INTO k_sol_decode_events (processor_name, processor_version, input_key, input_hash, event_key, signature, slot, instruction_path, program_id, protocol_code, surface_code, event_code, event_name, event_family, source_kind, confidence, proof_kind, proof_jsonb, payload_jsonb, schema_kind, schema_id, schema_version, schema_hash, transaction_failed, transaction_error_jsonb, observation_committed) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26) ON CONFLICT (processor_name, processor_version, input_key, event_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, payload_jsonb = EXCLUDED.payload_jsonb, proof_jsonb = EXCLUDED.proof_jsonb, schema_kind = EXCLUDED.schema_kind, schema_id = EXCLUDED.schema_id, schema_version = EXCLUDED.schema_version, schema_hash = EXCLUDED.schema_hash, transaction_failed = EXCLUDED.transaction_failed, transaction_error_jsonb = EXCLUDED.transaction_error_jsonb, observation_committed = EXCLUDED.observation_committed, updated_at = NOW()",
|
||||
)
|
||||
.bind(observation.processor_name.as_str())
|
||||
.bind(observation.processor_version.as_str())
|
||||
@@ -429,6 +403,10 @@ pub(crate) async fn persist_decode_result(
|
||||
.bind(observation.proof_kind.as_str())
|
||||
.bind(&observation.proof_json)
|
||||
.bind(&observation.payload_json)
|
||||
.bind(schema_kind)
|
||||
.bind(schema_id)
|
||||
.bind(schema_version)
|
||||
.bind(schema_hash)
|
||||
.bind(observation.transaction_failed)
|
||||
.bind(observation.transaction_error.clone())
|
||||
.bind(observation.observation_committed)
|
||||
@@ -467,7 +445,7 @@ pub(crate) async fn persist_decode_result(
|
||||
_unknown => "decoded",
|
||||
};
|
||||
let lifecycle_result = sqlx::query(
|
||||
"UPDATE kb_sol_core_instructions SET processing_state = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN 'decoded' WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $1 ELSE processing_state END, processor_name = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN $2 WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $2 ELSE processor_name END, processor_version = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN $3 WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $3 ELSE processor_version END, lifecycle_reason = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN $4 WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $4 ELSE lifecycle_reason END, updated_at = NOW() WHERE signature = $5 AND instruction_path = $6",
|
||||
"UPDATE k_sol_core_instructions SET processing_state = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN 'decoded' WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $1 ELSE processing_state END, processor_name = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN $2 WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $2 ELSE processor_name END, processor_version = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN $3 WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $3 ELSE processor_version END, lifecycle_reason = CASE WHEN $1 = 'decoded' AND processing_state <> 'materialized' THEN $4 WHEN $1 IN ('ignored', 'failed') AND processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $4 ELSE lifecycle_reason END, updated_at = NOW() WHERE signature = $5 AND instruction_path = $6",
|
||||
)
|
||||
.bind(lifecycle_state)
|
||||
.bind(bundle.ledger_identity.processor_name.as_str())
|
||||
@@ -537,7 +515,7 @@ pub(crate) async fn mark_decode_failed(
|
||||
},
|
||||
};
|
||||
let lifecycle_result = sqlx::query(
|
||||
"UPDATE kb_sol_core_instructions SET processing_state = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN 'failed' ELSE processing_state END, processor_name = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $1 ELSE processor_name END, processor_version = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $2 ELSE processor_version END, lifecycle_reason = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $3 ELSE lifecycle_reason END, updated_at = NOW() WHERE signature = $4 AND instruction_path = $5",
|
||||
"UPDATE k_sol_core_instructions SET processing_state = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN 'failed' ELSE processing_state END, processor_name = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $1 ELSE processor_name END, processor_version = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $2 ELSE processor_version END, lifecycle_reason = CASE WHEN processing_state IN ('pending', 'failed', 'replay_requested', 'ignored') THEN $3 ELSE lifecycle_reason END, updated_at = NOW() WHERE signature = $4 AND instruction_path = $5",
|
||||
)
|
||||
.bind(failure.ledger_identity.processor_name.as_str())
|
||||
.bind(failure.ledger_identity.processor_version.as_str())
|
||||
@@ -610,7 +588,7 @@ pub(crate) async fn persist_materialization_result(
|
||||
},
|
||||
};
|
||||
let delete_result = sqlx::query(
|
||||
"DELETE FROM kb_sol_mat_events WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
"DELETE FROM k_sol_mat_outputs WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
)
|
||||
.bind(bundle.ledger_identity.processor_name.as_str())
|
||||
.bind(bundle.ledger_identity.processor_version.as_str())
|
||||
@@ -633,7 +611,7 @@ pub(crate) async fn persist_materialization_result(
|
||||
},
|
||||
};
|
||||
let insert_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_mat_events (processor_name, processor_version, input_key, input_hash, output_key, source_event_key, source_decoder_name, source_decoder_version, source_decode_input_key, signature, slot, materialized_family, payload_jsonb) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) ON CONFLICT (processor_name, processor_version, input_key, output_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, source_decoder_name = EXCLUDED.source_decoder_name, source_decoder_version = EXCLUDED.source_decoder_version, source_decode_input_key = EXCLUDED.source_decode_input_key, payload_jsonb = EXCLUDED.payload_jsonb, updated_at = NOW()",
|
||||
"INSERT INTO k_sol_mat_outputs (processor_name, processor_version, input_key, input_hash, output_key, source_event_key, source_decoder_name, source_decoder_version, source_decode_input_key, signature, slot, materialized_family, payload_jsonb) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) ON CONFLICT (processor_name, processor_version, input_key, output_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, source_decoder_name = EXCLUDED.source_decoder_name, source_decoder_version = EXCLUDED.source_decoder_version, source_decode_input_key = EXCLUDED.source_decode_input_key, payload_jsonb = EXCLUDED.payload_jsonb, updated_at = NOW()",
|
||||
)
|
||||
.bind(output.processor_name.as_str())
|
||||
.bind(output.processor_version.as_str())
|
||||
@@ -670,7 +648,7 @@ pub(crate) async fn persist_materialization_result(
|
||||
}
|
||||
if !bundle.outputs.is_empty() {
|
||||
let lifecycle_result = sqlx::query(
|
||||
"UPDATE kb_sol_core_instructions SET processing_state = 'materialized', processor_name = $1, processor_version = $2, lifecycle_reason = $3, updated_at = NOW() WHERE signature = $4 AND instruction_path = $5",
|
||||
"UPDATE k_sol_core_instructions SET processing_state = 'materialized', processor_name = $1, processor_version = $2, lifecycle_reason = $3, updated_at = NOW() WHERE signature = $4 AND instruction_path = $5",
|
||||
)
|
||||
.bind(bundle.ledger_identity.processor_name.as_str())
|
||||
.bind(bundle.ledger_identity.processor_version.as_str())
|
||||
@@ -729,7 +707,7 @@ pub(crate) async fn list_decode_coverage_summary(
|
||||
));
|
||||
}
|
||||
let query_result = sqlx::query(
|
||||
"WITH declared AS (SELECT processor_name, processor_version, program_id, surface_code, entry_code, COUNT(*)::BIGINT AS declared_count FROM kb_sol_decode_coverage_declarations WHERE ($1::text IS NULL OR processor_name = $1) AND ($2::text IS NULL OR processor_version = $2) GROUP BY processor_name, processor_version, program_id, surface_code, entry_code), observed AS (SELECT processor_name, processor_version, program_id, surface_code, COALESCE(entry_code, 'unknown') AS entry_code, COUNT(*)::BIGINT AS observed_count, COUNT(*) FILTER (WHERE recognized)::BIGINT AS recognized_count, COALESCE(SUM(decoded_count), 0)::BIGINT AS decoded_count, COALESCE(SUM(materialized_count), 0)::BIGINT AS materialized_count, COALESCE(SUM(error_count), 0)::BIGINT AS error_count, COUNT(*) FILTER (WHERE status = 'unsupported' OR entry_code IS NULL)::BIGINT AS unknown_count, COUNT(*) FILTER (WHERE NOT transaction_failed)::BIGINT AS successful_transaction_count, COUNT(*) FILTER (WHERE transaction_failed)::BIGINT AS failed_transaction_count FROM kb_sol_decode_coverage_observations WHERE ($1::text IS NULL OR processor_name = $1) AND ($2::text IS NULL OR processor_version = $2) GROUP BY processor_name, processor_version, program_id, surface_code, COALESCE(entry_code, 'unknown')) SELECT COALESCE(d.processor_name, o.processor_name) AS processor_name, COALESCE(d.processor_version, o.processor_version) AS processor_version, COALESCE(d.program_id, o.program_id) AS program_id, COALESCE(d.surface_code, o.surface_code) AS surface_code, COALESCE(d.entry_code, o.entry_code) AS entry_code, COALESCE(d.declared_count, 0)::BIGINT AS declared_count, COALESCE(o.observed_count, 0)::BIGINT AS observed_count, COALESCE(o.recognized_count, 0)::BIGINT AS recognized_count, COALESCE(o.decoded_count, 0)::BIGINT AS decoded_count, COALESCE(o.materialized_count, 0)::BIGINT AS materialized_count, COALESCE(o.error_count, 0)::BIGINT AS error_count, COALESCE(o.unknown_count, 0)::BIGINT AS unknown_count, COALESCE(o.successful_transaction_count, 0)::BIGINT AS successful_transaction_count, COALESCE(o.failed_transaction_count, 0)::BIGINT AS failed_transaction_count FROM declared d FULL OUTER JOIN observed o ON d.processor_name = o.processor_name AND d.processor_version = o.processor_version AND d.program_id = o.program_id AND COALESCE(d.surface_code, '') = COALESCE(o.surface_code, '') AND d.entry_code = o.entry_code ORDER BY observed_count DESC, processor_name, program_id, entry_code LIMIT $3",
|
||||
"WITH declared AS (SELECT processor_name, processor_version, program_id, surface_code, entry_code, COUNT(*)::BIGINT AS declared_count FROM k_sol_decode_coverage_declarations WHERE ($1::text IS NULL OR processor_name = $1) AND ($2::text IS NULL OR processor_version = $2) GROUP BY processor_name, processor_version, program_id, surface_code, entry_code), observed AS (SELECT processor_name, processor_version, program_id, surface_code, COALESCE(entry_code, 'unknown') AS entry_code, COUNT(*)::BIGINT AS observed_count, COUNT(*) FILTER (WHERE recognized)::BIGINT AS recognized_count, COALESCE(SUM(decoded_count), 0)::BIGINT AS decoded_count, COALESCE(SUM(materialized_count), 0)::BIGINT AS materialized_count, COALESCE(SUM(error_count), 0)::BIGINT AS error_count, COUNT(*) FILTER (WHERE status = 'unsupported' OR entry_code IS NULL)::BIGINT AS unknown_count, COUNT(*) FILTER (WHERE NOT transaction_failed)::BIGINT AS successful_transaction_count, COUNT(*) FILTER (WHERE transaction_failed)::BIGINT AS failed_transaction_count FROM k_sol_decode_coverage_observations WHERE ($1::text IS NULL OR processor_name = $1) AND ($2::text IS NULL OR processor_version = $2) GROUP BY processor_name, processor_version, program_id, surface_code, COALESCE(entry_code, 'unknown')) SELECT COALESCE(d.processor_name, o.processor_name) AS processor_name, COALESCE(d.processor_version, o.processor_version) AS processor_version, COALESCE(d.program_id, o.program_id) AS program_id, COALESCE(d.surface_code, o.surface_code) AS surface_code, COALESCE(d.entry_code, o.entry_code) AS entry_code, COALESCE(d.declared_count, 0)::BIGINT AS declared_count, COALESCE(o.observed_count, 0)::BIGINT AS observed_count, COALESCE(o.recognized_count, 0)::BIGINT AS recognized_count, COALESCE(o.decoded_count, 0)::BIGINT AS decoded_count, COALESCE(o.materialized_count, 0)::BIGINT AS materialized_count, COALESCE(o.error_count, 0)::BIGINT AS error_count, COALESCE(o.unknown_count, 0)::BIGINT AS unknown_count, COALESCE(o.successful_transaction_count, 0)::BIGINT AS successful_transaction_count, COALESCE(o.failed_transaction_count, 0)::BIGINT AS failed_transaction_count FROM declared d FULL OUTER JOIN observed o ON d.processor_name = o.processor_name AND d.processor_version = o.processor_version AND d.program_id = o.program_id AND COALESCE(d.surface_code, '') = COALESCE(o.surface_code, '') AND d.entry_code = o.entry_code ORDER BY observed_count DESC, processor_name, program_id, entry_code LIMIT $3",
|
||||
)
|
||||
.bind(processor_name)
|
||||
.bind(processor_version)
|
||||
@@ -798,7 +776,7 @@ async fn upsert_coverage_observation(
|
||||
},
|
||||
};
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_decode_coverage_observations (processor_name, processor_version, input_key, input_hash, signature, slot, instruction_path, program_id, surface_code, entry_code, discriminator_hex, status, recognized, decoded_count, materialized_count, error_count, transaction_failed) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) ON CONFLICT (processor_name, processor_version, input_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, surface_code = EXCLUDED.surface_code, entry_code = EXCLUDED.entry_code, discriminator_hex = EXCLUDED.discriminator_hex, status = EXCLUDED.status, recognized = EXCLUDED.recognized, decoded_count = EXCLUDED.decoded_count, materialized_count = EXCLUDED.materialized_count, error_count = EXCLUDED.error_count, transaction_failed = EXCLUDED.transaction_failed, updated_at = NOW()",
|
||||
"INSERT INTO k_sol_decode_coverage_observations (processor_name, processor_version, input_key, input_hash, signature, slot, instruction_path, program_id, surface_code, entry_code, discriminator_hex, status, recognized, decoded_count, materialized_count, error_count, transaction_failed) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) ON CONFLICT (processor_name, processor_version, input_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, surface_code = EXCLUDED.surface_code, entry_code = EXCLUDED.entry_code, discriminator_hex = EXCLUDED.discriminator_hex, status = EXCLUDED.status, recognized = EXCLUDED.recognized, decoded_count = EXCLUDED.decoded_count, materialized_count = EXCLUDED.materialized_count, error_count = EXCLUDED.error_count, transaction_failed = EXCLUDED.transaction_failed, updated_at = NOW()",
|
||||
)
|
||||
.bind(coverage.processor_name.as_str())
|
||||
.bind(coverage.processor_version.as_str())
|
||||
@@ -834,7 +812,7 @@ async fn refresh_materialized_coverage_count(
|
||||
decode_input_key: &str,
|
||||
) -> ks_core::Result<()> {
|
||||
let query_result = sqlx::query(
|
||||
"UPDATE kb_sol_decode_coverage_observations SET materialized_count = (SELECT COUNT(*)::INTEGER FROM kb_sol_mat_events WHERE source_decoder_name = $1 AND source_decoder_version = $2 AND source_decode_input_key = $3), updated_at = NOW() WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
"UPDATE k_sol_decode_coverage_observations SET materialized_count = (SELECT COUNT(*)::INTEGER FROM k_sol_mat_outputs WHERE source_decoder_name = $1 AND source_decoder_version = $2 AND source_decode_input_key = $3), updated_at = NOW() WHERE processor_name = $1 AND processor_version = $2 AND input_key = $3",
|
||||
)
|
||||
.bind(decoder_name)
|
||||
.bind(decoder_version)
|
||||
@@ -857,7 +835,7 @@ async fn upsert_ledger_terminal(
|
||||
error_message: std::option::Option<&str>,
|
||||
) -> ks_core::Result<()> {
|
||||
let query_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_ops_processing_ledger (stage, processor_name, processor_version, input_key, input_hash, status, attempt_count, started_at, finished_at, error_code, error_message) VALUES ($1, $2, $3, $4, $5, $6, 1, NOW(), NOW(), $7, $8) ON CONFLICT (stage, processor_name, processor_version, input_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, status = EXCLUDED.status, attempt_count = kb_sol_ops_processing_ledger.attempt_count + 1, started_at = NOW(), finished_at = NOW(), error_code = EXCLUDED.error_code, error_message = EXCLUDED.error_message, updated_at = NOW()",
|
||||
"INSERT INTO k_sol_ops_processing_ledger (stage, processor_name, processor_version, input_key, input_hash, status, attempt_count, started_at, finished_at, error_code, error_message) VALUES ($1, $2, $3, $4, $5, $6, 1, NOW(), NOW(), $7, $8) ON CONFLICT (stage, processor_name, processor_version, input_key) DO UPDATE SET input_hash = EXCLUDED.input_hash, status = EXCLUDED.status, attempt_count = k_sol_ops_processing_ledger.attempt_count + 1, started_at = NOW(), finished_at = NOW(), error_code = EXCLUDED.error_code, error_message = EXCLUDED.error_message, updated_at = NOW()",
|
||||
)
|
||||
.bind(identity.stage.as_str())
|
||||
.bind(identity.processor_name.as_str())
|
||||
@@ -1032,9 +1010,7 @@ mod tests {
|
||||
};
|
||||
let pool_result = sqlx::PgPool::connect(url.as_str()).await;
|
||||
let pool = result_or_panic(pool_result);
|
||||
result_or_panic(crate::apply_raw_store_schema(&pool).await);
|
||||
result_or_panic(crate::apply_core_store_schema(&pool).await);
|
||||
result_or_panic(crate::apply_decode_store_schema(&pool).await);
|
||||
result_or_panic(crate::apply_store_schema(&pool).await);
|
||||
return std::option::Option::Some(pool);
|
||||
}
|
||||
|
||||
@@ -1069,12 +1045,11 @@ mod tests {
|
||||
crate::persist_decode_coverage_declarations(&pool, &[declaration]).await,
|
||||
);
|
||||
assert_eq!(third, crate::InsertOutcome::new(0, 1, 0));
|
||||
let cleanup_result = sqlx::query(
|
||||
"DELETE FROM kb_sol_decode_coverage_declarations WHERE processor_name = $1",
|
||||
)
|
||||
.bind(processor_name.as_str())
|
||||
.execute(&pool)
|
||||
.await;
|
||||
let cleanup_result =
|
||||
sqlx::query("DELETE FROM k_sol_decode_coverage_declarations WHERE processor_name = $1")
|
||||
.bind(processor_name.as_str())
|
||||
.execute(&pool)
|
||||
.await;
|
||||
result_or_panic(cleanup_result);
|
||||
}
|
||||
|
||||
@@ -1126,15 +1101,14 @@ mod tests {
|
||||
result_or_panic(crate::persist_decode_result(&pool, &bundle, true).await);
|
||||
let current = result_or_panic(crate::is_decode_current(&pool, &identity).await);
|
||||
assert!(current);
|
||||
let cleanup_coverage = sqlx::query(
|
||||
"DELETE FROM kb_sol_decode_coverage_observations WHERE processor_name = $1",
|
||||
)
|
||||
.bind(processor_name.as_str())
|
||||
.execute(&pool)
|
||||
.await;
|
||||
let cleanup_coverage =
|
||||
sqlx::query("DELETE FROM k_sol_decode_coverage_observations WHERE processor_name = $1")
|
||||
.bind(processor_name.as_str())
|
||||
.execute(&pool)
|
||||
.await;
|
||||
result_or_panic(cleanup_coverage);
|
||||
let cleanup_ledger =
|
||||
sqlx::query("DELETE FROM kb_sol_ops_processing_ledger WHERE processor_name = $1")
|
||||
sqlx::query("DELETE FROM k_sol_ops_processing_ledger WHERE processor_name = $1")
|
||||
.bind(processor_name.as_str())
|
||||
.execute(&pool)
|
||||
.await;
|
||||
@@ -1142,7 +1116,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn optional_postgres_materialized_event_query_is_bounded_and_typed_from_env() {
|
||||
async fn optional_postgres_materialized_output_query_is_bounded_and_typed_from_env() {
|
||||
let _postgres_guard = crate::postgres_test_guard().await;
|
||||
let pool = match test_pool_from_env().await {
|
||||
std::option::Option::Some(value) => value,
|
||||
@@ -1150,7 +1124,7 @@ mod tests {
|
||||
};
|
||||
let input_key = unique_processor_name("annotation_query_test");
|
||||
let insert_result = sqlx::query(
|
||||
"INSERT INTO kb_sol_mat_events (processor_name, processor_version, input_key, input_hash, output_key, source_event_key, source_decoder_name, source_decoder_version, source_decode_input_key, signature, slot, materialized_family, payload_jsonb) VALUES ('materializer.transaction.annotations', '0.4.3', $1, 'hash', 'annotation', 'memo:0', 'ks-lib-decoder.spl.memo', '0.4.3', 'decode-input', $2, 42, 'transaction_annotation', $3)",
|
||||
"INSERT INTO k_sol_mat_outputs (processor_name, processor_version, input_key, input_hash, output_key, source_event_key, source_decoder_name, source_decoder_version, source_decode_input_key, signature, slot, materialized_family, payload_jsonb) VALUES ('materializer.transaction.annotations', '0.4.3', $1, 'hash', 'annotation', 'memo:0', 'ks-lib-decoder.spl.memo', '0.4.3', 'decode-input', $2, 42, 'transaction_annotation', $3)",
|
||||
)
|
||||
.bind(input_key.as_str())
|
||||
.bind(input_key.as_str())
|
||||
@@ -1158,17 +1132,17 @@ mod tests {
|
||||
.execute(&pool)
|
||||
.await;
|
||||
result_or_panic(insert_result);
|
||||
let filter = result_or_panic(crate::MaterializedEventFilter::new(
|
||||
let filter = result_or_panic(crate::MaterializedOutputFilter::new(
|
||||
std::option::Option::Some("materializer.transaction.annotations".to_string()),
|
||||
std::option::Option::Some("transaction_annotation".to_string()),
|
||||
std::option::Option::Some(input_key.clone()),
|
||||
1,
|
||||
));
|
||||
let rows = result_or_panic(crate::list_materialized_events(&pool, &filter).await);
|
||||
let rows = result_or_panic(crate::list_materialized_outputs(&pool, &filter).await);
|
||||
assert_eq!(rows.len(), 1);
|
||||
assert_eq!(rows[0].slot, 42);
|
||||
assert_eq!(rows[0].payload_json["text"], "postgres annotation");
|
||||
let cleanup_result = sqlx::query("DELETE FROM kb_sol_mat_events WHERE input_key = $1")
|
||||
let cleanup_result = sqlx::query("DELETE FROM k_sol_mat_outputs WHERE input_key = $1")
|
||||
.bind(input_key.as_str())
|
||||
.execute(&pool)
|
||||
.await;
|
||||
@@ -1184,22 +1158,20 @@ mod tests {
|
||||
let _postgres_guard = crate::postgres_test_guard().await;
|
||||
let pool_result = sqlx::PgPool::connect(url.as_str()).await;
|
||||
let pool = result_or_panic(pool_result);
|
||||
result_or_panic(crate::apply_raw_store_schema(&pool).await);
|
||||
result_or_panic(crate::apply_core_store_schema(&pool).await);
|
||||
result_or_panic(crate::apply_decode_store_schema(&pool).await);
|
||||
result_or_panic(crate::apply_store_schema(&pool).await);
|
||||
execute_sql(
|
||||
&pool,
|
||||
"DELETE FROM kb_sol_decode_events WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
"DELETE FROM k_sol_decode_events WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
)
|
||||
.await;
|
||||
execute_sql(
|
||||
&pool,
|
||||
"DELETE FROM kb_sol_decode_coverage_observations WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
"DELETE FROM k_sol_decode_coverage_observations WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
)
|
||||
.await;
|
||||
execute_sql(
|
||||
&pool,
|
||||
"DELETE FROM kb_sol_ops_processing_ledger WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
"DELETE FROM k_sol_ops_processing_ledger WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
)
|
||||
.await;
|
||||
execute_sql(
|
||||
@@ -1209,12 +1181,12 @@ mod tests {
|
||||
.await;
|
||||
execute_sql(
|
||||
&pool,
|
||||
"DROP TRIGGER IF EXISTS ks_test_reject_decode_ledger_trigger ON kb_sol_ops_processing_ledger",
|
||||
"DROP TRIGGER IF EXISTS ks_test_reject_decode_ledger_trigger ON k_sol_ops_processing_ledger",
|
||||
)
|
||||
.await;
|
||||
execute_sql(
|
||||
&pool,
|
||||
"CREATE TRIGGER ks_test_reject_decode_ledger_trigger BEFORE INSERT OR UPDATE ON kb_sol_ops_processing_ledger FOR EACH ROW EXECUTE FUNCTION ks_test_reject_decode_ledger()",
|
||||
"CREATE TRIGGER ks_test_reject_decode_ledger_trigger BEFORE INSERT OR UPDATE ON k_sol_ops_processing_ledger FOR EACH ROW EXECUTE FUNCTION ks_test_reject_decode_ledger()",
|
||||
)
|
||||
.await;
|
||||
let identity_result = crate::ProcessingLedgerIdentity::new(
|
||||
@@ -1264,6 +1236,7 @@ mod tests {
|
||||
proof_kind: "exact_layout".to_string(),
|
||||
proof_json: serde_json::json!({"source": "test"}),
|
||||
payload_json: serde_json::json!({"value": 1}),
|
||||
schema_provenance: std::option::Option::None,
|
||||
transaction_failed: false,
|
||||
transaction_error: std::option::Option::None,
|
||||
observation_committed: true,
|
||||
@@ -1281,13 +1254,13 @@ mod tests {
|
||||
let persistence_result = crate::persist_decode_result(&pool, &bundle, true).await;
|
||||
assert!(persistence_result.is_err());
|
||||
let event_count_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT COUNT(*) FROM kb_sol_decode_events WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
"SELECT COUNT(*) FROM k_sol_decode_events WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await;
|
||||
let event_count = result_or_panic(event_count_result);
|
||||
let ledger_count_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT COUNT(*) FROM kb_sol_ops_processing_ledger WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
"SELECT COUNT(*) FROM k_sol_ops_processing_ledger WHERE processor_name = 'decode_atomic_rollback_test'",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await;
|
||||
@@ -1296,7 +1269,7 @@ mod tests {
|
||||
assert_eq!(ledger_count, 0);
|
||||
execute_sql(
|
||||
&pool,
|
||||
"DROP TRIGGER IF EXISTS ks_test_reject_decode_ledger_trigger ON kb_sol_ops_processing_ledger",
|
||||
"DROP TRIGGER IF EXISTS ks_test_reject_decode_ledger_trigger ON k_sol_ops_processing_ledger",
|
||||
)
|
||||
.await;
|
||||
execute_sql(&pool, "DROP FUNCTION IF EXISTS ks_test_reject_decode_ledger()").await;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/postgres/query/health_queries.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! PostgreSQL health and diagnostic SQL queries.
|
||||
|
||||
@@ -42,36 +42,3 @@ pub(crate) async fn load_server_version(
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn load_migration_table_name(
|
||||
pool: &sqlx::PgPool,
|
||||
) -> ks_core::Result<std::option::Option<std::string::String>> {
|
||||
let query_result =
|
||||
sqlx::query_scalar::<sqlx::Postgres, std::option::Option<std::string::String>>(
|
||||
"SELECT to_regclass('_sqlx_migrations')::text",
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
return match query_result {
|
||||
std::result::Result::Ok(table_name) => std::result::Result::Ok(table_name),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres migration table query failed: {error}"
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn load_latest_migration_version(
|
||||
pool: &sqlx::PgPool,
|
||||
) -> ks_core::Result<std::option::Option<std::string::String>> {
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, std::option::Option<std::string::String>>(
|
||||
"SELECT version::text FROM _sqlx_migrations WHERE success = true ORDER BY version DESC LIMIT 1",
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await;
|
||||
return match query_result {
|
||||
std::result::Result::Ok(version) => std::result::Result::Ok(version.flatten()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres latest migration query failed: {error}"
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,61 +1,19 @@
|
||||
// file: ks-store/src/postgres/query/raw_queries.rs
|
||||
// version: 6
|
||||
// version: 8
|
||||
|
||||
//! PostgreSQL canonical transaction and acquisition observation SQL queries.
|
||||
|
||||
pub(crate) async fn apply_raw_store_schema(pool: &sqlx::PgPool) -> ks_core::Result<()> {
|
||||
let validation_result = crate::validate_raw_store_table_names();
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let transaction_result = pool.begin().await;
|
||||
let mut transaction = match transaction_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres canonical raw store schema transaction failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let lock_result = sqlx::query("SELECT pg_advisory_xact_lock($1)")
|
||||
.bind(crate::STORE_SCHEMA_ADVISORY_LOCK_ID)
|
||||
.execute(&mut *transaction)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = lock_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres canonical raw store schema advisory lock failed: {error}"
|
||||
)));
|
||||
}
|
||||
for statement in crate::raw_store_schema_statements() {
|
||||
let execution_result = sqlx::query(statement).execute(&mut *transaction).await;
|
||||
if let std::result::Result::Err(error) = execution_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres canonical raw store schema statement failed: {error}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
let commit_result = transaction.commit().await;
|
||||
if let std::result::Result::Err(error) = commit_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres canonical raw store schema commit failed: {error}"
|
||||
)));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
pub(crate) async fn has_raw_transaction_signature(
|
||||
pool: &sqlx::PgPool,
|
||||
signature: &str,
|
||||
) -> ks_core::Result<bool> {
|
||||
let validation_result = crate::postgres::query::raw_queries::validate_required_text(
|
||||
signature,
|
||||
"canonical raw transaction signature must not be empty",
|
||||
);
|
||||
let validation_result =
|
||||
validate_required_text(signature, "canonical raw transaction signature must not be empty");
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, bool>(
|
||||
"SELECT EXISTS(SELECT 1 FROM kb_sol_raw_transactions WHERE signature = $1)",
|
||||
"SELECT EXISTS(SELECT 1 FROM k_sol_raw_transactions WHERE signature = $1)",
|
||||
)
|
||||
.bind(signature)
|
||||
.fetch_one(pool)
|
||||
@@ -72,15 +30,13 @@ pub(crate) async fn has_transaction_observation_key(
|
||||
pool: &sqlx::PgPool,
|
||||
observation_key: &str,
|
||||
) -> ks_core::Result<bool> {
|
||||
let validation_result = crate::postgres::query::raw_queries::validate_required_text(
|
||||
observation_key,
|
||||
"transaction observation key must not be empty",
|
||||
);
|
||||
let validation_result =
|
||||
validate_required_text(observation_key, "transaction observation key must not be empty");
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, bool>(
|
||||
"SELECT EXISTS(SELECT 1 FROM kb_sol_obs_transaction_observations WHERE observation_key = $1)",
|
||||
"SELECT EXISTS(SELECT 1 FROM k_sol_obs_transaction_observations WHERE observation_key = $1)",
|
||||
)
|
||||
.bind(observation_key)
|
||||
.fetch_one(pool)
|
||||
@@ -97,7 +53,7 @@ pub(crate) async fn insert_raw_transaction(
|
||||
pool: &sqlx::PgPool,
|
||||
input: &crate::RawTransactionInsert,
|
||||
) -> ks_core::Result<crate::InsertOutcome> {
|
||||
let slot_result = crate::postgres::query::raw_queries::sql_slot_from_u64(input.slot);
|
||||
let slot_result = sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -112,10 +68,11 @@ pub(crate) async fn insert_raw_transaction(
|
||||
},
|
||||
};
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_raw_transactions (signature, slot, canonical_json, canonical_json_hash, canonical_format_version, retention_state, processing_state) VALUES ($1, $2, $3, $4, $5, 'full', 'received') ON CONFLICT (signature) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_raw_transactions (signature, slot, block_time, canonical_json, canonical_json_hash, canonical_format_version, retention_state, processing_state) VALUES ($1, $2, $3, $4, $5, $6, 'full', 'received') ON CONFLICT (signature) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.signature.as_str())
|
||||
.bind(slot)
|
||||
.bind(input.block_time)
|
||||
.bind(&input.canonical_json)
|
||||
.bind(input.canonical_json_hash.as_deref())
|
||||
.bind(canonical_format_version)
|
||||
@@ -138,23 +95,20 @@ pub(crate) async fn insert_transaction_observation(
|
||||
pool: &sqlx::PgPool,
|
||||
input: &crate::TransactionObservationInsert,
|
||||
) -> ks_core::Result<crate::InsertOutcome> {
|
||||
let slot_result = crate::postgres::query::raw_queries::optional_sql_slot_from_u64(input.slot);
|
||||
let slot_result = optional_sql_slot_from_u64(input.slot);
|
||||
let slot = match slot_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payload_size_result =
|
||||
crate::postgres::query::raw_queries::optional_sql_bigint_from_u64(input.payload_size_bytes);
|
||||
let payload_size_result = optional_sql_bigint_from_u64(input.payload_size_bytes);
|
||||
let payload_size_bytes = match payload_size_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let origin =
|
||||
crate::postgres::query::raw_queries::transaction_observation_origin_to_sql(input.origin);
|
||||
let status =
|
||||
crate::postgres::query::raw_queries::transaction_observation_status_to_sql(input.status);
|
||||
let origin = transaction_observation_origin_to_sql(input.origin);
|
||||
let status = transaction_observation_status_to_sql(input.status);
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"INSERT INTO kb_sol_obs_transaction_observations (raw_transaction_id, observation_key, signature, slot, provider, endpoint_code, protocol, acquisition_method, origin, commitment, capture_session_id, filter_code, detected_at, received_at, normalized_at, payload_size_bytes, source_payload_hash, status, error_code, error_message) VALUES (COALESCE($1, (SELECT id FROM kb_sol_raw_transactions WHERE signature = $3 LIMIT 1)), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ON CONFLICT (observation_key) DO NOTHING RETURNING id",
|
||||
"INSERT INTO k_sol_obs_transaction_observations (raw_transaction_id, observation_key, signature, slot, provider, endpoint_code, protocol, acquisition_method, origin, commitment, capture_session_id, filter_code, detected_at, received_at, normalized_at, payload_size_bytes, source_payload_hash, status, error_code, error_message) VALUES (COALESCE($1, (SELECT id FROM k_sol_raw_transactions WHERE signature = $3 LIMIT 1)), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ON CONFLICT (observation_key) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(input.raw_transaction_id)
|
||||
.bind(input.observation_key.as_str())
|
||||
@@ -200,7 +154,7 @@ pub(crate) async fn update_raw_payload_lifecycle(
|
||||
"raw lifecycle table name is not supported by the PostgreSQL canonical raw store",
|
||||
));
|
||||
}
|
||||
return crate::postgres::query::raw_queries::update_raw_transaction_lifecycle(pool, mark).await;
|
||||
return update_raw_transaction_lifecycle(pool, mark).await;
|
||||
}
|
||||
|
||||
fn raw_retention_state_to_sql(state: crate::RawPayloadRetentionState) -> &'static str {
|
||||
@@ -223,27 +177,27 @@ fn raw_processing_state_to_sql(state: crate::RawPayloadProcessingState) -> &'sta
|
||||
}
|
||||
|
||||
fn transaction_observation_origin_to_sql(
|
||||
origin: crate::TransactionObservationOrigin,
|
||||
origin: crate::AcquisitionObservationOrigin,
|
||||
) -> &'static str {
|
||||
return match origin {
|
||||
crate::TransactionObservationOrigin::Live => "live",
|
||||
crate::TransactionObservationOrigin::Backfill => "backfill",
|
||||
crate::TransactionObservationOrigin::Replay => "replay",
|
||||
crate::TransactionObservationOrigin::Repair => "repair",
|
||||
crate::TransactionObservationOrigin::Migration => "migration",
|
||||
crate::AcquisitionObservationOrigin::Live => "live",
|
||||
crate::AcquisitionObservationOrigin::Backfill => "backfill",
|
||||
crate::AcquisitionObservationOrigin::Replay => "replay",
|
||||
crate::AcquisitionObservationOrigin::Repair => "repair",
|
||||
crate::AcquisitionObservationOrigin::Migration => "migration",
|
||||
};
|
||||
}
|
||||
|
||||
fn transaction_observation_status_to_sql(
|
||||
status: crate::TransactionObservationStatus,
|
||||
status: crate::AcquisitionObservationStatus,
|
||||
) -> &'static str {
|
||||
return match status {
|
||||
crate::TransactionObservationStatus::Detected => "detected",
|
||||
crate::TransactionObservationStatus::Received => "received",
|
||||
crate::TransactionObservationStatus::Normalized => "normalized",
|
||||
crate::TransactionObservationStatus::Persisted => "persisted",
|
||||
crate::TransactionObservationStatus::Failed => "failed",
|
||||
crate::TransactionObservationStatus::Missing => "missing",
|
||||
crate::AcquisitionObservationStatus::Detected => "detected",
|
||||
crate::AcquisitionObservationStatus::Received => "received",
|
||||
crate::AcquisitionObservationStatus::Normalized => "normalized",
|
||||
crate::AcquisitionObservationStatus::Persisted => "persisted",
|
||||
crate::AcquisitionObservationStatus::Failed => "failed",
|
||||
crate::AcquisitionObservationStatus::Missing => "missing",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,7 +214,7 @@ fn sql_slot_from_u64(slot: u64) -> ks_core::Result<i64> {
|
||||
fn optional_sql_slot_from_u64(
|
||||
slot: std::option::Option<u64>,
|
||||
) -> ks_core::Result<std::option::Option<i64>> {
|
||||
return crate::postgres::query::raw_queries::optional_sql_bigint_from_u64(slot);
|
||||
return optional_sql_bigint_from_u64(slot);
|
||||
}
|
||||
|
||||
fn optional_sql_bigint_from_u64(
|
||||
@@ -293,12 +247,10 @@ async fn update_raw_transaction_lifecycle(
|
||||
pool: &sqlx::PgPool,
|
||||
mark: &crate::RawPayloadLifecycleMark,
|
||||
) -> ks_core::Result<crate::InsertOutcome> {
|
||||
let retention_state =
|
||||
crate::postgres::query::raw_queries::raw_retention_state_to_sql(mark.retention_state);
|
||||
let processing_state =
|
||||
crate::postgres::query::raw_queries::raw_processing_state_to_sql(mark.processing_state);
|
||||
let retention_state = raw_retention_state_to_sql(mark.retention_state);
|
||||
let processing_state = raw_processing_state_to_sql(mark.processing_state);
|
||||
let query_result = sqlx::query(
|
||||
"UPDATE kb_sol_raw_transactions SET retention_state = $1, processing_state = $2, lifecycle_reason = $3, updated_at = NOW() WHERE signature = $4",
|
||||
"UPDATE k_sol_raw_transactions SET retention_state = $1, processing_state = $2, lifecycle_reason = $3, updated_at = NOW() WHERE signature = $4",
|
||||
)
|
||||
.bind(retention_state)
|
||||
.bind(processing_state)
|
||||
@@ -306,7 +258,7 @@ async fn update_raw_transaction_lifecycle(
|
||||
.bind(mark.raw_row_key.as_str())
|
||||
.execute(pool)
|
||||
.await;
|
||||
return crate::postgres::query::raw_queries::outcome_from_update_result(
|
||||
return outcome_from_update_result(
|
||||
query_result,
|
||||
"postgres canonical raw transaction lifecycle update failed",
|
||||
);
|
||||
@@ -334,39 +286,36 @@ fn outcome_from_update_result(
|
||||
mod tests {
|
||||
#[test]
|
||||
fn retention_state_serializes_to_lower_snake_case() {
|
||||
let value = crate::postgres::query::raw_queries::raw_retention_state_to_sql(
|
||||
crate::RawPayloadRetentionState::Compacted,
|
||||
);
|
||||
let value = super::raw_retention_state_to_sql(crate::RawPayloadRetentionState::Compacted);
|
||||
assert_eq!(value, "compacted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn processing_state_serializes_to_lower_snake_case() {
|
||||
let value = crate::postgres::query::raw_queries::raw_processing_state_to_sql(
|
||||
crate::RawPayloadProcessingState::CoreExtracted,
|
||||
);
|
||||
let value =
|
||||
super::raw_processing_state_to_sql(crate::RawPayloadProcessingState::CoreExtracted);
|
||||
assert_eq!(value, "core_extracted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observation_origin_serializes_to_lower_snake_case() {
|
||||
let value = crate::postgres::query::raw_queries::transaction_observation_origin_to_sql(
|
||||
crate::TransactionObservationOrigin::Backfill,
|
||||
let value = super::transaction_observation_origin_to_sql(
|
||||
crate::AcquisitionObservationOrigin::Backfill,
|
||||
);
|
||||
assert_eq!(value, "backfill");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observation_status_serializes_to_lower_snake_case() {
|
||||
let value = crate::postgres::query::raw_queries::transaction_observation_status_to_sql(
|
||||
crate::TransactionObservationStatus::Normalized,
|
||||
let value = super::transaction_observation_status_to_sql(
|
||||
crate::AcquisitionObservationStatus::Normalized,
|
||||
);
|
||||
assert_eq!(value, "normalized");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_slot_rejects_values_above_bigint() {
|
||||
let result = crate::postgres::query::raw_queries::sql_slot_from_u64(u64::MAX);
|
||||
let result = super::sql_slot_from_u64(u64::MAX);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -387,7 +336,7 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("unexpected connect error: {error}"),
|
||||
};
|
||||
let schema_result = store.initialize_raw_store_schema().await;
|
||||
let schema_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = schema_result {
|
||||
panic!("unexpected schema error: {error}");
|
||||
}
|
||||
@@ -395,6 +344,7 @@ mod tests {
|
||||
let raw_input_result = crate::RawTransactionInsert::new(
|
||||
signature.clone(),
|
||||
1,
|
||||
std::option::Option::None,
|
||||
serde_json::json!({"source": "test"}),
|
||||
1,
|
||||
);
|
||||
@@ -432,7 +382,7 @@ mod tests {
|
||||
"test_provider",
|
||||
"solana_http",
|
||||
"getTransaction",
|
||||
crate::TransactionObservationOrigin::Backfill,
|
||||
crate::AcquisitionObservationOrigin::Backfill,
|
||||
chrono::Utc::now(),
|
||||
);
|
||||
let observation = match observation_result {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/postgres/query/replay_candidate_queries.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Read-only PostgreSQL queries for replay candidate discovery.
|
||||
|
||||
@@ -119,11 +119,11 @@ pub(crate) async fn list_replay_program_summaries(
|
||||
) -> ks_core::Result<std::vec::Vec<crate::ReplayProgramSummary>> {
|
||||
let query_result = sqlx::query_as::<sqlx::Postgres, crate::postgres::query::replay_candidate_queries::ReplayProgramSummaryRow>(
|
||||
r#"WITH occurrences AS (
|
||||
SELECT program_id, signature, slot, 'top_level'::TEXT AS scope FROM kb_sol_core_instructions
|
||||
SELECT program_id, signature, slot, 'top_level'::TEXT AS scope FROM k_sol_core_instructions
|
||||
UNION ALL
|
||||
SELECT program_id, signature, slot, 'inner'::TEXT AS scope FROM kb_sol_core_inner_instructions
|
||||
SELECT program_id, signature, slot, 'inner'::TEXT AS scope FROM k_sol_core_inner_instructions
|
||||
UNION ALL
|
||||
SELECT program_id, signature, slot, 'logs'::TEXT AS scope FROM kb_sol_core_logs WHERE program_id IS NOT NULL
|
||||
SELECT program_id, signature, slot, 'logs'::TEXT AS scope FROM k_sol_core_logs WHERE program_id IS NOT NULL
|
||||
)
|
||||
SELECT program_id,
|
||||
COUNT(DISTINCT signature)::BIGINT AS transaction_count,
|
||||
@@ -176,15 +176,15 @@ pub(crate) async fn list_replay_entity_summaries(
|
||||
>(
|
||||
r#"WITH entities AS (
|
||||
SELECT 'mint'::TEXT AS entity_kind, mint AS entity_value, signature, slot
|
||||
FROM kb_sol_core_balance_changes
|
||||
FROM k_sol_core_balance_changes
|
||||
WHERE mint IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT 'owner'::TEXT AS entity_kind, owner AS entity_value, signature, slot
|
||||
FROM kb_sol_core_balance_changes
|
||||
FROM k_sol_core_balance_changes
|
||||
WHERE owner IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT 'account_key'::TEXT AS entity_kind, account_key AS entity_value, signature, slot
|
||||
FROM kb_sol_core_account_keys
|
||||
FROM k_sol_core_account_keys
|
||||
)
|
||||
SELECT entity_kind,
|
||||
entity_value,
|
||||
@@ -250,11 +250,11 @@ fn transaction_candidate_sql(order: &str) -> &'static str {
|
||||
COALESCE(top_level_stats.program_count, 0)::BIGINT AS top_level_program_count,
|
||||
COALESCE(inner_stats.program_count, 0)::BIGINT AS inner_program_count,
|
||||
raw.updated_at::TEXT AS updated_at
|
||||
FROM kb_sol_raw_transactions raw
|
||||
LEFT JOIN kb_sol_core_transactions core ON core.signature = raw.signature
|
||||
FROM k_sol_raw_transactions raw
|
||||
LEFT JOIN k_sol_core_transactions core ON core.signature = raw.signature
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT status, processor_version, attempt_count
|
||||
FROM kb_sol_ops_processing_ledger
|
||||
FROM k_sol_ops_processing_ledger
|
||||
WHERE stage = 'core_extraction'
|
||||
AND processor_name = 'canonical_to_core'
|
||||
AND input_key = raw.signature
|
||||
@@ -263,12 +263,12 @@ fn transaction_candidate_sql(order: &str) -> &'static str {
|
||||
) ledger ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*)::BIGINT AS instruction_count, COUNT(DISTINCT program_id)::BIGINT AS program_count
|
||||
FROM kb_sol_core_instructions
|
||||
FROM k_sol_core_instructions
|
||||
WHERE signature = raw.signature
|
||||
) top_level_stats ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*)::BIGINT AS instruction_count, COUNT(DISTINCT program_id)::BIGINT AS program_count
|
||||
FROM kb_sol_core_inner_instructions
|
||||
FROM k_sol_core_inner_instructions
|
||||
WHERE signature = raw.signature
|
||||
) inner_stats ON TRUE
|
||||
WHERE ($1::TEXT IS NULL OR raw.signature ILIKE '%' || $1 || '%')
|
||||
@@ -278,26 +278,26 @@ fn transaction_candidate_sql(order: &str) -> &'static str {
|
||||
AND ($5::TEXT IS NULL OR ($5 = 'not_started' AND ledger.status IS NULL) OR ledger.status = $5)
|
||||
AND ($6::TEXT IS NULL OR
|
||||
($7 = 'any' AND (
|
||||
EXISTS (SELECT 1 FROM kb_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM kb_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM kb_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)
|
||||
EXISTS (SELECT 1 FROM k_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM k_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM k_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)
|
||||
)) OR
|
||||
($7 = 'top_level' AND EXISTS (SELECT 1 FROM kb_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6)) OR
|
||||
($7 = 'inner' AND EXISTS (SELECT 1 FROM kb_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6)) OR
|
||||
($7 = 'logs' AND EXISTS (SELECT 1 FROM kb_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)))
|
||||
($7 = 'top_level' AND EXISTS (SELECT 1 FROM k_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6)) OR
|
||||
($7 = 'inner' AND EXISTS (SELECT 1 FROM k_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6)) OR
|
||||
($7 = 'logs' AND EXISTS (SELECT 1 FROM k_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)))
|
||||
AND ($8::TEXT IS NULL OR
|
||||
($8 = 'mint' AND EXISTS (
|
||||
SELECT 1 FROM kb_sol_core_balance_changes candidate_balance
|
||||
SELECT 1 FROM k_sol_core_balance_changes candidate_balance
|
||||
WHERE candidate_balance.signature = raw.signature
|
||||
AND candidate_balance.mint = $9
|
||||
)) OR
|
||||
($8 = 'owner' AND EXISTS (
|
||||
SELECT 1 FROM kb_sol_core_balance_changes candidate_balance
|
||||
SELECT 1 FROM k_sol_core_balance_changes candidate_balance
|
||||
WHERE candidate_balance.signature = raw.signature
|
||||
AND candidate_balance.owner = $9
|
||||
)) OR
|
||||
($8 = 'account_key' AND EXISTS (
|
||||
SELECT 1 FROM kb_sol_core_account_keys candidate_account
|
||||
SELECT 1 FROM k_sol_core_account_keys candidate_account
|
||||
WHERE candidate_account.signature = raw.signature
|
||||
AND candidate_account.account_key = $9
|
||||
)))
|
||||
@@ -318,11 +318,11 @@ fn transaction_candidate_sql(order: &str) -> &'static str {
|
||||
COALESCE(top_level_stats.program_count, 0)::BIGINT AS top_level_program_count,
|
||||
COALESCE(inner_stats.program_count, 0)::BIGINT AS inner_program_count,
|
||||
raw.updated_at::TEXT AS updated_at
|
||||
FROM kb_sol_raw_transactions raw
|
||||
LEFT JOIN kb_sol_core_transactions core ON core.signature = raw.signature
|
||||
FROM k_sol_raw_transactions raw
|
||||
LEFT JOIN k_sol_core_transactions core ON core.signature = raw.signature
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT status, processor_version, attempt_count
|
||||
FROM kb_sol_ops_processing_ledger
|
||||
FROM k_sol_ops_processing_ledger
|
||||
WHERE stage = 'core_extraction'
|
||||
AND processor_name = 'canonical_to_core'
|
||||
AND input_key = raw.signature
|
||||
@@ -331,12 +331,12 @@ fn transaction_candidate_sql(order: &str) -> &'static str {
|
||||
) ledger ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*)::BIGINT AS instruction_count, COUNT(DISTINCT program_id)::BIGINT AS program_count
|
||||
FROM kb_sol_core_instructions
|
||||
FROM k_sol_core_instructions
|
||||
WHERE signature = raw.signature
|
||||
) top_level_stats ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*)::BIGINT AS instruction_count, COUNT(DISTINCT program_id)::BIGINT AS program_count
|
||||
FROM kb_sol_core_inner_instructions
|
||||
FROM k_sol_core_inner_instructions
|
||||
WHERE signature = raw.signature
|
||||
) inner_stats ON TRUE
|
||||
WHERE ($1::TEXT IS NULL OR raw.signature ILIKE '%' || $1 || '%')
|
||||
@@ -346,26 +346,26 @@ fn transaction_candidate_sql(order: &str) -> &'static str {
|
||||
AND ($5::TEXT IS NULL OR ($5 = 'not_started' AND ledger.status IS NULL) OR ledger.status = $5)
|
||||
AND ($6::TEXT IS NULL OR
|
||||
($7 = 'any' AND (
|
||||
EXISTS (SELECT 1 FROM kb_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM kb_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM kb_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)
|
||||
EXISTS (SELECT 1 FROM k_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM k_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6) OR
|
||||
EXISTS (SELECT 1 FROM k_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)
|
||||
)) OR
|
||||
($7 = 'top_level' AND EXISTS (SELECT 1 FROM kb_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6)) OR
|
||||
($7 = 'inner' AND EXISTS (SELECT 1 FROM kb_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6)) OR
|
||||
($7 = 'logs' AND EXISTS (SELECT 1 FROM kb_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)))
|
||||
($7 = 'top_level' AND EXISTS (SELECT 1 FROM k_sol_core_instructions candidate_top_level WHERE candidate_top_level.signature = raw.signature AND candidate_top_level.program_id = $6)) OR
|
||||
($7 = 'inner' AND EXISTS (SELECT 1 FROM k_sol_core_inner_instructions candidate_inner WHERE candidate_inner.signature = raw.signature AND candidate_inner.program_id = $6)) OR
|
||||
($7 = 'logs' AND EXISTS (SELECT 1 FROM k_sol_core_logs candidate_log WHERE candidate_log.signature = raw.signature AND candidate_log.program_id = $6)))
|
||||
AND ($8::TEXT IS NULL OR
|
||||
($8 = 'mint' AND EXISTS (
|
||||
SELECT 1 FROM kb_sol_core_balance_changes candidate_balance
|
||||
SELECT 1 FROM k_sol_core_balance_changes candidate_balance
|
||||
WHERE candidate_balance.signature = raw.signature
|
||||
AND candidate_balance.mint = $9
|
||||
)) OR
|
||||
($8 = 'owner' AND EXISTS (
|
||||
SELECT 1 FROM kb_sol_core_balance_changes candidate_balance
|
||||
SELECT 1 FROM k_sol_core_balance_changes candidate_balance
|
||||
WHERE candidate_balance.signature = raw.signature
|
||||
AND candidate_balance.owner = $9
|
||||
)) OR
|
||||
($8 = 'account_key' AND EXISTS (
|
||||
SELECT 1 FROM kb_sol_core_account_keys candidate_account
|
||||
SELECT 1 FROM k_sol_core_account_keys candidate_account
|
||||
WHERE candidate_account.signature = raw.signature
|
||||
AND candidate_account.account_key = $9
|
||||
)))
|
||||
@@ -411,13 +411,9 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("unexpected connect error: {error}"),
|
||||
};
|
||||
let raw_schema_result = store.initialize_raw_store_schema().await;
|
||||
if let std::result::Result::Err(error) = raw_schema_result {
|
||||
panic!("unexpected raw schema error: {error}");
|
||||
}
|
||||
let core_schema_result = store.initialize_core_store_schema().await;
|
||||
if let std::result::Result::Err(error) = core_schema_result {
|
||||
panic!("unexpected core schema error: {error}");
|
||||
let schema_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = schema_result {
|
||||
panic!("unexpected store schema error: {error}");
|
||||
}
|
||||
let transaction_filter_result = crate::ReplayTransactionFilter::new(
|
||||
std::option::Option::None,
|
||||
|
||||
162
ks-store/src/postgres/query/schema_queries.rs
Normal file
162
ks-store/src/postgres/query/schema_queries.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
// file: ks-store/src/postgres/query/schema_queries.rs
|
||||
// version: 1
|
||||
|
||||
//! PostgreSQL orchestration for the complete store baseline schema.
|
||||
|
||||
pub(crate) async fn apply_store_schema(pool: &sqlx::PgPool) -> ks_core::Result<()> {
|
||||
let resource_validation_result = crate::validate_postgres_sql_resources();
|
||||
if let std::result::Result::Err(error) = resource_validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let legacy_result = legacy_store_schema_detected(pool).await;
|
||||
let legacy_detected = match legacy_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if legacy_detected {
|
||||
return std::result::Result::Err(crate::storage_contract_error(
|
||||
"store_legacy_schema_detected",
|
||||
"historical store schema detected; rebuild the database before initialization",
|
||||
));
|
||||
}
|
||||
let transaction_result = pool.begin().await;
|
||||
let mut transaction = match transaction_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres store schema transaction failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let lock_result = sqlx::query("SELECT pg_advisory_xact_lock($1)")
|
||||
.bind(crate::STORE_SCHEMA_ADVISORY_LOCK_ID)
|
||||
.execute(&mut *transaction)
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = lock_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres store schema advisory lock failed: {error}"
|
||||
)));
|
||||
}
|
||||
let raw_result =
|
||||
execute_schema_group(&mut transaction, "raw", crate::raw_store_schema_statements()).await;
|
||||
if let std::result::Result::Err(error) = raw_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let core_result =
|
||||
execute_schema_group(&mut transaction, "core", crate::core_store_schema_statements()).await;
|
||||
if let std::result::Result::Err(error) = core_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let decode_result =
|
||||
execute_schema_group(&mut transaction, "decode", crate::decode_store_schema_statements())
|
||||
.await;
|
||||
if let std::result::Result::Err(error) = decode_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let commit_result = transaction.commit().await;
|
||||
if let std::result::Result::Err(error) = commit_result {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres store schema commit failed: {error}"
|
||||
)));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
async fn execute_schema_group(
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
model: &str,
|
||||
statements: std::vec::Vec<&'static str>,
|
||||
) -> ks_core::Result<()> {
|
||||
for (statement_index, statement) in statements.into_iter().enumerate() {
|
||||
tracing::trace!(
|
||||
target: crate::TRACING_TARGET,
|
||||
backend = "postgres",
|
||||
domain = "ks-store.pg",
|
||||
action = "schema_statement",
|
||||
model,
|
||||
statement_index,
|
||||
sql = statement,
|
||||
"execute PostgreSQL schema statement"
|
||||
);
|
||||
let execution_result = sqlx::query(statement).execute(&mut **transaction).await;
|
||||
if let std::result::Result::Err(error) = execution_result {
|
||||
tracing::error!(
|
||||
target: crate::TRACING_TARGET,
|
||||
backend = "postgres",
|
||||
domain = "ks-store.pg",
|
||||
action = "schema_statement",
|
||||
model,
|
||||
statement_index,
|
||||
"PostgreSQL schema statement failed"
|
||||
);
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres store schema statement failed for model {model} at index {statement_index}: {error}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
async fn legacy_store_schema_detected(pool: &sqlx::PgPool) -> ks_core::Result<bool> {
|
||||
let pattern = format!("{}%", crate::LEGACY_SOLANA_TABLE_PREFIX);
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, bool>(
|
||||
"SELECT EXISTS(SELECT 1 FROM pg_class object JOIN pg_namespace namespace ON namespace.oid = object.relnamespace WHERE namespace.nspname = current_schema() AND object.relkind IN ('r', 'p', 'S', 'i') AND object.relname LIKE $1)",
|
||||
)
|
||||
.bind(pattern)
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
return match query_result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres historical schema detection failed: {error}"
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn load_expected_index_counts(pool: &sqlx::PgPool) -> ks_core::Result<(u32, u32)> {
|
||||
let names = crate::expected_postgres_index_names()
|
||||
.iter()
|
||||
.map(|value| return (*value).to_string())
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let expected_count = match u32::try_from(names.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(
|
||||
"postgres expected index count exceeds u32",
|
||||
));
|
||||
},
|
||||
};
|
||||
let query_result = sqlx::query_scalar::<sqlx::Postgres, i64>(
|
||||
"SELECT COUNT(*)::BIGINT FROM pg_indexes WHERE schemaname = current_schema() AND indexname = ANY($1::TEXT[])",
|
||||
)
|
||||
.bind(&names)
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
let available_i64 = match query_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(format!(
|
||||
"postgres expected index verification failed: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let available_count = match u32::try_from(available_i64) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_error) => {
|
||||
return std::result::Result::Err(ks_core::Error::db(
|
||||
"postgres available index count exceeds u32",
|
||||
));
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok((expected_count, available_count));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn legacy_prefix_is_distinct_from_the_candidate_baseline_prefix() {
|
||||
assert_eq!(crate::LEGACY_SOLANA_TABLE_PREFIX, "kb_sol_");
|
||||
assert!(crate::RAW_TRANSACTIONS_TABLE_NAME.starts_with("k_sol_"));
|
||||
assert!(!crate::RAW_TRANSACTIONS_TABLE_NAME.starts_with(crate::LEGACY_SOLANA_TABLE_PREFIX));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/postgres/query/table_diagnostics_queries.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Read-only PostgreSQL diagnostics for known Solana store tables.
|
||||
|
||||
@@ -27,7 +27,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::RAW_TRANSACTIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_raw_transactions_sql(),
|
||||
crate::table_stats_k_sol_raw_transactions_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -35,7 +35,15 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::TRANSACTION_OBSERVATIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_obs_transaction_observations_sql(),
|
||||
crate::table_stats_k_sol_obs_transaction_observations_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
},
|
||||
crate::ACCOUNT_OBSERVATIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_k_sol_obs_account_observations_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -43,7 +51,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::CORE_TRANSACTIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_core_transactions_sql(),
|
||||
crate::table_stats_k_sol_core_transactions_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -51,7 +59,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::CORE_ACCOUNT_KEYS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_core_account_keys_sql(),
|
||||
crate::table_stats_k_sol_core_account_keys_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -59,7 +67,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::CORE_INSTRUCTIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_core_instructions_sql(),
|
||||
crate::table_stats_k_sol_core_instructions_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -67,7 +75,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::CORE_INNER_INSTRUCTIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_core_inner_instructions_sql(),
|
||||
crate::table_stats_k_sol_core_inner_instructions_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -75,7 +83,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::CORE_LOGS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_core_logs_sql(),
|
||||
crate::table_stats_k_sol_core_logs_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -83,7 +91,23 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::CORE_BALANCE_CHANGES_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_core_balance_changes_sql(),
|
||||
crate::table_stats_k_sol_core_balance_changes_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
},
|
||||
crate::CORE_RETURN_DATA_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_k_sol_core_return_data_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
},
|
||||
crate::CORE_ACCOUNT_STATES_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_k_sol_core_account_states_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -91,7 +115,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::PROCESSING_LEDGER_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_ops_processing_ledger_sql(),
|
||||
crate::table_stats_k_sol_ops_processing_ledger_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -99,7 +123,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::DECODE_EVENTS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_decode_events_sql(),
|
||||
crate::table_stats_k_sol_decode_events_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -107,7 +131,7 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::DECODE_COVERAGE_DECLARATIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_decode_coverage_declarations_sql(),
|
||||
crate::table_stats_k_sol_decode_coverage_declarations_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
@@ -115,15 +139,15 @@ pub(crate) async fn load_table_statistics(
|
||||
crate::DECODE_COVERAGE_OBSERVATIONS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_decode_coverage_observations_sql(),
|
||||
crate::table_stats_k_sol_decode_coverage_observations_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
},
|
||||
crate::MATERIALIZED_EVENTS_TABLE_NAME => {
|
||||
crate::MATERIALIZED_OUTPUTS_TABLE_NAME => {
|
||||
load_table_statistics_from_sql(
|
||||
pool,
|
||||
crate::table_stats_kb_sol_mat_events_sql(),
|
||||
crate::table_stats_k_sol_mat_outputs_sql(),
|
||||
table_name,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/postgres/repository/decode_pipeline_repository.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! PostgreSQL contextual decode and materialization repository.
|
||||
|
||||
@@ -96,10 +96,10 @@ impl crate::DecodePipelineStore for crate::PostgresStore {
|
||||
clippy::implicit_return,
|
||||
reason = "async_trait expansion triggers implicit_return on generated async trait methods."
|
||||
)]
|
||||
async fn list_materialized_events(
|
||||
async fn list_materialized_outputs(
|
||||
&self,
|
||||
filter: &crate::MaterializedEventFilter,
|
||||
) -> ks_core::Result<std::vec::Vec<crate::MaterializedEventQueryRow>> {
|
||||
return crate::list_materialized_events(self.pool(), filter).await;
|
||||
filter: &crate::MaterializedOutputFilter,
|
||||
) -> ks_core::Result<std::vec::Vec<crate::MaterializedOutputQueryRow>> {
|
||||
return crate::list_materialized_outputs(self.pool(), filter).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/postgres/store.rs
|
||||
// version: 9
|
||||
// version: 13
|
||||
|
||||
//! PostgreSQL store implementation kept behind the backend-agnostic `Store` facade.
|
||||
|
||||
@@ -171,10 +171,24 @@ impl crate::PostgresStore {
|
||||
/// Connects to PostgreSQL from validated backend options.
|
||||
pub(crate) async fn connect(options: crate::PostgresStoreOptions) -> ks_core::Result<Self> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "connection_open", "open PostgreSQL store connection");
|
||||
let connect_options_result =
|
||||
options.database_url.parse::<sqlx::postgres::PgConnectOptions>();
|
||||
let connect_options = match connect_options_result {
|
||||
std::result::Result::Ok(value) => {
|
||||
sqlx::ConnectOptions::disable_statement_logging(value)
|
||||
},
|
||||
std::result::Result::Err(_error) => {
|
||||
tracing::error!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "connection_open", connected = false, "PostgreSQL store connection options are invalid");
|
||||
return std::result::Result::Err(crate::storage_contract_error(
|
||||
"store_backend_connection_failed",
|
||||
"postgres connection failed",
|
||||
));
|
||||
},
|
||||
};
|
||||
let pool_options = sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(options.max_connections)
|
||||
.acquire_timeout(std::time::Duration::from_millis(options.connect_timeout_ms));
|
||||
let connect_result = pool_options.connect(options.database_url.as_str()).await;
|
||||
let connect_result = pool_options.connect_with(connect_options).await;
|
||||
return match connect_result {
|
||||
std::result::Result::Ok(pool) => {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "connection_open", connected = true, "PostgreSQL store connection opened");
|
||||
@@ -195,40 +209,21 @@ impl crate::PostgresStore {
|
||||
return &self.pool;
|
||||
}
|
||||
|
||||
/// Reads expected and available PostgreSQL index counts without exposing physical names.
|
||||
pub(crate) async fn index_object_counts(&self) -> ks_core::Result<(u32, u32)> {
|
||||
return crate::load_expected_index_counts(&self.pool).await;
|
||||
}
|
||||
|
||||
/// Applies each idempotent store schema once per invocation in dependency order.
|
||||
pub(crate) async fn initialize_store_schema(&self) -> ks_core::Result<()> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "schema_initialize", "initialize PostgreSQL store schema");
|
||||
let raw_result = crate::apply_raw_store_schema(&self.pool).await;
|
||||
if let std::result::Result::Err(error) = raw_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let core_result = crate::apply_core_store_schema(&self.pool).await;
|
||||
if let std::result::Result::Err(error) = core_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let result = crate::apply_decode_store_schema(&self.pool).await;
|
||||
let result = crate::apply_store_schema(&self.pool).await;
|
||||
if result.is_ok() {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = "postgres", domain = "ks-store.pg", action = "schema_initialize", initialized = true, "PostgreSQL store schema initialized");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Applies the idempotent minimal raw Solana store schema.
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn initialize_raw_store_schema(&self) -> ks_core::Result<()> {
|
||||
return crate::apply_raw_store_schema(&self.pool).await;
|
||||
}
|
||||
|
||||
/// Applies the idempotent minimal Core Solana store schema.
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn initialize_core_store_schema(&self) -> ks_core::Result<()> {
|
||||
let raw_result = self.initialize_raw_store_schema().await;
|
||||
if let std::result::Result::Err(error) = raw_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return crate::apply_core_store_schema(&self.pool).await;
|
||||
}
|
||||
|
||||
/// Reads a UI-safe backend descriptor.
|
||||
pub(crate) async fn backend_descriptor(
|
||||
&self,
|
||||
@@ -265,27 +260,45 @@ impl crate::PostgresStore {
|
||||
};
|
||||
}
|
||||
|
||||
/// Reads a non-destructive migration snapshot.
|
||||
/// Reads the current store schema contract status without relying on backend migration history.
|
||||
pub(crate) async fn migration_snapshot(
|
||||
&self,
|
||||
) -> ks_core::Result<crate::StoreMigrationSnapshot> {
|
||||
let migration_table_result = crate::load_migration_table_name(&self.pool).await;
|
||||
return match migration_table_result {
|
||||
std::result::Result::Ok(std::option::Option::None) => {
|
||||
std::result::Result::Ok(crate::StoreMigrationSnapshot::new(
|
||||
crate::StoreMigrationStatus::NotInitialized,
|
||||
std::option::Option::None,
|
||||
std::vec::Vec::new(),
|
||||
std::option::Option::Some(std::string::String::from(
|
||||
"no migration history table detected; crate-managed schema initialization is active",
|
||||
)),
|
||||
))
|
||||
},
|
||||
std::result::Result::Ok(std::option::Option::Some(_table_name)) => {
|
||||
self.migration_snapshot_from_existing_table().await
|
||||
},
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
let resources_result = self.known_resource_diagnostics().await;
|
||||
let resources = match resources_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let expected_count = resources.len();
|
||||
let available_count = resources.iter().filter(|resource| return resource.available).count();
|
||||
if available_count == 0 {
|
||||
return std::result::Result::Ok(crate::StoreMigrationSnapshot::new(
|
||||
crate::StoreMigrationStatus::NotInitialized,
|
||||
std::option::Option::None,
|
||||
std::vec::Vec::new(),
|
||||
std::option::Option::Some(std::string::String::from(
|
||||
"store baseline schema is not initialized",
|
||||
)),
|
||||
));
|
||||
}
|
||||
if available_count == expected_count {
|
||||
return std::result::Result::Ok(crate::StoreMigrationSnapshot::new(
|
||||
crate::StoreMigrationStatus::Current,
|
||||
std::option::Option::Some(crate::STORE_SCHEMA_CONTRACT_VERSION.to_string()),
|
||||
std::vec::Vec::new(),
|
||||
std::option::Option::Some(std::string::String::from(
|
||||
"store baseline schema contract is current",
|
||||
)),
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(crate::StoreMigrationSnapshot::new(
|
||||
crate::StoreMigrationStatus::Drift,
|
||||
std::option::Option::None,
|
||||
std::vec::Vec::new(),
|
||||
std::option::Option::Some(std::string::String::from(
|
||||
"store baseline schema is partially initialized",
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
/// Reads a complete backend-neutral diagnostic snapshot.
|
||||
@@ -420,25 +433,6 @@ impl crate::PostgresStore {
|
||||
}
|
||||
return std::result::Result::Ok(diagnostics);
|
||||
}
|
||||
|
||||
async fn migration_snapshot_from_existing_table(
|
||||
&self,
|
||||
) -> ks_core::Result<crate::StoreMigrationSnapshot> {
|
||||
let version_result = crate::load_latest_migration_version(&self.pool).await;
|
||||
return match version_result {
|
||||
std::result::Result::Ok(current_version) => {
|
||||
std::result::Result::Ok(crate::StoreMigrationSnapshot::new(
|
||||
crate::StoreMigrationStatus::Current,
|
||||
current_version,
|
||||
std::vec::Vec::new(),
|
||||
std::option::Option::Some(std::string::String::from(
|
||||
"migration history table detected",
|
||||
)),
|
||||
))
|
||||
},
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a PostgreSQL connection descriptor masked for logs and diagnostics.
|
||||
@@ -489,6 +483,14 @@ fn query_suffix(had_query: bool) -> std::string::String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn postgres_connection_disables_statement_logging() {
|
||||
let source = include_str!("store.rs");
|
||||
assert!(source.contains("parse::<sqlx::postgres::PgConnectOptions>()"));
|
||||
assert!(source.contains("sqlx::ConnectOptions::disable_statement_logging(value)"));
|
||||
assert!(source.contains("connect_with(connect_options)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backend_options_reject_empty_database_url() {
|
||||
let result = crate::PostgresStoreOptions::new(" ", 1, 1000, false);
|
||||
|
||||
Reference in New Issue
Block a user