{"record":{"id":"ad6546b3d4fa3626","repo":"nautechsystems/nautilus_trader","slug":"migration-query-requires-plaintext","errorCode":null,"errorMessage":"migration query requires plaintext","messagePattern":"migration query requires plaintext","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":4958,"sourceCode":"            self.complete_execution_payload_migration(&mut transaction, keys)\n                .await?;\n            transaction\n                .commit()\n                .await\n                .context(\"failed to commit execution payload migration completion\")?;\n            return Ok(true);\n        }\n\n        for hash in rows {\n            anyhow::ensure!(\n                hash.sealed_transaction.is_none(),\n                \"Execution transaction {} contains both plaintext and sealed payloads\",\n                hash.id\n            );\n            let raw_transaction = hash\n                .raw_transaction\n                .as_deref()\n                .expect(\"migration query requires plaintext\");\n            let intent = load_execution_intent(&mut transaction, hash.intent_id).await?;\n            let context = authenticate_retained_payload(\n                raw_transaction,\n                &intent,\n                &hash,\n                keys.deployment_id(),\n            )\n            .with_context(|| format!(\"failed to authenticate execution payload {}\", hash.id))?;\n            reserve_execution_payload_seal(&mut transaction, keys.active_key_id()).await?;\n            let envelope = keys.seal(raw_transaction, &context)?;\n            let unsealed = keys.unseal(&envelope, &context)?;\n            authenticate_retained_payload(&unsealed, &intent, &hash, keys.deployment_id())?;\n            anyhow::ensure!(\n                unsealed == raw_transaction,\n                \"Execution payload {} changed during seal round trip\",\n                hash.id\n            );\n            let result = sqlx::query(","sourceCodeStart":4940,"sourceCodeEnd":4976,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L4940-L4976","documentation":"During the migration query path, a transaction-hash row contains both a plaintext `raw_transaction` and a sealed payload, and the code dereferences the plaintext field expecting it to be present — panicking with \"migration query requires plaintext\" if it is `None`. The migration path only supports rows stored as plaintext, so a missing raw transaction is treated as a programmer/data invariant violation.","triggerScenarios":"Calling the migration query on an `execution_transaction_hash` row whose `raw_transaction` is NULL (the row was stored sealed-only), after the code has already flagged the both-representations conflict.","commonSituations":"Migrating a database where rows were written by a newer sealed-payload code path; mixed old/new schema rows; running the migration against data written by a version that sealed payloads without keeping plaintext.","solutions":["Unseal the sealed payload into plaintext (with `keys.unseal`) before running the migration path.","Re-run the migration only on rows stored in plaintext; filter sealed-only rows out beforehand.","Restore or backfill `raw_transaction` for the affected rows.","Check that the migration code path matches the storage representation of your rows (plaintext vs sealed)."],"exampleFix":"// before\nlet raw_transaction = hash.raw_transaction.as_deref()\n    .expect(\"migration query requires plaintext\");\n// after\nlet raw_transaction = match hash.raw_transaction.as_deref() {\n    Some(raw) => raw,\n    None => {\n        let envelope = hash.sealed_transaction.as_deref()\n            .ok_or_else(|| anyhow!(\"row {} has neither payload form\", hash.id))?;\n        &keys.unseal(envelope, &payload_context(&intent, &hash, keys.deployment_id())?)?\n    }\n};","handlingStrategy":"validation","validationCode":"if hash.raw_transaction.is_none() {\n    // unseal from sealed_transaction first, or skip this row in the migration\n}","typeGuard":"fn has_plaintext(h: &ExecutionTransactionHashRow) -> bool { h.raw_transaction.is_some() }","tryCatchPattern":"let Some(raw) = hash.raw_transaction.as_deref() else {\n    return Err(anyhow!(\"row {} stored sealed-only; unseal before migration\", hash.id));\n};","preventionTips":["Run migrations only on rows matching the expected storage representation.","Backfill/unseal sealed rows into plaintext before plaintext-requiring migrations.","Audit schema state (raw vs sealed columns) before running migration queries."],"tags":["database","migration","panic","null-value"],"backgroundTag":"null-argument","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}