diff --git a/Cargo.toml b/Cargo.toml index 9c6f872..81a01ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 69 +# version: 72 [workspace] resolver = "3" @@ -19,7 +19,7 @@ members = [ ] [workspace.package] -version = "0.5.3-pre.2" +version = "0.5.3-pre.3" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" diff --git a/config/exemples/example.logging.config.json b/config/exemples/example.logging.config.json index cb44904..69df5ce 100644 --- a/config/exemples/example.logging.config.json +++ b/config/exemples/example.logging.config.json @@ -34,14 +34,34 @@ } ], "target_filters": [ + { + "target": "h2", + "level": "warn" + }, { "target": "hyper", "level": "warn" }, + { + "target": "hyper_util", + "level": "warn" + }, { "target": "reqwest", "level": "warn" }, + { + "target": "rustls", + "level": "warn" + }, + { + "target": "rustls_platform_verifier", + "level": "warn" + }, + { + "target": "sqlx", + "level": "warn" + }, { "target": "tao", "level": "warn" diff --git a/config/logging.config.json b/config/logging.config.json index ebc0e9e..0ab6d8a 100644 --- a/config/logging.config.json +++ b/config/logging.config.json @@ -1168,14 +1168,34 @@ } ], "target_filters": [ + { + "target": "h2", + "level": "warn" + }, { "target": "hyper", "level": "warn" }, + { + "target": "hyper_util", + "level": "warn" + }, { "target": "reqwest", "level": "warn" }, + { + "target": "rustls", + "level": "warn" + }, + { + "target": "rustls_platform_verifier", + "level": "warn" + }, + { + "target": "sqlx", + "level": "warn" + }, { "target": "tao", "level": "warn" @@ -2352,14 +2372,34 @@ } ], "target_filters": [ + { + "target": "h2", + "level": "warn" + }, { "target": "hyper", "level": "warn" }, + { + "target": "hyper_util", + "level": "warn" + }, { "target": "reqwest", "level": "warn" }, + { + "target": "rustls", + "level": "warn" + }, + { + "target": "rustls_platform_verifier", + "level": "warn" + }, + { + "target": "sqlx", + "level": "warn" + }, { "target": "tao", "level": "warn" @@ -3536,14 +3576,34 @@ } ], "target_filters": [ + { + "target": "h2", + "level": "warn" + }, { "target": "hyper", "level": "warn" }, + { + "target": "hyper_util", + "level": "warn" + }, { "target": "reqwest", "level": "warn" }, + { + "target": "rustls", + "level": "warn" + }, + { + "target": "rustls_platform_verifier", + "level": "warn" + }, + { + "target": "sqlx", + "level": "warn" + }, { "target": "tao", "level": "warn" diff --git a/docs/architecture/STORAGE_ARCHITECTURE.md b/docs/architecture/STORAGE_ARCHITECTURE.md index 6c2de3f..ce89932 100644 --- a/docs/architecture/STORAGE_ARCHITECTURE.md +++ b/docs/architecture/STORAGE_ARCHITECTURE.md @@ -1,5 +1,5 @@ - + # Architecture du stockage @@ -9,42 +9,45 @@ - la façade publique backend-agnostique `Store` et ses options d'ouverture ; - les contrats store-neutral ; -- les DTO et entités persistées ; +- les DTO et entités persistés ; - la pagination, le replay et les rapports de santé ; - les traits de repositories ; - les résumés runtime/init sanitisés ; -- l’implémentation PostgreSQL privée ; -- les migrations et requêtes appartenant au backend actif. +- l'implémentation PostgreSQL privée ; +- les ressources de schéma, maintenance et requêtes appartenant au backend actif. Depuis `0.5.3-pre.002`, les consommateurs ne construisent plus un backend concret. `ks-store` possède la connexion et le dispatch interne ; `PostgresStore`, `PgPool` et SQLx ne traversent plus sa frontière publique. +Depuis `0.5.3-pre.003`, PostgreSQL construit directement le nouveau baseline `k_sol_*` à partir de ressources SQL atomiques ; les migrations historiques `0001` à `0004` ne constituent plus la chaîne active. + ## 2. Frontières ### 2.1 Contrats store-neutral -Les contrats ne dépendent pas des détails SQL lorsqu’une abstraction stable est suffisante. Ils décrivent les entrées, sorties, identifiants, pages, diagnostics et erreurs nécessaires aux consommateurs. `StoreOpenOptions` transporte un code backend et un objet d'options opaque ; seul `ks-store` interprète le moteur sélectionné. +Les contrats ne dépendent pas des détails SQL lorsqu'une abstraction stable est suffisante. Ils décrivent les entrées, sorties, identifiants, pages, diagnostics et erreurs nécessaires aux consommateurs. `StoreOpenOptions` transporte un code backend et un objet d'options opaque ; seul `ks-store` interprète le moteur sélectionné. -Le résumé runtime expose des modèles logiques et des compteurs d'objets par catégorie sans publier les noms physiques. PostgreSQL peut ainsi exposer des compteurs `table`/`index`, tandis qu'un futur backend peut utiliser d'autres catégories sans modifier le desktop. +Le résumé runtime expose des modèles logiques et des compteurs d'objets par catégorie sans publier les noms physiques. PostgreSQL expose actuellement des compteurs `table` et `index`, tandis qu'un futur backend peut utiliser d'autres catégories sans modifier le desktop. ### 2.2 Adaptateur PostgreSQL Le module PostgreSQL possède : - la validation de ses options ; -- la connexion et l’initialisation ; -- l’application idempotente des migrations ; +- la connexion et l'initialisation ; +- l'orchestrateur transactionnel global du baseline ; +- les ressources SQL `include_str!` ; - les requêtes typées ; - les diagnostics physiques ; - les opérations de replay et de sélection de candidats ; -- le target de tracing canonique `ks-store`, enrichi des champs `backend="postgres"`, `domain="ks-store.pg"` et `action` pour les opérations PostgreSQL. +- le target de tracing canonique `ks-store`, enrichi des champs `backend="postgres"`, `domain="ks-store.pg"` et `action`. Les détails physiques restent privés. Les erreurs et résumés traversant la façade ne doivent pas divulguer de DSN, password ou option sensible. ### 2.3 Modèles partagés -Les modèles métier communs restent dans `ks-lib` lorsqu’ils dépassent la seule persistance. `ks-store` ne doit pas créer une seconde définition concurrente d’un contrat partagé. +Les modèles métier communs restent dans `ks-lib` lorsqu'ils dépassent la seule persistance. `ks-store` ne doit pas créer une seconde définition concurrente d'un contrat partagé. -## 3. Catégories de données +## 3. Niveaux de persistance Le stockage distingue quatre niveaux durables : @@ -53,11 +56,101 @@ Le stockage distingue quatre niveaux durables : - **N3** — decode/materialization générique et journaux versionnés ; - **N4** — projections spécialisées/queryables additives. -N1/N2/N3 constituent la fondation fortement gelée après `0.5.3`, sous réserve d'une urgence ou d'une omission structurelle majeure. N4 reste plus évolutif. Les replays N1 -> N2, N2 -> N3 et N3 -> N4 doivent pouvoir être exécutés indépendamment et de manière idempotente. +N1/N2/N3 constituent la fondation candidate au gel de `0.5.3`. Le gel formel n'est déclaré qu'après snapshot et validations de réconciliation ; après clôture, leur évolution normale doit être fortement contrainte. N4 reste volontairement plus évolutif. -Les noms de tables, contrats de replay et APIs publiques sont documentés dans `ks-store/USAGE.md` à partir des exports et migrations actuels. +Les replays N1 -> N2, N2 -> N3 et N3 -> N4 doivent pouvoir être exécutés indépendamment et de manière idempotente. -## 4. Propriétés attendues +## 4. Baseline N1-N3 de `pre.003` + +Le baseline PostgreSQL comporte **16 tables**. + +### 4.1 N1 + +- `k_sol_raw_transactions` ; +- `k_sol_obs_transaction_observations` ; +- `k_sol_obs_account_observations`. + +`k_sol_raw_transactions` conserve le document canonique source-independent et `block_time` lorsqu'il est fourni par Solana. Les observations d'acquisition restent séparées de ce document canonique. + +`k_sol_obs_account_observations` réserve un input générique pour les acquisitions d'état de compte : identité du compte, slot/context, bytes exacts, hash et provenance d'acquisition. Cette structure n'est liée à aucun décodeur ou IDL. + +### 4.2 N2 Core + +- `k_sol_core_transactions` ; +- `k_sol_core_account_keys` ; +- `k_sol_core_instructions` ; +- `k_sol_core_inner_instructions` ; +- `k_sol_core_logs` ; +- `k_sol_core_balance_changes` ; +- `k_sol_core_return_data` ; +- `k_sol_core_account_states`. + +`k_sol_core_transactions` porte l'ancre transactionnelle et `block_time`. + +Les instructions top-level et CPI restent physiquement séparées. Les deux portent `stack_height` lorsque disponible ; les CPI conservent leur parent immédiat reconstructible. Elles doivent converger vers des capacités lifecycle/replay équivalentes sans imposer un stockage physique unique. + +`k_sol_core_return_data` évite de perdre en N2 la `returnData` déjà présente dans la transaction canonique N1. + +`k_sol_core_account_states` fournit le pendant Core des observations de comptes et conserve les bytes exacts ainsi que leur provenance N1. + +### 4.3 N3 et transverse + +- `k_sol_ops_processing_ledger` ; +- `k_sol_decode_events` ; +- `k_sol_decode_coverage_declarations` ; +- `k_sol_decode_coverage_observations` ; +- `k_sol_mat_outputs`. + +`k_sol_mat_outputs` est le journal générique durable des sorties de matérialisation. Les projections métier N4 ne le remplacent pas. + +`k_sol_decode_events` réserve une provenance de schéma nullable (`kind/id/version/hash`) pour les mécanismes de décodage pouvant dépendre d'un schéma externe. Les décodeurs compilés actuels la laissent vide. + +## 5. Readiness des futurs décodeurs + +Le canari future-decoder ne doit pas introduire Anchor dans le schéma. Il vérifie que les faits Solana génériques suffisants sont conservés : + +- bytes d'instruction et comptes ordonnés ; +- propriétés signer/writable résolubles ; +- instructions top-level et CPI ; +- `stack_height` et parent CPI ; +- logs ordonnés ; +- `returnData` ; +- observations/états bruts de comptes ; +- temporalité et provenance ; +- identité/version/hash d'un schéma externe lorsqu'un décodeur en utilise un. + +La conséquence de `pre.003` est l'ajout de trois contrats génériques au-dessus des 13 tables historiques : observation de compte, état Core de compte et return data Core. Le baseline candidat au gel passe donc à 16 tables. + +Les métriques transactionnelles comme fee, rewards, compute units et cost units restent disponibles dans N1. Elles ne sont pas requises pour ce canari et ne justifient pas une extension de l'ancre Core en `pre.003`; une future projection additive pourra les rendre queryables si nécessaire. + +## 6. Ressources PostgreSQL + +Les ressources actives sont classées sous : + +```text +ks-store/migrations/postgres/ + schema/ + tables/ + constraints/ + indexes/ + maintenance/ + truncate/ + drop/ +``` + +Principes : + +- une instruction SQL par fichier ; +- un `include_str!` privé par ressource ; +- aucun DDL dupliqué dans une chaîne Rust ; +- tables avant contraintes, contraintes avant index ; +- initialisation complète dans une transaction PostgreSQL unique avec advisory lock ; +- `DROP`/`TRUNCATE` séparés de l'auto-init ; +- refus explicite d'un schéma contenant encore des objets `kb_sol_*`. + +Le baseline `pre.003` contient **236 ressources SQL atomiques** et **75 index attendus**. Ces nombres décrivent l'implémentation PostgreSQL actuelle, pas une API publique gelée. + +## 7. Propriétés attendues - initialisation idempotente ; - pagination bornée ; @@ -65,12 +158,12 @@ Les noms de tables, contrats de replay et APIs publiques sont documentés dans ` - absence de double effet lors des replays ; - validation stricte des entrées ; - erreurs explicites ; -- séparation entre données brutes, résultats de décodage et matérialisations. +- séparation entre acquisition/raw, Core, decode/materialization et projections N4 ; +- absence de dépendance de N1/N2 à une technologie de décodeur ; +- conservation des faits nécessaires à un redécodage historique. -La série `0.5.3` normalisera cette fondation avant l’arrivée des protocoles trading. Elle doit notamment distinguer `slot`, `block_time` et les timestamps d’acquisition/persistance, normaliser la structure interne de `ks-store`, vérifier les champs et index manquants et préparer les futures matérialisations trading, routing et multi-pools sans perdre les contrats de provenance et d’idempotence. +Les index non uniques restent des optimisations physiques évolutives. Les noms/colonnes/types/nullabilités/PK/FK/uniques/checks/sémantiques du baseline N1-N3 seront figés séparément lors de la tranche de gel. -La même migration remplace le préfixe historique des tables Solana `kb_sol_*` par `k_sol_*`. La base peut encore être reconstruite ou migrée proprement avant `0.6.x`, il n’est donc pas nécessaire de conserver indéfiniment l’ancien préfixe. Le préfixe `kb_*` est réservé aux éventuelles tables dont la responsabilité appartient réellement au domaine applicatif Bot, pas aux faits Solana simplement consommés par le bot. +## 8. Données de test -## 5. Données de test - -Les fixtures privées, bases locales et preuves temporaires ne font pas partie des livraisons. Les matrices contractuelles partagées restent sous `test-fixtures/contract-matrices/` lorsqu’elles sont nécessaires aux tests. +Les fixtures privées, bases locales et preuves temporaires ne font pas partie des livraisons. Les matrices contractuelles partagées restent sous `test-fixtures/contract-matrices/` lorsqu'elles sont nécessaires aux tests. diff --git a/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md b/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md index bb3b475..912847c 100644 --- a/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md +++ b/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md @@ -1,5 +1,5 @@ - + # Politique de namespace Khadhroony Solana @@ -7,7 +7,7 @@ Cette décision est adoptée pendant la clôture de `0.5.0` et devient la cible normative des migrations de fondation `0.5.1` à `0.5.3`. -À partir de `0.5.1-pre.002`, les dix crates Solana généralistes sont physiquement nommées `ks-*` et leurs identifiants Rust `ks_*`. `0.5.1-pre.003` migre également les identités techniques `kb-lib.*` vers `ks-lib-*`. `0.5.1-pre.004` migre les variables possédées par Khadhroony Solana vers `KS_*` et réserve explicitement `KB_*` aux composants réellement possédés par Khadhroony Bot. Le namespace SQL `kb_sol_*` reste temporairement présent jusqu'à `0.5.3`. +À partir de `0.5.1-pre.002`, les dix crates Solana généralistes sont physiquement nommées `ks-*` et leurs identifiants Rust `ks_*`. `0.5.1-pre.003` migre également les identités techniques `kb-lib.*` vers `ks-lib-*`. `0.5.1-pre.004` migre les variables possédées par Khadhroony Solana vers `KS_*` et réserve explicitement `KB_*` aux composants réellement possédés par Khadhroony Bot. Le namespace SQL historique `kb_sol_*` est remplacé par `k_sol_*` dans le baseline PostgreSQL de `0.5.3-pre.003`. Le renommage des bibliothèques internes ne renomme pas le workspace, le dépôt ni le répertoire racine : ils restent `khadhroony-bot3` pendant toute la série pré-`1.0`. Un éventuel renommage du workspace racine est explicitement hors périmètre de `0.5.x` et ne doit intervenir qu'en `1.0` ou ultérieurement sur décision dédiée. @@ -108,7 +108,7 @@ Les segments métier internes (`solana`, `spl`, protocoles, surfaces et opérati La normalisation SQL appartient à `0.5.3` avec `ks-store`. -Les tables génériques représentant des faits Solana doivent migrer du préfixe actuel `kb_sol_*` vers : +Depuis `0.5.3-pre.003`, le baseline PostgreSQL actif des faits Solana utilise : ```text k_sol_* @@ -124,7 +124,7 @@ kb_* Une table ne reçoit pas `kb_*` simplement parce qu'elle est consommée par le bot. Elle doit contenir des données dont la responsabilité appartient réellement au domaine applicatif Bot et non à la blockchain ou aux bibliothèques Solana généralistes. -La migration `kb_sol_*` → `k_sol_*` doit être traitée avec les contrats temporels, de provenance, d'idempotence, d'index et de replay de `0.5.3`, et non comme un remplacement textuel isolé. +La reconstruction `kb_sol_*` → `k_sol_*` de `0.5.3-pre.003` est traitée avec les contrats temporels, de provenance, d'idempotence, d'index et de replay ; l'ancien namespace reste uniquement un marqueur détecté/refusé et une référence historique. ## 8. Ordre des migrations diff --git a/docs/guides/POSTGRES_STORAGE.md b/docs/guides/POSTGRES_STORAGE.md index 81f65cf..aa4e943 100644 --- a/docs/guides/POSTGRES_STORAGE.md +++ b/docs/guides/POSTGRES_STORAGE.md @@ -1,5 +1,5 @@ - + # Guide PostgreSQL et contrats de stockage @@ -35,47 +35,114 @@ Le descripteur public éventuellement affiché provient du résumé runtime et e ## Domaines -- raw : acquisitions et observations ; -- Core : transactions, instructions, comptes et contexte normalisés ; -- decode : ledger, observations décodées et matérialisations ; -- replay : candidats et résumés bornés. +- N1 raw/acquisition : transactions canoniques, observations de transactions et observations génériques de comptes ; +- N2 Core : transactions, clés, top-level, CPI, logs, variations de balances, return data et états de comptes ; +- N3/transverse : ledger, observations décodées, couverture et `k_sol_mat_outputs` ; +- replay/diagnostic : sélections et résumés bornés au-dessus de ces contrats. -## Migrations +## Baseline `0.5.3-pre.003` -`0.5.3-pre.002` conserve temporairement le schéma PostgreSQL historique `kb_sol_*` et son mécanisme d'initialisation afin de réaliser d'abord la rupture d'API backend. `pre.003` reconstruit le baseline sous `k_sol_*`, range les ressources sous `ks-store/migrations/postgres/` et impose une instruction SQL par fichier exécutée par les fonctions privées du backend. +`pre.003` reconstruit directement le schéma sous `k_sol_*`. Les bases Devnet/Mainnet/test historiques sont considérées reconstructibles ; aucune migration in-place depuis `kb_sol_*` n'est fournie. -Les bases Devnet/Mainnet/test `0.5.2` sont reconstructibles : aucune compatibilité in-place n'est requise pour ce changement de baseline. +Le comportement attendu à l'ouverture est : + +1. base vide + auto-init : création du baseline complet ; +2. base `k_sol_*` déjà conforme : vérification/idempotence ; +3. base contenant encore des objets `kb_sol_*` : refus non destructif et demande de reconstruction explicite ; +4. `DROP`/`TRUNCATE` : opérations de maintenance distinctes, jamais déclenchées implicitement par `Store::open()`. + +Le baseline candidat au gel comprend 16 tables N1-N3. + +## Ressources SQL + +Les ressources actives sont sous : + +```text +ks-store/migrations/postgres/ + schema/ + tables/ + constraints/ + indexes/ + maintenance/ + truncate/ + drop/ +``` + +Chaque fichier contient une instruction SQL. Le code PostgreSQL privé utilise `include_str!` et l'orchestrateur global exécute le schéma dans une transaction unique avec advisory lock. + +Il n'existe plus trois initialiseurs raw/Core/decode indépendants : **l'initialisation du schéma est globale**. + +Le corpus `pre.003` contient actuellement : + +- 16 créations de tables ; +- 129 ressources de contraintes ; +- 59 créations d'index explicites ; +- 16 ressources `TRUNCATE` ; +- 16 ressources `DROP` ; +- soit **236 ressources SQL**. + +PostgreSQL attend **75 index** au total dans son diagnostic : 59 index explicites plus les 16 index de clé primaire créés par PostgreSQL. + +Les index non uniques restent des optimisations physiques et ne font pas partie du gel logique N1-N3. + +## Statut du schéma + +La façade n'utilise plus `_sqlx_migrations` comme preuve du baseline actuel. Le statut est dérivé du contrat réellement observé : + +- aucune ressource connue : non initialisé ; +- toutes les ressources attendues : contrat courant ; +- présence partielle : drift/partial. + +Le résumé d'initialisation expose des compteurs de modèles, tables et index sans publier leurs noms physiques. ## Compatibilité des identités de processeurs -Depuis `0.5.1-pre.003`, les identités techniques produites par `ks-lib` utilisent `ks-lib-decoder.*`, `ks-lib-executor.*` et `ks-lib-materializer.*`. Une base alimentée avant cette migration peut encore contenir des valeurs `kb-lib.*` dans les contrats de provenance/replay. +Depuis `0.5.1-pre.003`, les identités techniques produites par `ks-lib` utilisent `ks-lib-decoder.*`, `ks-lib-executor.*` et `ks-lib-materializer.*`. -Aucune migration SQL automatique de ces valeurs n'est ajoutée en `0.5.1` : avant `0.6.x`, une base conservée doit soit être reconstruite proprement, soit faire l'objet d'une migration de données explicitement contrôlée avant reprise du replay. Il ne faut pas exploiter durablement un même corpus avec les anciennes et nouvelles identités mélangées. Les noms de tables `kb_sol_*` restent inchangés jusqu'au chantier `0.5.3`. +Comme les bases `0.5.2` doivent être reconstruites pour le nouveau baseline `0.5.3`, les anciennes identités `kb-lib.*` ne sont pas migrées automatiquement dans ce chantier. + +## Core readiness + +`pre.003` ajoute les éléments génériques nécessaires au futur redécodage : + +- `block_time` sur raw/Core transaction ; +- `stack_height` sur top-level/CPI ; +- parent CPI immédiat reconstructible ; +- `k_sol_core_return_data` ; +- `k_sol_obs_account_observations` ; +- `k_sol_core_account_states` ; +- provenance de schéma optionnelle sur les observations décodées. + +Ces contrats sont indépendants d'Anchor/IDL. Aucun parser Anchor ni table `anchor_*` n'appartient à `0.5.3`. ## Repositories -Les traits publics et la façade `Store` décrivent des opérations fonctionnelles indépendantes du moteur. PostgreSQL implémente ces contrats à l’intérieur de `ks-store`; aucune crate consommatrice ne reçoit `PgPool`, un type SQLx ou une requête concrète. Les opérations de lecture utilisent des filtres et paginations bornés. +Les traits publics et la façade `Store` décrivent des opérations fonctionnelles indépendantes du moteur. PostgreSQL implémente ces contrats à l'intérieur de `ks-store`; aucune crate consommatrice ne reçoit `PgPool`, un type SQLx ou une requête concrète. + +Le schéma des observations/états de comptes est introduit avant gel en `pre.003`; son ingestion/replay fonctionnel est raccordé dans la tranche repositories/replay suivante. ## Diagnostics La surface publique expose : - health snapshot ; -- migration snapshot ; +- schema/migration snapshot générique ; - backend descriptor masqué ; - résumé de configuration sanitisé ; - résumé d'initialisation/vérification par modèle logique ; -- diagnostics de ressources raw/Core/decode/materialization sans nom physique d'objet. +- diagnostics de ressources sans nom physique d'objet. -Les noms de tables, index, contraintes et le SQL restent des détails backend-internes. Les traces PostgreSQL utilisent l’unique `target: crate::TRACING_TARGET` (`ks-store`) avec les champs structurés `backend="postgres"`, `domain="ks-store.pg"` et un `action` précis (`connection`, `migration`, `query`, `health`, `replay`, etc.). +Les noms de tables, index, contraintes et le SQL restent des détails backend-internes. Les traces PostgreSQL utilisent l'unique `target: crate::TRACING_TARGET` (`ks-store`) avec les champs structurés `backend="postgres"`, `domain="ks-store.pg"` et un `action` précis. ## Invariants - aucune donnée canonique ne doit être dupliquée sans justification ; - les écritures rejouables doivent être idempotentes ; - la progression de campagne doit rester cohérente avec les lignes effectivement traitées ; -- les requêtes dynamiques n’acceptent que des identifiants validés ; -- les détails PostgreSQL utiles au diagnostic restent backend-internes ; les erreurs traversant la façade publique ne doivent jamais divulguer DSN, password ou options sensibles. +- les requêtes dynamiques n'acceptent que des identifiants validés ; +- les détails PostgreSQL utiles au diagnostic restent backend-internes ; +- les erreurs traversant la façade publique ne doivent jamais divulguer DSN, password ou options sensibles ; +- un nouveau décodeur doit pouvoir exploiter N1/N2/N3 sans remodelage normal de ces niveaux. ## Références diff --git a/docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md b/docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md index edf0953..2c3c459 100644 --- a/docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md +++ b/docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.5.3` — audit et normalisation de `ks-store` @@ -174,7 +174,7 @@ Les fonctions privées PostgreSQL correspondantes sont séparées et utilisent ` | `k_sol_decode_events` | `DecodeObservationInsert` | `DecodePipelineStore` | `decode_pipeline_queries` | `ks-pipeline::decode_replay` | | `k_sol_decode_coverage_declarations` | `DecodeCoverageDeclarationInsert` | `DecodePipelineStore` | `decode_pipeline_queries` | `ks-pipeline::decode_replay` | | `k_sol_decode_coverage_observations` | `DecodeCoverageObservationInsert`, `DecodeCoverageSummaryRow` | `DecodePipelineStore` | `decode_pipeline_queries` | pipeline + desktop diagnostics | -| `k_sol_mat_outputs` | `MaterializedOutputInsert`, `MaterializedEventQueryRow` | `DecodePipelineStore` | `decode_pipeline_queries` | pipeline, scénarios Devnet, desktop | +| `k_sol_mat_outputs` | `MaterializedOutputInsert`, `MaterializedOutputQueryRow` | `DecodePipelineStore` | `decode_pipeline_queries` | pipeline, scénarios Devnet, desktop | ### 5.1 Contrats publics hérités sans implémentation @@ -1079,7 +1079,7 @@ La même règle s'applique à tout autre protocole : une table nommée d'après - réouverture idempotente d'une base déjà initialisée ; - refus propre d'une base contenant encore des objets `kb_sol_*` ; - absence finale de tables, contraintes, index et séquences `kb_sol_*` après reconstruction ; -- présence des **13 tables de baseline**, plus des éventuels contrats N1/N2/N3 génériques explicitement retenus après le canari future-decoder ; +- présence des **16 tables du baseline candidat au gel** retenu en `pre.003` ; - snapshot exact des colonnes/types/nullabilités/PK/FK/uniques/checks frozen ; - présence et contrat séparé des tables top-level/CPI ; - test de lecture logique combinée top-level+CPI sans perte, doublon ni ordre instable ; @@ -1154,7 +1154,7 @@ Avec `KS_SECRET_POSTGRES_TEST_URL` local : ## 26. Snapshot de contrat frozen -Le snapshot `0.5.3` couvre les **13 tables existantes de baseline, plus toute structure N1/N2/N3 générique dont le canari future-decoder démontre la nécessité avant gel**, ainsi que les tables transverses de processing/decode qui assurent leur idempotence et leur journalisation. Les futures projections N4 disposent de contrats/version propres, mais ne sont pas ajoutées à ce snapshot frozen fondamental. +Le snapshot `0.5.3` couvre les **16 tables du baseline candidat au gel retenu en `pre.003`**, y compris les trois contrats génériques ajoutés par le canari future-decoder (`account observation`, `account state`, `transaction return data`), ainsi que les tables transverses de processing/decode qui assurent leur idempotence et leur journalisation. Les futures projections N4 disposent de contrats/version propres, mais ne sont pas ajoutées à ce snapshot frozen fondamental. Une fixture machine-readable sera créée avant clôture, par exemple : @@ -1320,7 +1320,7 @@ cargo tauri dev -c kb-app-demo-desktop/tauri.conf.json La version ne peut être clôturée que si : - il n'existe plus d'objet actif `kb_sol_*` ; -- les 13 tables de baseline et toute table N1/N2/N3 générique ajoutée après le canari future-decoder correspondent exactement au snapshot frozen documenté ; +- les 16 tables du baseline candidat retenu en `pre.003` correspondent exactement au snapshot frozen documenté ; - `block_time` est conservé lorsqu'il existe et reste `NULL` sinon ; - les top-level et CPI restent physiquement séparées mais peuvent être sélectionnées séparément ou comme flux logique commun pour le decode ; - replay et invalidation descendante sont cohérents et transactionnels, avec contrats N1 -> N2, N2 -> N3 et frontière N3 -> N4 documentée/testable ; @@ -1453,3 +1453,159 @@ Le nettoyage fin de l’instrumentation des requêtes (durées, nombres de ligne - n’implémente pas Anchor. Ces travaux restent affectés aux tranches `pre.003+` prévues par le présent plan. + +## 33. État réalisé de `0.5.3-pre.003` + +`pre.003` réalise la reconstruction physique PostgreSQL et ferme les principaux manques révélés par le canari future-decoder. Le résultat est un **baseline candidat au gel**, pas encore le snapshot frozen final de `pre.006`. + +### 33.1 Baseline candidat : 16 tables + +Les 13 tables historiques sont conservées conceptuellement sous le namespace `k_sol_*`, sans fusion des instructions top-level/CPI. Trois contrats Solana génériques sont ajoutés avant gel : + +1. `k_sol_obs_account_observations` — observation N1 d'un état de compte avec bytes exacts, hash et provenance d'acquisition ; +2. `k_sol_core_account_states` — état N2 canonique dérivé d'une observation de compte ; +3. `k_sol_core_return_data` — conservation N2 de la `returnData` transactionnelle déjà présente dans le document canonique N1. + +Le baseline candidat comporte donc : + +```text +N1 + k_sol_raw_transactions + k_sol_obs_transaction_observations + k_sol_obs_account_observations + +N2 + k_sol_core_transactions + k_sol_core_account_keys + k_sol_core_instructions + k_sol_core_inner_instructions + k_sol_core_logs + k_sol_core_balance_changes + k_sol_core_return_data + k_sol_core_account_states + +N3 / transverse + k_sol_ops_processing_ledger + k_sol_decode_events + k_sol_decode_coverage_declarations + k_sol_decode_coverage_observations + k_sol_mat_outputs +``` + +Aucune structure n'est nommée d'après Anchor, une IDL, un DEX ou un protocole. + +### 33.2 Temporalité et Core instructionnel + +`block_time` devient une colonne nullable de premier rang sur `k_sol_raw_transactions` et `k_sol_core_transactions`. La Core extraction vérifie la cohérence entre le document canonique N1 et l'ancre Core lorsqu'ils sont reliés. + +Les instructions top-level et CPI restent physiquement séparées. Les deux contrats portent désormais `stack_height` lorsque Solana la fournit. La Core extraction reconstruit les chemins CPI imbriqués à partir de la pile ; `parent_instruction_path` pointe alors vers le parent immédiat. Lorsque `stackHeight` est absent, la CPI reste rattachée de manière déterministe à la top-level racine sans inventer un niveau de pile. + +Les CPI reçoivent dans le schéma les mêmes champs de lifecycle/processor que les top-level. La sélection autonome et le replay opérationnel CPI restent à finaliser dans `pre.004`. + +### 33.3 Replay Core v3 + +`MdCoreInstructionReplayInput` passe à la version 3 et peut transporter : + +- `block_time` ; +- `stack_height` ; +- `parent_instruction_path` ; +- le contexte ordonné des instructions top-level ; +- la `returnData` transactionnelle. + +Le champ de contexte nouveau est nommé `top_level_instructions_json`. Les usages historiques du terme `outer` dans des décodeurs existants ne sont pas renommés mécaniquement lorsqu'ils décrivent encore une sémantique propre à ces contrats. + +Le constructeur historique reste disponible pour les tests et sources synthétiques ; les inputs provenant du Core persistant sont enrichis par le store. + +### 33.4 Provenance de schéma N3 + +`k_sol_decode_events` réserve des champs nullable de provenance de schéma : + +```text +schema_kind +schema_id +schema_version +schema_hash +``` + +Ils restent vides pour les décodeurs compilés actuels. Un futur mécanisme basé sur un schéma externe/IDL pourra les renseigner sans introduire de colonne Anchor ou modifier N1/N2. + +### 33.5 `k_sol_mat_outputs` + +Le renommage conceptuel est réalisé jusque dans l'API : + +```text +MaterializedOutputFilter +MaterializedOutputQueryRow +DecodePipelineStore::list_materialized_outputs +``` + +`k_sol_mat_outputs` reste le journal N3 générique versionné et la source de replay vers les futures projections N4. + +### 33.6 Ressources PostgreSQL atomiques + +La chaîne active historique `0001` à `0004` est retirée. Les scripts de maintenance monolithiques sont également retirés. + +La nouvelle arborescence contient : + +```text +migrations/postgres/ + schema/tables/ 16 + schema/constraints/ 129 + schema/indexes/ 59 + maintenance/truncate/16 + maintenance/drop/ 16 +``` + +Soit **236 ressources SQL**. Chaque ressource contient une seule instruction top-level et est embarquée une seule fois par `include_str!`. + +L'orchestrateur PostgreSQL global : + +- valide les ressources ; +- refuse les objets historiques `kb_sol_*` ; +- ouvre une transaction ; +- prend un advisory lock ; +- applique tables, contraintes et index dans l'ordre ; +- commit uniquement lorsque le baseline complet est cohérent. + +Les initialiseurs partiels raw/Core/decode sont supprimés, y compris des tests. Une initialisation ne peut donc plus valider accidentellement un sous-schéma comme si le baseline complet était prêt. + +### 33.7 Diagnostics du schéma + +Le diagnostic ne dépend plus de `_sqlx_migrations` pour déterminer si le baseline courant est disponible. Il compare les ressources connues réellement présentes. + +Le résumé backend-agnostique expose : + +- modèles logiques ; +- compte de tables ; +- compte d'index ; +- état ready/partial/not-initialized ; + +sans publier les noms physiques. + +Le backend PostgreSQL attend actuellement **75 index** : 59 index explicites et les 16 index de clés primaires produits par PostgreSQL. + +### 33.8 Conclusion du canari future-decoder + +Le canari a effectivement révélé deux pertes/absences structurelles de la baseline `0.5.2` : + +- `returnData` était conservée en N1 mais perdue en N2 ; +- aucun chemin persistant générique d'observation/état de compte n'était réservé. + +Ces deux lacunes sont corrigées avant gel. + +Les autres metadata transactionnelles auditées (`fee`, rewards, compute units, cost units) restent conservées dans le document canonique N1. Elles ne sont pas nécessaires au décodage futur et ne justifient pas de gonfler l'ancre N2 avant gel ; une projection additive reste possible si un besoin queryable stable apparaît. + +### 33.9 Travaux volontairement laissés à `pre.004+` + +`pre.003` ne clôt pas encore : + +- la lecture logique commune top-level+CPI et le replay CPI autonome ; +- l'ingestion/replay opérationnel du nouveau flux account observation -> account state ; +- l'invalidation descendante N1 -> N2 -> N3 -> N4 ; +- la pagination cursorisée ; +- la validation des index par plans réels sur dataset représentatif ; +- le snapshot machine-readable frozen ; +- la refonte fonctionnelle desktop finale ; +- les projections N4 metadata/trading. + +Ces points restent dans les tranches suivantes et peuvent décaler le premier candidat de clôture au-delà de `pre.007`. diff --git a/kb-app-demo-desktop/CHANGELOG.md b/kb-app-demo-desktop/CHANGELOG.md index 95da62a..2c901d3 100644 --- a/kb-app-demo-desktop/CHANGELOG.md +++ b/kb-app-demo-desktop/CHANGELOG.md @@ -1,8 +1,14 @@ - + # CHANGELOG — kb-app-demo-desktop +## `0.5.3-pre.003` + +- adapte les vues de replay/journaux aux contrats N3 `MaterializedOutput*` et au journal `k_sol_mat_outputs` sans réintroduire de dépendance PostgreSQL dans les DTO desktop ; +- neutralise les derniers libellés visibles de sources physiques dans la fenêtre de candidats replay : l'UI décrit désormais les faits Core/logiques plutôt que les noms de tables PostgreSQL ; +- conserve la refonte fonctionnelle complète des compteurs/modèles du nouveau baseline pour la tranche consommateurs prévue par le plan. + ## `0.5.3-pre.002-delta-fix-004` - aligne les versions Tauri/Vite de l'application sur `0.5.3` dans `package.json` et `tauri.conf.json` ; diff --git a/kb-app-demo-desktop/frontend/demo_store_replay_candidates.html b/kb-app-demo-desktop/frontend/demo_store_replay_candidates.html index f1010ed..c31a28d 100644 --- a/kb-app-demo-desktop/frontend/demo_store_replay_candidates.html +++ b/kb-app-demo-desktop/frontend/demo_store_replay_candidates.html @@ -1,5 +1,5 @@ - + @@ -281,7 +281,7 @@

Mints

Aucune requête exécutée.
-
Source : kb_sol_core_balance_changes.mint.
+
Source : faits Core de variation de balance (mint).
@@ -339,7 +339,7 @@

Owners

Aucune requête exécutée.
-
Source : kb_sol_core_balance_changes.owner.
+
Source : faits Core de variation de balance (owner).
@@ -397,7 +397,7 @@

Comptes

Aucune requête exécutée.
-
Source : kb_sol_core_account_keys.account_key.
+
Source : clés de comptes Core résolues.
diff --git a/kb-app-demo-desktop/src/demo_decode_replay.rs b/kb-app-demo-desktop/src/demo_decode_replay.rs index fd1dd8f..abd4696 100644 --- a/kb-app-demo-desktop/src/demo_decode_replay.rs +++ b/kb-app-demo-desktop/src/demo_decode_replay.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/demo_decode_replay.rs -// version: 44 +// version: 45 //! Tauri commands and UI payloads for contextual instruction decode replay. @@ -605,7 +605,7 @@ pub(crate) async fn demo_decode_replay_annotations( state: tauri::State<'_, crate::AppState>, request: crate::DemoTransactionAnnotationRequest, ) -> std::result::Result, std::string::String> { - let filter_result = ks_store::MaterializedEventFilter::new( + let filter_result = ks_store::MaterializedOutputFilter::new( std::option::Option::Some("materializer.transaction.annotations".to_string()), std::option::Option::Some("transaction_annotation".to_string()), request.signature_contains, @@ -622,7 +622,7 @@ pub(crate) async fn demo_decode_replay_annotations( std::result::Result::Err(error) => return std::result::Result::Err(error), }; let rows_result = - ks_store::DecodePipelineStore::list_materialized_events(&store, &filter).await; + ks_store::DecodePipelineStore::list_materialized_outputs(&store, &filter).await; let rows = match rows_result { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), @@ -882,7 +882,7 @@ fn coverage_payload( } fn annotation_payload( - row: ks_store::MaterializedEventQueryRow, + row: ks_store::MaterializedOutputQueryRow, ) -> std::result::Result { if row.processor_name != "materializer.transaction.annotations" || row.materialized_family != "transaction_annotation" @@ -1274,7 +1274,7 @@ mod tests { #[test] fn committed_annotation_row_is_mapped_to_ui_safe_contract() { - let row = ks_store::MaterializedEventQueryRow { + let row = ks_store::MaterializedOutputQueryRow { processor_name: "materializer.transaction.annotations".to_string(), processor_version: "0.4.3".to_string(), input_key: "input".to_string(), @@ -1310,7 +1310,7 @@ mod tests { #[test] fn malformed_annotation_payload_fails_closed() { - let row = ks_store::MaterializedEventQueryRow { + let row = ks_store::MaterializedOutputQueryRow { processor_name: "materializer.transaction.annotations".to_string(), processor_version: "0.4.3".to_string(), input_key: "input".to_string(), diff --git a/kb-app-demo-desktop/src/demo_spl_ata.rs b/kb-app-demo-desktop/src/demo_spl_ata.rs index 412685a..0634a89 100644 --- a/kb-app-demo-desktop/src/demo_spl_ata.rs +++ b/kb-app-demo-desktop/src/demo_spl_ata.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/demo_spl_ata.rs -// version: 18 +// version: 19 //! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads. @@ -316,7 +316,7 @@ pub(crate) async fn demo_spl_ata_journal( std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::Some("materializer.token.accounts".to_string()), std::option::Option::None, request.signature_contains.clone(), @@ -325,7 +325,7 @@ pub(crate) async fn demo_spl_ata_journal( std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; - let rows = match ks_store::DecodePipelineStore::list_materialized_events(&store, &filter).await + let rows = match ks_store::DecodePipelineStore::list_materialized_outputs(&store, &filter).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), @@ -560,7 +560,7 @@ fn journal_matches(payload: &serde_json::Value, request: &crate::DemoSplAtaJourn return true; } -fn journal_row(row: ks_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow { +fn journal_row(row: ks_store::MaterializedOutputQueryRow) -> crate::DemoSplAtaJournalRow { let text = |key: &str| { return row .payload_json diff --git a/kb-app-demo-desktop/src/demo_spl_token.rs b/kb-app-demo-desktop/src/demo_spl_token.rs index 0a0eaa2..578ecc9 100644 --- a/kb-app-demo-desktop/src/demo_spl_token.rs +++ b/kb-app-demo-desktop/src/demo_spl_token.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/demo_spl_token.rs -// version: 16 +// version: 17 //! Thin Tauri adapter for classic SPL Token execution and materialized journals. @@ -263,11 +263,11 @@ pub(crate) async fn demo_spl_token_journal( let needs_payload_filter = validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some(); let query_limit = if needs_payload_filter { - ks_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS + ks_store::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS } else { validated.limit }; - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::None, validated.family.clone(), validated.signature_contains.clone(), @@ -276,7 +276,7 @@ pub(crate) async fn demo_spl_token_journal( std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; - let rows = match ks_store::DecodePipelineStore::list_materialized_events(&store, &filter).await + let rows = match ks_store::DecodePipelineStore::list_materialized_outputs(&store, &filter).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), @@ -412,10 +412,10 @@ fn replay_diagnostics(summary: &ks_pipeline::DecodeReplaySummary) -> serde_json: fn validate_journal_request( request: crate::DemoSplTokenJournalRequest, ) -> std::result::Result { - if request.limit == 0 || request.limit > ks_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS { + if request.limit == 0 || request.limit > ks_store::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS { return std::result::Result::Err(format!( "journal limit must be between 1 and {}", - ks_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS + ks_store::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS )); } if request.profile_name.trim().is_empty() { @@ -496,7 +496,7 @@ fn payload_matches( return true; } -fn journal_row(row: ks_store::MaterializedEventQueryRow) -> DemoSplTokenJournalRow { +fn journal_row(row: ks_store::MaterializedOutputQueryRow) -> DemoSplTokenJournalRow { let operation = row .payload_json .get("operation") @@ -602,7 +602,7 @@ mod tests { payload.get("amountRaw").and_then(serde_json::Value::as_str), std::option::Option::Some("18446744073709551615") ); - let row = super::journal_row(ks_store::MaterializedEventQueryRow { + let row = super::journal_row(ks_store::MaterializedOutputQueryRow { processor_name: "materializer.token.accounts".to_string(), processor_version: "0.4.4".to_string(), input_key: "input".to_string(), diff --git a/ks-lib/CHANGELOG.md b/ks-lib/CHANGELOG.md index acdf076..92995d1 100644 --- a/ks-lib/CHANGELOG.md +++ b/ks-lib/CHANGELOG.md @@ -1,8 +1,14 @@ - + # CHANGELOG — ks-lib +## `0.5.3-pre.003` + +- fait évoluer `MdCoreInstructionReplayInput` vers le contrat version 3 afin de transporter le contexte Core nécessaire aux futurs décodeurs : `block_time`, `stack_height`, parent CPI et `returnData` transactionnelle ; +- renomme le contexte de transaction `outer_instructions_json` en `top_level_instructions_json` dans ce nouveau contrat store/replay, sans supprimer les usages historiques du terme `outer` lorsqu'ils décrivent encore explicitement une sémantique de décodeur existante ; +- conserve le constructeur historique de replay pour les tests/sources synthétiques et ajoute l'enrichissement `with_core_context` utilisé par `ks-store`. + ## `0.5.1-pre.008` - retire la dépendance `ts-rs`, les dérivations/attributs TS-RS et les bindings TypeScript générés historiques de `ks-lib` ; diff --git a/ks-lib/src/decoder/api/contracts.rs b/ks-lib/src/decoder/api/contracts.rs index aef0076..e6e280b 100644 --- a/ks-lib/src/decoder/api/contracts.rs +++ b/ks-lib/src/decoder/api/contracts.rs @@ -1,5 +1,5 @@ // file: ks-lib/src/decoder/api/contracts.rs -// version: 9 +// version: 10 //! Backend-neutral contextual instruction decoder contracts. @@ -511,7 +511,7 @@ mod tests { fn contextual_input_hash_changes_when_outer_context_changes() { let first = replay_input(); let mut second = replay_input(); - second.outer_instructions_json = serde_json::json!([{ + second.top_level_instructions_json = serde_json::json!([{ "instructionIndex": 0, "instructionPath": "0", "programId": "program", diff --git a/ks-lib/src/decoder/solana/core/compute_budget.rs b/ks-lib/src/decoder/solana/core/compute_budget.rs index e406cb2..afddf58 100644 --- a/ks-lib/src/decoder/solana/core/compute_budget.rs +++ b/ks-lib/src/decoder/solana/core/compute_budget.rs @@ -1,5 +1,5 @@ // file: ks-lib/src/decoder/solana/core/compute_budget.rs -// version: 7 +// version: 8 //! Maximal current and historical Compute Budget instruction decoding. @@ -341,7 +341,7 @@ fn compute_budget_outer_rows( input: &crate::MdCoreInstructionReplayInput, ) -> std::vec::Vec { let mut rows = std::vec::Vec::new(); - let outer_values = match input.outer_instructions_json.as_array() { + let outer_values = match input.top_level_instructions_json.as_array() { std::option::Option::Some(value) => value, std::option::Option::None => return rows, }; @@ -705,7 +705,7 @@ mod tests { fn replay_input_with_outer( bytes: &[u8], instruction_path: &str, - outer_instructions_json: serde_json::Value, + top_level_instructions_json: serde_json::Value, ) -> crate::MdCoreInstructionReplayInput { let encoded = base64::engine::general_purpose::STANDARD.encode(bytes); let result = crate::MdCoreInstructionReplayInput::new( @@ -720,7 +720,7 @@ mod tests { serde_json::json!([]), std::option::Option::Some(serde_json::json!({"dataBase64": encoded})), std::option::Option::Some("payload-hash".to_string()), - outer_instructions_json, + top_level_instructions_json, serde_json::json!([]), serde_json::json!([]), serde_json::json!([]), diff --git a/ks-lib/src/decoder/solana/core/payload.rs b/ks-lib/src/decoder/solana/core/payload.rs index 21a0433..88dc8b8 100644 --- a/ks-lib/src/decoder/solana/core/payload.rs +++ b/ks-lib/src/decoder/solana/core/payload.rs @@ -1,5 +1,5 @@ // file: ks-lib/src/decoder/solana/core/payload.rs -// version: 7 +// version: 8 //! Bounded native instruction payload helpers. @@ -228,7 +228,7 @@ fn resolve_explicit_outer_instruction( input: &crate::MdCoreInstructionReplayInput, instruction_index: usize, ) -> ks_core::Result { - let entries = match input.outer_instructions_json.as_array() { + let entries = match input.top_level_instructions_json.as_array() { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(ks_core::Error::invalid_state( diff --git a/ks-lib/src/decoder/solana/core/precompiles.rs b/ks-lib/src/decoder/solana/core/precompiles.rs index 1dfb521..3f91f6b 100644 --- a/ks-lib/src/decoder/solana/core/precompiles.rs +++ b/ks-lib/src/decoder/solana/core/precompiles.rs @@ -1,5 +1,5 @@ // file: ks-lib/src/decoder/solana/core/precompiles.rs -// version: 6 +// version: 7 //! Bounded structural decoding for Solana signature verification precompiles. @@ -1599,11 +1599,11 @@ mod tests { for (program_id, target, external) in cases { let mut missing = replay_input(program_id, target.clone(), vec![(0, external.clone())], false); - missing.outer_instructions_json[0]["payloadJson"] = serde_json::Value::Null; + missing.top_level_instructions_json[0]["payloadJson"] = serde_json::Value::Null; let missing_result = crate::decoder_solana_core_precompiles_decode(&missing); assert_eq!(missing_result.status, crate::DcApiDecoderOutcomeStatus::Failed); let mut invalid = replay_input(program_id, target, vec![(0, external)], false); - invalid.outer_instructions_json[0]["payloadJson"] = + invalid.top_level_instructions_json[0]["payloadJson"] = serde_json::json!({"dataBase64": "%%%"}); let invalid_result = crate::decoder_solana_core_precompiles_decode(&invalid); assert_eq!(invalid_result.status, crate::DcApiDecoderOutcomeStatus::Failed); diff --git a/ks-lib/src/decoder/solana/core/slashing.rs b/ks-lib/src/decoder/solana/core/slashing.rs index 1c3b98b..c1f3773 100644 --- a/ks-lib/src/decoder/solana/core/slashing.rs +++ b/ks-lib/src/decoder/solana/core/slashing.rs @@ -1,5 +1,5 @@ // file: ks-lib/src/decoder/solana/core/slashing.rs -// version: 4 +// version: 5 //! Exact structural decoding for the enshrined stateless Slashing Program. @@ -413,7 +413,7 @@ fn preceding_ed25519_instruction(input: &crate::MdCoreInstructionReplayInput) -> }); }, }; - let previous = input.outer_instructions_json.as_array().and_then(|instructions| { + let previous = input.top_level_instructions_json.as_array().and_then(|instructions| { return instructions.iter().find(|instruction| { return instruction .get("instructionIndex") diff --git a/ks-lib/src/model/replay.rs b/ks-lib/src/model/replay.rs index 68c3679..28a1bcc 100644 --- a/ks-lib/src/model/replay.rs +++ b/ks-lib/src/model/replay.rs @@ -1,10 +1,10 @@ // file: ks-lib/src/model/replay.rs -// version: 4 +// version: 5 //! Source-neutral instruction replay input shared by decoders and stores. /// Version of the normalized core replay contract. -pub const MD_CORE_REPLAY_INPUT_CONTRACT_VERSION: u32 = 2; +pub const MD_CORE_REPLAY_INPUT_CONTRACT_VERSION: u32 = 3; /// Decoder replay input containing one instruction plus extracted transaction context. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] @@ -17,8 +17,14 @@ pub struct MdCoreInstructionReplayInput { pub signature: std::string::String, /// Transaction slot in Solana unsigned representation. pub slot: u64, + /// Optional block timestamp as Unix seconds supplied by Solana. + pub block_time: std::option::Option, /// Stable instruction path, for example `0` or `2/1`. pub instruction_path: std::string::String, + /// Optional immediate or best-known parent instruction path for CPI inputs. + pub parent_instruction_path: std::option::Option, + /// Optional runtime invocation stack height. + pub stack_height: std::option::Option, /// Program id as non-empty base58 text. pub program_id: std::string::String, /// Optional surface hint supplied by an operator or upstream classifier. @@ -35,8 +41,10 @@ pub struct MdCoreInstructionReplayInput { pub instruction_payload_json: std::option::Option, /// Optional deterministic hash of the retained instruction payload. pub instruction_payload_hash: std::option::Option, - /// Ordered outer instructions for the transaction. - pub outer_instructions_json: serde_json::Value, + /// Optional transaction return-data fact retained by Core. + pub return_data: std::option::Option, + /// Ordered top-level instructions for the transaction. + pub top_level_instructions_json: serde_json::Value, /// Inner instruction tree or subtree relevant to the target instruction. pub inner_instructions_json: serde_json::Value, /// Ordered logs relevant to the transaction or target instruction. @@ -45,7 +53,7 @@ pub struct MdCoreInstructionReplayInput { pub balance_changes_json: serde_json::Value, } -impl MdCoreInstructionReplayInput { +impl crate::MdCoreInstructionReplayInput { /// Builds a decoder replay input after validating its stable identity and array context. #[allow(clippy::too_many_arguments)] pub fn new( @@ -60,14 +68,14 @@ impl MdCoreInstructionReplayInput { instruction_accounts_json: serde_json::Value, instruction_payload_json: std::option::Option, instruction_payload_hash: std::option::Option, - outer_instructions_json: serde_json::Value, + top_level_instructions_json: serde_json::Value, inner_instructions_json: serde_json::Value, logs_json: serde_json::Value, balance_changes_json: serde_json::Value, ) -> ks_core::Result { - if !outer_instructions_json.is_array() { + if !top_level_instructions_json.is_array() { return std::result::Result::Err(ks_core::Error::invalid_state( - "core replay input outer instructions must be a JSON array", + "core replay input top-level instructions must be a JSON array", )); } let value = Self { @@ -75,7 +83,10 @@ impl MdCoreInstructionReplayInput { replay_input_key: replay_input_key.into(), signature: signature.into(), slot, + block_time: std::option::Option::None, instruction_path: instruction_path.into(), + parent_instruction_path: std::option::Option::None, + stack_height: std::option::Option::None, program_id: program_id.into(), surface_code_hint: std::option::Option::None, transaction_failed, @@ -84,7 +95,8 @@ impl MdCoreInstructionReplayInput { instruction_accounts_json, instruction_payload_json, instruction_payload_hash, - outer_instructions_json, + return_data: std::option::Option::None, + top_level_instructions_json, inner_instructions_json, logs_json, balance_changes_json, @@ -124,6 +136,47 @@ impl MdCoreInstructionReplayInput { return std::result::Result::Ok(()); } + /// Adds transaction time, CPI ancestry and return-data context retained by Core. + pub fn with_core_context( + mut self, + block_time: std::option::Option, + parent_instruction_path: std::option::Option, + stack_height: std::option::Option, + return_data: std::option::Option, + ) -> ks_core::Result { + if parent_instruction_path + .as_deref() + .is_some_and(|value| return value.trim().is_empty()) + { + return std::result::Result::Err(ks_core::Error::invalid_state( + "core replay parent instruction path must not be empty when present", + )); + } + if stack_height.is_some_and(|value| return value == 0) { + return std::result::Result::Err(ks_core::Error::invalid_state( + "core replay stack height must be greater than zero when present", + )); + } + if parent_instruction_path.is_some() && stack_height.is_some_and(|value| return value <= 1) + { + return std::result::Result::Err(ks_core::Error::invalid_state( + "core replay CPI stack height must be greater than one when present", + )); + } + if let std::option::Option::Some(value) = &return_data { + if value.program_id.trim().is_empty() { + return std::result::Result::Err(ks_core::Error::invalid_state( + "core replay return-data program id must not be empty", + )); + } + } + self.block_time = block_time; + self.parent_instruction_path = parent_instruction_path; + self.stack_height = stack_height; + self.return_data = return_data; + return std::result::Result::Ok(self); + } + /// Adds an optional stable surface hint after validation. pub fn with_surface_code_hint( mut self, diff --git a/ks-logging/CHANGELOG.md b/ks-logging/CHANGELOG.md index bd35128..f404c07 100644 --- a/ks-logging/CHANGELOG.md +++ b/ks-logging/CHANGELOG.md @@ -1,8 +1,21 @@ - + # CHANGELOG — ks-logging +## `0.5.3-pre.003` + +### Fix `pre.003-delta-fix-005` + +- ajoute `h2` et `hyper_util` à `WARN` dans les profils standards ; `hyper` reste explicitement filtré à `WARN` ; +- étend le canari de configuration aux six targets tierces bruyantes : `h2`, `hyper`, `hyper_util`, `rustls`, `rustls_platform_verifier` et `sqlx` ; +- documente que le statement logging automatique SQLx est désormais désactivé dans `ks-store`, les traces fonctionnelles de requêtes devant être produites par `ks-store` lui-même. + + +- ajoute `rustls`, `rustls_platform_verifier` et `sqlx` à `WARN` dans les `target_filters` des profils standards `local_devnet`, `mainnet_research` et `mainnet` ; +- aligne l'exemple de configuration et ajoute un test vérifiant cette politique pour chaque profil ; +- documente que les logs détaillés des dépendances doivent être réactivés par une route explicite, sans changer leurs targets propriétaires. + ## `0.5.1-pre.008` - Fix: remplace un ancien fragment de credential Helius utilisé comme canari négatif par le nom synthétique `KS_SECRET_HELIUS_API_KEY`; aucune donnée réelle ou partielle de credential ne doit être conservée dans les tests. diff --git a/ks-logging/USAGE.md b/ks-logging/USAGE.md index 4694a2b..a28d78a 100644 --- a/ks-logging/USAGE.md +++ b/ks-logging/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de ks-logging @@ -58,3 +58,12 @@ match ks_logging::init_logging(&config) { ``` L'initialisation reste globale au processus. Les binaires doivent donc résoudre leur configuration avant le premier appel à `init_logging`. + +## Filtres des dépendances tierces + +Les routes wildcard `debug` restent destinées aux composants Khadhroony, mais elles ne doivent pas transformer la console et `debug.log` en trace brute des bibliothèques réseau ou SQL. + +Les profils standards imposent `WARN` aux targets `h2`, `hyper`, `hyper_util`, `rustls`, `rustls_platform_verifier` et `sqlx`. Les targets tiers restent ceux de leurs crates propriétaires : un événement HTTP/2 reste `h2::*`, un événement Hyper reste `hyper*`, un événement SQLx reste `sqlx::query` et un événement TLS reste `rustls::*`. + +`ks-store` désactive en complément le statement logging automatique de SQLx. Les requêtes fonctionnellement importantes doivent être instrumentées par `ks-store` avec son target canonique et des champs structurés, sans dépendre du format de logging d'une bibliothèque tierce. Les warnings/errors des dépendances restent observables dans les profils standards. + diff --git a/ks-logging/src/document.rs b/ks-logging/src/document.rs index ecd8335..a159806 100644 --- a/ks-logging/src/document.rs +++ b/ks-logging/src/document.rs @@ -1,5 +1,5 @@ // file: ks-logging/src/document.rs -// version: 6 +// version: 8 //! Independent logging configuration document loading and validation. @@ -521,6 +521,32 @@ mod tests { } } + #[test] + fn default_logging_config_suppresses_verbose_dependency_targets() { + let mut value = parse_default_value(); + value["logs_directory"] = serde_json::Value::String("logs".to_string()); + let raw_json = value_to_json(&value); + let document_result = super::parse_logging_json(&raw_json); + let document = match document_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("default logging config must parse: {error}"), + }; + for profile in &document.profiles { + for dependency_target in + ["h2", "hyper", "hyper_util", "rustls", "rustls_platform_verifier", "sqlx"] + { + assert!( + profile.config.target_filters.iter().any(|filter| { + return filter.target == dependency_target && filter.level == "warn"; + }), + "logging profile {} must suppress verbose {} events", + profile.name, + dependency_target + ); + } + } + } + #[test] fn default_logging_config_uses_canonical_wallet_and_pipeline_routes() { assert!(!DEFAULT_LOGGING_CONFIG.contains("KS_SECRET_HELIUS_API_KEY")); diff --git a/ks-pipeline-demo-scenarios/CHANGELOG.md b/ks-pipeline-demo-scenarios/CHANGELOG.md index 4b0fa68..5642597 100644 --- a/ks-pipeline-demo-scenarios/CHANGELOG.md +++ b/ks-pipeline-demo-scenarios/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — ks-pipeline-demo-scenarios +## `0.5.3-pre.003` + +- adapte les scénarios réutilisables au renommage N3 `MaterializedOutput*` / `list_materialized_outputs` sans modifier les campagnes Devnet ni leurs critères de succès ; +- conserve la façade `Store` backend-agnostique introduite en `pre.002` pendant la reconstruction physique PostgreSQL de `pre.003`. + ## `0.5.3-pre.002` - remplace les constructions `PostgresStore`/`PostgresStoreOptions` des scénarios par `ks_store::Store` et `StoreOpenOptions` ; diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs index 03cc78d..e6de313 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs -// version: 26 +// version: 27 //! Real Devnet simulation and submission for current Metaplex Token Metadata operations. @@ -357,7 +357,7 @@ pub struct DevnetMetaplexTokenMetadataExecutionSummary { /// Second replay proving idempotence for the exact same input. pub idempotence_replay: std::option::Option, /// Exact materialized rows produced for the submitted Metaplex transaction. - pub materializations: std::vec::Vec, + pub materializations: std::vec::Vec, /// Aggregated canonical post-execution diagnostic. pub post_execution: std::option::Option, } @@ -758,7 +758,7 @@ where .push(replay_summary_diagnostic("first Metaplex replay", &first_replay)); } summary.decode_replay = std::option::Option::Some(first_replay); - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::None, std::option::Option::None, std::option::Option::Some(signature.0.clone()), @@ -767,7 +767,8 @@ where std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let rows = match ks_store::DecodePipelineStore::list_materialized_events(store, &filter).await { + let rows = match ks_store::DecodePipelineStore::list_materialized_outputs(store, &filter).await + { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic @@ -1119,8 +1120,8 @@ where } fn metaplex_instruction_materializations( - rows: std::vec::Vec, -) -> std::vec::Vec { + rows: std::vec::Vec, +) -> std::vec::Vec { let materializer_name = ks_lib::MtApiEventMaterializer::identity( &ks_lib::MtMetadataMetaplexTokenMetadataMaterializer, ) @@ -1613,8 +1614,8 @@ mod tests { #[test] fn materialization_evidence_is_owned_by_the_metaplex_materializer() { - fn row(processor_name: &str) -> ks_store::MaterializedEventQueryRow { - return ks_store::MaterializedEventQueryRow { + fn row(processor_name: &str) -> ks_store::MaterializedOutputQueryRow { + return ks_store::MaterializedOutputQueryRow { processor_name: processor_name.to_string(), processor_version: "0.4.8-pre.13".to_string(), input_key: "input".to_string(), diff --git a/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs b/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs index 9a172cd..0ee25de 100644 --- a/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs +++ b/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs -// version: 19 +// version: 20 //! Devnet ATA execution with stateful and canonical post-validation. @@ -102,7 +102,7 @@ pub struct DevnetSplAssociatedTokenAccountExecutionSummary { /// Second replay proving idempotence for the same decoder version and input. pub idempotence_replay: std::option::Option, /// Exact materialized rows produced for the submitted Token transaction. - pub materializations: std::vec::Vec, + pub materializations: std::vec::Vec, /// Aggregated post-execution validation diagnostic. pub post_execution: std::option::Option, } @@ -424,7 +424,7 @@ where }; diagnostic.decode_replayed = decode_completed(&first_replay); summary.decode_replay = std::option::Option::Some(first_replay); - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::None, std::option::Option::None, std::option::Option::Some(signature.0.clone()), @@ -433,7 +433,8 @@ where std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let rows = match ks_store::DecodePipelineStore::list_materialized_events(store, &filter).await { + let rows = match ks_store::DecodePipelineStore::list_materialized_outputs(store, &filter).await + { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic.diagnostics.push(format!( diff --git a/ks-pipeline-demo-scenarios/src/spl/memo.rs b/ks-pipeline-demo-scenarios/src/spl/memo.rs index e93c06c..900c5d9 100644 --- a/ks-pipeline-demo-scenarios/src/spl/memo.rs +++ b/ks-pipeline-demo-scenarios/src/spl/memo.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/memo.rs -// version: 15 +// version: 16 //! Devnet SPL Memo v4 execution with canonical post-validation. @@ -107,7 +107,7 @@ pub struct DevnetMemoExecutionSummary { /// Second replay proving that the same decoder version and input are idempotent. pub idempotence_replay: std::option::Option, /// Exact persisted transaction annotation rows for the submitted signature. - pub annotations: std::vec::Vec, + pub annotations: std::vec::Vec, /// Aggregated post-execution validation diagnostic. pub post_execution: std::option::Option, } @@ -490,7 +490,7 @@ where }; diagnostic.decode_replayed = decode_completed(&first_replay); summary.decode_replay = std::option::Option::Some(first_replay); - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::Some("materializer.transaction.annotations".to_string()), std::option::Option::Some("transaction_annotation".to_string()), std::option::Option::Some(signature.0.clone()), @@ -500,7 +500,7 @@ where std::result::Result::Err(error) => return std::result::Result::Err(error), }; summary.annotations = - match ks_store::DecodePipelineStore::list_materialized_events(store, &filter).await { + match ks_store::DecodePipelineStore::list_materialized_outputs(store, &filter).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic.diagnostics.push(format!("Memo annotation query failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/spl/token/execution.rs b/ks-pipeline-demo-scenarios/src/spl/token/execution.rs index e1617f7..345c29b 100644 --- a/ks-pipeline-demo-scenarios/src/spl/token/execution.rs +++ b/ks-pipeline-demo-scenarios/src/spl/token/execution.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/token/execution.rs -// version: 19 +// version: 20 //! Devnet classic SPL Token execution with stateful and canonical post-validation. @@ -99,7 +99,7 @@ pub struct DevnetSplTokenExecutionSummary { /// Second replay proving idempotence for the same decoder version and input. pub idempotence_replay: std::option::Option, /// Exact materialized rows produced for the submitted Token transaction. - pub materializations: std::vec::Vec, + pub materializations: std::vec::Vec, /// Aggregated post-execution validation diagnostic. pub post_execution: std::option::Option, } @@ -390,7 +390,7 @@ where }; diagnostic.decode_replayed = crate::decode_completed(&first_replay); summary.decode_replay = std::option::Option::Some(first_replay); - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::None, std::option::Option::None, std::option::Option::Some(signature.0.clone()), @@ -399,7 +399,8 @@ where std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let rows = match ks_store::DecodePipelineStore::list_materialized_events(store, &filter).await { + let rows = match ks_store::DecodePipelineStore::list_materialized_outputs(store, &filter).await + { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic diff --git a/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs b/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs index 5cd16c5..2e5a4f3 100644 --- a/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs +++ b/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs -// version: 18 +// version: 19 //! Controlled Devnet lifecycle for freshly prepared classic SPL Token accounts. @@ -662,7 +662,7 @@ where "lifecycle predecessor decode and materialization did not complete", )); } - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::None, std::option::Option::None, std::option::Option::Some(signature.0.clone()), @@ -671,7 +671,8 @@ where std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let rows = match ks_store::DecodePipelineStore::list_materialized_events(store, &filter).await { + let rows = match ks_store::DecodePipelineStore::list_materialized_outputs(store, &filter).await + { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; diff --git a/ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs b/ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs index a81ed5a..0fbd42a 100644 --- a/ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs +++ b/ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs -// version: 12 +// version: 13 //! Devnet Token-2022 execution with stateful and canonical post-validation. @@ -99,7 +99,7 @@ pub struct DevnetSplToken2022ExecutionSummary { /// Second replay proving idempotence for the same decoder version and input. pub idempotence_replay: std::option::Option, /// Exact materialized rows produced for the submitted Token transaction. - pub materializations: std::vec::Vec, + pub materializations: std::vec::Vec, /// Aggregated post-execution validation diagnostic. pub post_execution: std::option::Option, } @@ -390,7 +390,7 @@ where }; diagnostic.decode_replayed = crate::decode_completed(&first_replay); summary.decode_replay = std::option::Option::Some(first_replay); - let filter = match ks_store::MaterializedEventFilter::new( + let filter = match ks_store::MaterializedOutputFilter::new( std::option::Option::None, std::option::Option::None, std::option::Option::Some(signature.0.clone()), @@ -399,7 +399,8 @@ where std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let rows = match ks_store::DecodePipelineStore::list_materialized_events(store, &filter).await { + let rows = match ks_store::DecodePipelineStore::list_materialized_outputs(store, &filter).await + { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic diff --git a/ks-pipeline/CHANGELOG.md b/ks-pipeline/CHANGELOG.md index 92f9acc..b06f5a8 100644 --- a/ks-pipeline/CHANGELOG.md +++ b/ks-pipeline/CHANGELOG.md @@ -1,8 +1,16 @@ - + # CHANGELOG — ks-pipeline +## `0.5.3-pre.003` + +- corrige le premier delta `pre.003` en utilisant le nom canonique exporté `MdCanonicalInnerInstructionGroup` pour la reconstruction des chemins CPI. +- aligne Core extraction sur le nouveau baseline `ks-store` : conservation de `block_time`, `stack_height`, `returnData` et persistance du contexte N2 nécessaire au replay ; +- reconstruit les chemins CPI imbriqués à partir de `stackHeight` lorsque l'information est disponible et conserve un fallback déterministe vers la top-level lorsque Solana ne fournit pas cette hauteur ; +- enrichit les inputs N2 -> N3 avec le contexte Core v3 sans modifier le dispatch fonctionnel des décodeurs actuels ; +- adapte les lectures de matérialisation au contrat `MaterializedOutput*` renommé avec `k_sol_mat_outputs`. + ## `0.5.1-pre.006` - déplace ses fixtures de configuration vers le contrat runtime résolu sous `test-fixtures/config/` ; diff --git a/ks-pipeline/src/backfill.rs b/ks-pipeline/src/backfill.rs index 5ed06ed..533d3aa 100644 --- a/ks-pipeline/src/backfill.rs +++ b/ks-pipeline/src/backfill.rs @@ -1,5 +1,5 @@ // file: ks-pipeline/src/backfill.rs -// version: 11 +// version: 12 //! Reusable HTTP transaction backfill orchestration. @@ -1409,7 +1409,7 @@ where observation_with_timings.with_payload_metadata(payload_size_bytes, source_payload_hash); let observation = match payload_result { std::result::Result::Ok(value) => { - value.with_status(ks_store::TransactionObservationStatus::Persisted) + value.with_status(ks_store::AcquisitionObservationStatus::Persisted) }, std::result::Result::Err(error) => return std::result::Result::Err(error), }; @@ -1456,7 +1456,7 @@ where observation_with_timings.with_payload_metadata(payload_size_bytes, source_payload_hash); let observation = match payload_result { std::result::Result::Ok(value) => { - value.with_status(ks_store::TransactionObservationStatus::Missing) + value.with_status(ks_store::AcquisitionObservationStatus::Missing) }, std::result::Result::Err(error) => return std::result::Result::Err(error), }; @@ -1538,7 +1538,7 @@ fn base_observation( client.provider().to_string(), "solana_http_json_rpc".to_string(), "getTransaction".to_string(), - ks_store::TransactionObservationOrigin::Backfill, + ks_store::AcquisitionObservationOrigin::Backfill, received_at, ); let observation = match observation_result { diff --git a/ks-pipeline/src/core_extraction.rs b/ks-pipeline/src/core_extraction.rs index c8260ad..acffad7 100644 --- a/ks-pipeline/src/core_extraction.rs +++ b/ks-pipeline/src/core_extraction.rs @@ -1,5 +1,5 @@ // file: ks-pipeline/src/core_extraction.rs -// version: 11 +// version: 13 //! Canonical Solana transaction to normalized core extraction pipeline. @@ -568,6 +568,11 @@ pub fn extract_raw_transaction_to_core( "canonical transaction slot does not match raw row slot", )); } + if transaction.block_time != row.block_time { + return std::result::Result::Err(ks_core::Error::invalid_state( + "canonical transaction block time does not match raw row block time", + )); + } let computed_hash_result = transaction.canonical_json_hash(); let computed_hash = match computed_hash_result { std::result::Result::Ok(value) => value, @@ -594,6 +599,7 @@ pub fn extract_raw_transaction_to_core( let core_transaction_result = ks_store::CoreTransactionInsert::new( transaction.primary_signature.clone(), transaction.slot, + transaction.block_time, false, std::option::Option::None, ); @@ -620,6 +626,7 @@ pub fn extract_raw_transaction_to_core( inner_instructions, logs: std::vec::Vec::new(), balance_changes: std::vec::Vec::new(), + return_data: std::option::Option::None, }); } @@ -633,6 +640,7 @@ fn build_bundle_with_metadata( let core_transaction_result = ks_store::CoreTransactionInsert::new( transaction.primary_signature.clone(), transaction.slot, + transaction.block_time, failed, metadata.error.clone(), ); @@ -660,6 +668,11 @@ fn build_bundle_with_metadata( std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; + let return_data_result = crate::core_extraction::extract_return_data(transaction, metadata); + let return_data = match return_data_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; return std::result::Result::Ok(ks_store::CoreExtractionBundle { raw_transaction_id: row.id, ledger_identity, @@ -669,9 +682,32 @@ fn build_bundle_with_metadata( inner_instructions, logs, balance_changes, + return_data, }); } +fn extract_return_data( + transaction: &ks_lib::MdCanonicalTransaction, + metadata: &ks_lib::MdCanonicalTransactionMetadata, +) -> ks_core::Result> { + let value = match &metadata.return_data { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::result::Result::Ok(std::option::Option::None), + }; + let insert_result = ks_store::CoreReturnDataInsert::new( + transaction.primary_signature.clone(), + transaction.slot, + value.program_id.clone(), + value.data_base64.clone(), + ); + return match insert_result { + std::result::Result::Ok(insert) => { + std::result::Result::Ok(std::option::Option::Some(insert)) + }, + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + fn ledger_identity_from_raw( row: &ks_store::RawTransactionRow, ) -> ks_core::Result { @@ -819,6 +855,7 @@ fn extract_instructions( transaction.slot, path, program_id, + instruction.stack_height, accounts_json, payload_json, ); @@ -836,9 +873,12 @@ fn extract_instructions( let mut inner_instructions = std::vec::Vec::new(); if let std::option::Option::Some(metadata) = &transaction.metadata { for group in &metadata.inner_instructions { - let parent_path = group.parent_instruction_index.to_string(); - for (inner_index, instruction) in group.instructions.iter().enumerate() { - let path = format!("{parent_path}/{inner_index}"); + let paths_result = crate::core_extraction::resolve_inner_instruction_paths(group); + let paths = match paths_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + for (instruction, path) in group.instructions.iter().zip(paths.iter()) { let resolved_result = crate::core_extraction::resolve_instruction(&keys, instruction); let (program_id, accounts_json, payload_json, payload_hash) = match resolved_result @@ -849,9 +889,10 @@ fn extract_instructions( let insert_result = ks_store::CoreInnerInstructionInsert::new( transaction.primary_signature.clone(), transaction.slot, - parent_path.clone(), - path, + path.0.clone(), + path.1.clone(), program_id, + instruction.stack_height, accounts_json, payload_json, ); @@ -871,6 +912,57 @@ fn extract_instructions( return std::result::Result::Ok((instructions, inner_instructions)); } +fn resolve_inner_instruction_paths( + group: &ks_lib::MdCanonicalInnerInstructionGroup, +) -> ks_core::Result> { + let root_path = group.parent_instruction_index.to_string(); + let mut stack = std::vec::Vec::<(u32, std::string::String)>::new(); + let mut child_counts = std::collections::BTreeMap::::new(); + let mut output = std::vec::Vec::with_capacity(group.instructions.len()); + for instruction in &group.instructions { + let parent_path = match instruction.stack_height { + std::option::Option::Some(height) if height <= 1 => { + return std::result::Result::Err(ks_core::Error::invalid_state( + "inner instruction stack height must be greater than one", + )); + }, + std::option::Option::Some(2) => { + stack.clear(); + root_path.clone() + }, + std::option::Option::Some(height) => { + while stack.last().is_some_and(|entry| return entry.0 >= height) { + stack.pop(); + } + match stack.last() { + std::option::Option::Some((parent_height, path)) + if parent_height.saturating_add(1) == height => + { + path.clone() + }, + _ => { + return std::result::Result::Err(ks_core::Error::invalid_state( + "inner instruction stack height is inconsistent with invocation order", + )); + }, + } + }, + std::option::Option::None => { + stack.clear(); + root_path.clone() + }, + }; + let child_index = child_counts.entry(parent_path.clone()).or_insert(0); + let instruction_path = format!("{parent_path}/{}", *child_index); + *child_index = child_index.saturating_add(1); + if let std::option::Option::Some(height) = instruction.stack_height { + stack.push((height, instruction_path.clone())); + } + output.push((parent_path, instruction_path)); + } + return std::result::Result::Ok(output); +} + fn resolve_instruction( keys: &[std::string::String], instruction: &ks_lib::MdCanonicalCompiledInstruction, @@ -918,7 +1010,11 @@ fn extract_logs( metadata: &ks_lib::MdCanonicalTransactionMetadata, ) -> ks_core::Result> { let mut output = std::vec::Vec::with_capacity(metadata.log_messages.len()); - let links = crate::core_extraction::resolve_log_links(transaction, metadata); + let links_result = crate::core_extraction::resolve_log_links(transaction, metadata); + let links = match links_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; for (index, text) in metadata.log_messages.iter().enumerate() { let log_index_result = u32::try_from(index); let log_index = match log_index_result { @@ -959,10 +1055,12 @@ fn extract_logs( fn resolve_log_links( transaction: &ks_lib::MdCanonicalTransaction, metadata: &ks_lib::MdCanonicalTransactionMetadata, -) -> std::vec::Vec<( - std::option::Option, - std::option::Option, -)> { +) -> ks_core::Result< + std::vec::Vec<( + std::option::Option, + std::option::Option, + )>, +> { let keys = crate::core_extraction::resolved_account_keys(transaction); let mut top_level = std::vec::Vec::<(std::string::String, std::string::String)>::new(); for (index, instruction) in transaction.message.instructions.iter().enumerate() { @@ -972,18 +1070,23 @@ fn resolve_log_links( top_level.push((program_id.clone(), index.to_string())); } } - let mut inner_by_parent = std::collections::BTreeMap::< + let mut inner_by_root = std::collections::BTreeMap::< std::string::String, - std::vec::Vec<(std::string::String, std::string::String)>, + std::vec::Vec<(std::string::String, std::string::String, std::option::Option)>, >::new(); for group in &metadata.inner_instructions { - let parent = group.parent_instruction_index.to_string(); - let entries = inner_by_parent.entry(parent.clone()).or_default(); - for (index, instruction) in group.instructions.iter().enumerate() { + let root = group.parent_instruction_index.to_string(); + let paths_result = crate::core_extraction::resolve_inner_instruction_paths(group); + let paths = match paths_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let entries = inner_by_root.entry(root).or_default(); + for (instruction, path) in group.instructions.iter().zip(paths.iter()) { if let std::option::Option::Some(program_id) = keys.get(usize::from(instruction.program_id_index)) { - entries.push((program_id.clone(), format!("{parent}/{index}"))); + entries.push((program_id.clone(), path.1.clone(), instruction.stack_height)); } } } @@ -1011,18 +1114,20 @@ fn resolve_log_links( } resolved } else { - let parent_path = stack.first().and_then(|entry| return entry.1.clone()); - match parent_path { - std::option::Option::Some(parent) => { - let cursor = inner_cursors.entry(parent.clone()).or_insert(0); + let root_path = stack.first().and_then(|entry| return entry.1.clone()); + match root_path { + std::option::Option::Some(root) => { + let cursor = inner_cursors.entry(root.clone()).or_insert(0); let mut resolved = std::option::Option::None; if let std::option::Option::Some(entries) = - inner_by_parent.get(parent.as_str()) + inner_by_root.get(root.as_str()) { while *cursor < entries.len() { let candidate = &entries[*cursor]; *cursor += 1; - if candidate.0 == program_id { + let depth_matches = + candidate.2.is_none_or(|height| return height == depth); + if candidate.0 == program_id && depth_matches { resolved = std::option::Option::Some(candidate.1.clone()); break; } @@ -1067,7 +1172,7 @@ fn resolve_log_links( }, } } - return output; + return std::result::Result::Ok(output); } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -1570,6 +1675,7 @@ mod tests { id: 1, signature: transaction.primary_signature.clone(), slot: 42, + block_time: transaction.block_time, canonical_json: std::option::Option::Some(json), canonical_json_hash: std::option::Option::Some(hash), canonical_format_version: 1, @@ -1598,6 +1704,105 @@ mod tests { assert_eq!(bundle.logs[1].instruction_path.as_deref(), std::option::Option::Some("0/0")); } + #[test] + fn nested_cpi_paths_preserve_immediate_parent_from_stack_height() { + let mut transaction = crate::core_extraction::tests::sample_transaction(); + let metadata = match transaction.metadata.as_mut() { + std::option::Option::Some(value) => value, + std::option::Option::None => panic!("sample metadata is required"), + }; + let template = metadata.inner_instructions[0].instructions[0].clone(); + let mut direct = template.clone(); + direct.stack_height = std::option::Option::Some(2); + let mut nested = template.clone(); + nested.stack_height = std::option::Option::Some(3); + let mut sibling = template; + sibling.stack_height = std::option::Option::Some(2); + metadata.inner_instructions[0].instructions = std::vec![direct, nested, sibling]; + let extraction_result = crate::core_extraction::extract_instructions(&transaction); + let (_top_level, inner) = match extraction_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("nested CPI extraction failed: {error}"), + }; + assert_eq!(inner[0].parent_instruction_path, "0"); + assert_eq!(inner[0].instruction_path, "0/0"); + assert_eq!(inner[1].parent_instruction_path, "0/0"); + assert_eq!(inner[1].instruction_path, "0/0/0"); + assert_eq!(inner[2].parent_instruction_path, "0"); + assert_eq!(inner[2].instruction_path, "0/1"); + assert_eq!(inner[1].stack_height, std::option::Option::Some(3)); + } + + #[test] + fn inconsistent_cpi_stack_height_is_rejected() { + let mut transaction = crate::core_extraction::tests::sample_transaction(); + let metadata = match transaction.metadata.as_mut() { + std::option::Option::Some(value) => value, + std::option::Option::None => panic!("sample metadata is required"), + }; + let template = metadata.inner_instructions[0].instructions[0].clone(); + let mut direct = template.clone(); + direct.stack_height = std::option::Option::Some(2); + let mut skipped_parent = template; + skipped_parent.stack_height = std::option::Option::Some(4); + metadata.inner_instructions[0].instructions = std::vec![direct, skipped_parent]; + let extraction_result = crate::core_extraction::extract_instructions(&transaction); + assert!(extraction_result.is_err()); + } + + #[test] + fn absent_cpi_stack_height_falls_back_to_top_level_parent() { + let mut transaction = crate::core_extraction::tests::sample_transaction(); + let metadata = match transaction.metadata.as_mut() { + std::option::Option::Some(value) => value, + std::option::Option::None => panic!("sample metadata is required"), + }; + metadata.inner_instructions[0].instructions[0].stack_height = std::option::Option::None; + let extraction_result = crate::core_extraction::extract_instructions(&transaction); + let (_top_level, inner) = match extraction_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("CPI extraction failed: {error}"), + }; + assert_eq!(inner[0].parent_instruction_path, "0"); + assert_eq!(inner[0].instruction_path, "0/0"); + assert_eq!(inner[0].stack_height, std::option::Option::None); + } + + #[test] + fn extraction_preserves_block_time_and_return_data() { + let mut transaction = crate::core_extraction::tests::sample_transaction(); + let metadata = match transaction.metadata.as_mut() { + std::option::Option::Some(value) => value, + std::option::Option::None => panic!("sample metadata is required"), + }; + metadata.return_data = std::option::Option::Some(ks_lib::MdCanonicalReturnData { + program_id: "11111111111111111111111111111111".to_string(), + data_base64: "AQID".to_string(), + }); + let row = crate::core_extraction::tests::raw_row(&transaction); + let extraction_result = crate::extract_raw_transaction_to_core(&row); + let bundle = match extraction_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("Core extraction failed: {error}"), + }; + assert_eq!(bundle.transaction.block_time, transaction.block_time); + let return_data = match bundle.return_data { + std::option::Option::Some(value) => value, + std::option::Option::None => panic!("return data must be preserved"), + }; + assert_eq!(return_data.program_id, "11111111111111111111111111111111"); + assert_eq!(return_data.data_base64, "AQID"); + } + + #[test] + fn extraction_rejects_block_time_mismatch() { + let transaction = crate::core_extraction::tests::sample_transaction(); + let mut row = crate::core_extraction::tests::raw_row(&transaction); + row.block_time = std::option::Option::Some(1_700_000_001); + let result = crate::extract_raw_transaction_to_core(&row); + assert!(result.is_err()); + } + #[test] fn extraction_rejects_hash_mismatch() { let transaction = crate::core_extraction::tests::sample_transaction(); diff --git a/ks-pipeline/src/decode_replay.rs b/ks-pipeline/src/decode_replay.rs index c6c00d5..a6b4474 100644 --- a/ks-pipeline/src/decode_replay.rs +++ b/ks-pipeline/src/decode_replay.rs @@ -1,5 +1,5 @@ // file: ks-pipeline/src/decode_replay.rs -// version: 3 +// version: 4 //! Common contextual instruction decode and optional materialization pipeline. @@ -1320,6 +1320,7 @@ fn observation_inserts( proof_kind: proof_kind_code(observation.proof.kind).to_string(), proof_json, payload_json: observation.payload_json.clone(), + schema_provenance: std::option::Option::None, transaction_failed: observation.transaction_failed, transaction_error: observation.transaction_error.clone(), observation_committed: observation.observation_committed, @@ -1821,10 +1822,10 @@ mod tests { 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: &ks_store::MaterializedEventFilter, - ) -> ks_core::Result> { + _filter: &ks_store::MaterializedOutputFilter, + ) -> ks_core::Result> { return std::result::Result::Ok(std::vec::Vec::new()); } } diff --git a/ks-pipeline/src/plan.rs b/ks-pipeline/src/plan.rs index ae7fb47..50df693 100644 --- a/ks-pipeline/src/plan.rs +++ b/ks-pipeline/src/plan.rs @@ -1,5 +1,5 @@ // file: ks-pipeline/src/plan.rs -// version: 3 +// version: 4 //! Pipeline planning primitives. @@ -16,7 +16,7 @@ pub enum PipelineStage { Decode, /// Materialize business events. Materialize, - /// Aggregate materialized events. + /// Aggregate materialized outputs. Aggregate, /// Validate outputs. Validate, diff --git a/ks-store/CHANGELOG.md b/ks-store/CHANGELOG.md index 80bcb2e..da9f34c 100644 --- a/ks-store/CHANGELOG.md +++ b/ks-store/CHANGELOG.md @@ -1,8 +1,53 @@ - + # CHANGELOG — ks-store +## `0.5.3-pre.003` + +### Fix `pre.003-delta-fix-005` + +- remplace le passage des statements SQLx en `TRACE` par `ConnectOptions::disable_statement_logging`, afin que `ks-store` ne dépende pas directement de la crate `log` uniquement pour `LevelFilter` ; +- conserve l'ouverture PostgreSQL via `PgConnectOptions` + `connect_with` et la journalisation fonctionnelle propre à `ks-store` sous `target: crate::TRACING_TARGET` ; +- adapte le canari de connexion pour vérifier explicitement la désactivation du statement logging SQLx. + + +### Fix `pre.003-delta-fix-004` + +- configure `PgConnectOptions` pour émettre les statements SQLx ordinaires au niveau `TRACE` avant l'ouverture du pool via `connect_with`, tout en conservant le target tiers `sqlx::query` ; +- préserve les événements fonctionnels PostgreSQL `ks-store` sous `target: crate::TRACING_TARGET` avec `backend`, `domain` et `action` ; +- ajoute un canari positif vérifiant le chemin `PgConnectOptions` + `TRACE` + `connect_with`. + +### Fix `pre.003-delta-fix-003` + +- corrige la branche `incomplete_signatures` de la sélection N2 -> N3 : le `SELECT` contextualisé projette désormais `stack_height` comme l'exige `CoreInstructionRow` ; +- extrait cette requête rare dans `INCOMPLETE_DECODE_INPUTS_SQL` et ajoute un test contractuel vérifiant toutes les colonnes attendues par le mapper Core, afin d'éviter une nouvelle dérive requête/DTO. + + +### Baseline N1-N3 PostgreSQL + +- corrige le premier delta `pre.003` : fermeture de l'`impl PostgresStore`, appels des helpers privés de tests via `super::` et retrait des validateurs de noms de tables du runtime/re-export crate-root ; ces validateurs restent des canaris privés `#[cfg(test)]`. +- reconstruit directement le schéma actif sous `k_sol_*` avec un baseline candidat au gel de **16 tables** : les 13 tables historiques conservées plus `k_sol_obs_account_observations`, `k_sol_core_account_states` et `k_sol_core_return_data` révélées par le canari future-decoder ; +- ajoute `block_time` aux ancres raw/Core transaction, `stack_height` aux contrats top-level/CPI et un lifecycle de processing symétrique pour les CPI ; la reconstruction de Core préserve le parent CPI immédiat lorsque la pile est disponible ; +- renomme le journal N3 en `k_sol_mat_outputs` et aligne l'API publique sur `MaterializedOutputFilter`, `MaterializedOutputQueryRow` et `list_materialized_outputs` ; +- réserve sur `k_sol_decode_events` une provenance nullable de schéma externe (`kind/id/version/hash`) afin qu'un futur décodeur IDL puisse tracer son schéma sans remodeler N1/N2/N3. + +### Ressources et initialisation + +- corrige `pre.003-delta-fix-002` l'ordre transactionnel des contraintes : les clés primaires référencées sont désormais appliquées avant les clés étrangères ; ajoute un canari couvrant les neuf dépendances FK du baseline afin d'empêcher un nouvel ordre invalide et le rollback complet observé sur base vide ; +- remplace les migrations historiques `0001` à `0004` et les deux scripts de maintenance monolithiques par **236 ressources PostgreSQL atomiques** sous `migrations/postgres/` : tables, contraintes, index, `TRUNCATE` et `DROP`, une instruction SQL par fichier embarquée par `include_str!` ; +- centralise l'initialisation dans un seul orchestrateur PostgreSQL transactionnel avec advisory lock ; les initialiseurs raw/Core/decode partiels sont supprimés, y compris dans les tests ; +- refuse explicitement la présence d'objets historiques `kb_sol_*` au lieu de mélanger les deux namespaces ; +- remplace la lecture `_sqlx_migrations` comme preuve du schéma courant par la vérification du contrat réellement observé ; +- étend le résumé backend-agnostique aux catégories physiques `table` et `index`, avec **75 index attendus** sans exposer leurs noms. + +### Readiness et contrats + +- conserve explicitement la `returnData` en N2 et enrichit le replay Core v3 avec `block_time`, `stack_height`, parent CPI, contexte top-level et return data ; +- introduit les DTO N1/N2 génériques d'observation/état de compte nécessaires au futur décodage de layouts inconnus, sans ajouter de structure Anchor/IDL spécifique ; leur ingestion/replay fonctionnel est raccordé dans la tranche repositories/replay ; +- ajoute un canari workspace imposant `crate::Type` pour les types publics de `ks-store` utilisés dans leur crate propriétaire ; +- ne crée aucune table DEX/trading ni projection metadata N4 et ne déclare pas encore le gel formel : le snapshot final reste prévu après les validations techniques. + ## `0.5.3-pre.002-delta-fix-003` - corrige deux appels de helpers strictement privés PostgreSQL qui avaient été qualifiés à tort via `crate::`; ils restent locaux à leur module et ne sont pas réexportés au crate-root ; diff --git a/ks-store/Cargo.toml b/ks-store/Cargo.toml index fcc1ad6..01787d2 100644 --- a/ks-store/Cargo.toml +++ b/ks-store/Cargo.toml @@ -1,5 +1,5 @@ # file: ks-store/Cargo.toml -# version: 3 +# version: 5 [package] name = "ks-store" diff --git a/ks-store/README.md b/ks-store/README.md index 5c4b20f..d74bf46 100644 --- a/ks-store/README.md +++ b/ks-store/README.md @@ -1,5 +1,5 @@ - + # ks-store @@ -10,7 +10,7 @@ La crate expose : - `Store` et `StoreOpenOptions` comme façade d'ouverture et handle persistant backend-agnostique ; -- les DTO et entités raw, observations, Core, décodage, matérialisation et ledger ; +- les DTO et entités N1 raw/acquisition, N2 Core, N3 decode/materialization et ledger ; - les traits async `StoreHealthStore`, `RawTransactionStore`, `CoreTransactionStore`, `CoreExtractionStore` et `DecodePipelineStore` ; - les filtres et résultats de replay sans préfixe backend ; - des diagnostics et résumés runtime/init sanitisés ne révélant ni secret ni nom physique d'objet ; @@ -26,11 +26,35 @@ La crate expose : `StoreOpenOptions` ne dérive pas `Debug` et ne rend pas les options backend accessibles après construction. Les résumés publics exposent uniquement des informations explicitement sanitisées : code backend, présence d'une connexion, état d'initialisation, health, modèle logique et compteurs. -Le backend PostgreSQL masque le descripteur de connexion et utilise l’unique target de crate `ks-store`, complété par `backend="postgres"`, `domain="ks-store.pg"` et un champ `action` structuré. Aucun DSN, password ou valeur bindée n'est destiné aux diagnostics publics. +Le backend PostgreSQL masque le descripteur de connexion et utilise l'unique target de crate `ks-store`, complété par `backend="postgres"`, `domain="ks-store.pg"` et un champ `action` structuré. Aucun DSN, password ou valeur bindée n'est destiné aux diagnostics publics. -## Schéma pendant `0.5.3` +## Baseline PostgreSQL `0.5.3-pre.003` -`0.5.3-pre.002` ne modifie pas encore les tables héritées `kb_sol_*`. Leur reconstruction sous `k_sol_*`, les ressources SQL atomiques et le gel N1/N2/N3 commencent en `pre.003` conformément au plan actif. +`pre.003` reconstruit directement le schéma actif sous le namespace `k_sol_*`. Les quatre migrations historiques `0001` à `0004` et les deux scripts de maintenance monolithiques ne font plus partie de la chaîne active. + +Le baseline candidat au gel comprend **16 tables N1-N3** : + +- N1 : transactions raw, observations d'acquisition de transactions et observations génériques de comptes ; +- N2 : transaction Core, clés de comptes, instructions top-level, CPI, logs, variations de balances, return data et états canoniques de comptes ; +- N3/transverse : processing ledger, événements décodés, déclarations/observations de couverture et `k_sol_mat_outputs`. + +Les ressources PostgreSQL sont rangées sous `migrations/postgres/` et suivent la règle **une instruction SQL par fichier**. L'orchestrateur privé applique tables, contraintes et index dans une transaction unique et refuse explicitement un schéma historique contenant encore des objets `kb_sol_*`. + +Le baseline comporte actuellement **236 ressources SQL atomiques** et **75 index attendus**, comptés sans exposer leurs noms dans l'API publique. + +## Readiness des futurs décodeurs + +Le canari future-decoder a conduit à trois corrections génériques avant gel : + +- `block_time` devient une colonne de premier rang sur raw/Core transaction ; +- `returnData` est conservée explicitement dans le Core via `k_sol_core_return_data` ; +- un chemin générique d'observation/état de compte est réservé via `k_sol_obs_account_observations` et `k_sol_core_account_states`. + +Les instructions top-level et CPI restent physiquement séparées. Les deux contrats portent `stack_height` lorsque disponible ; les CPI conservent un `parent_instruction_path` vers le parent immédiat reconstructible. + +N3 réserve en outre une provenance de schéma nullable sur les observations décodées afin qu'un futur décodeur basé sur un schéma externe/IDL puisse tracer ce schéma sans remodeler le Core. + +Aucune structure `anchor_*`, DEX ou projection metadata N4 n'est créée en `pre.003`. ## Responsabilités @@ -43,7 +67,7 @@ Le backend PostgreSQL masque le descripteur de connexion et utilise l’unique t ## Hors périmètre -La crate ne décode pas les instructions, n'acquiert pas les transactions et ne décide pas quelle matérialisation ou projection N4 exécuter. Elle persiste les faits produits par `ks-lib` et orchestrés par `ks-pipeline`. +La crate ne décode pas les instructions, n'acquiert pas elle-même les transactions/comptes et ne décide pas quelle matérialisation ou projection N4 exécuter. Elle persiste les faits produits par `ks-lib` et orchestrés par `ks-pipeline`. ## Relations diff --git a/ks-store/TODO.md b/ks-store/TODO.md index 01c62ed..8285e90 100644 --- a/ks-store/TODO.md +++ b/ks-store/TODO.md @@ -1,17 +1,19 @@ - + # TODO — ks-store ## Série `0.5.x` -- [ ] `0.5.3` — reconstruire `kb_sol_* -> k_sol_*`, ajouter `block_time` aux ancres raw/Core, conserver top-level/CPI dans deux tables Core avec lifecycle/replay équivalents, `stack_height`/parent CPI audités et renommer le journal N3 en `k_sol_mat_outputs`. -- [ ] `0.5.3` — déplacer les ressources sous `migrations/postgres/`, imposer une instruction SQL par fichier avec exécution privée via `include_str!`, séparer tables/keys/index/drop/truncate et supprimer le DDL dupliqué en Rust. -- [ ] `0.5.3` — corriger l'invalidation descendante, formaliser les replays N1 -> N2 / N2 -> N3 / frontière N3 -> N4, les paginations/limites, les conversions BIGINT et les index justifiés par les requêtes réelles. -- [ ] `0.5.3` — finaliser en `pre.005` la présentation fonctionnelle des surfaces `demo_store_*`, `demo_config` et le splash autour des modèles/compteurs du nouveau baseline sans noms d'objets physiques ; le renommage technique `demo_sql_* -> demo_store_*` est réalisé en `pre.002`. -- [ ] `0.5.3` — exécuter avant gel le canari future-decoder/Anchor-readiness sur N1/N2/N3 : bytes d'instruction, comptes ordonnés/flags, hiérarchie CPI, logs, `returnData`, observations/états de comptes et provenance decoder/schema ; ajouter seulement les contrats Solana génériques manquants, sans table `anchor_*`. -- [ ] `0.5.3` — produire le snapshot machine-readable des tables N1/N2/N3 réellement stabilisées après ce canari (13 tables existantes de baseline, plus éventuels contrats génériques nécessaires), documenter la politique N4 plus souple et les futures projections metadata asset/token + program metadata, puis ajouter les canaris anti-`kb_sol_*`, anti-backend leak, anti-secret et logging sous le target canonique `ks-store` avec domaine structuré `ks-store.pg`. -- [ ] PostgreSQL - auditer le dimensionnement du pool selon les charges réelles. -- [ ] Résilience - décider et implémenter, si justifié, un retry borné pour les timeouts transitoires. -- [ ] Administration - définir les outils supplémentaires prévus par le ROADMAP avant leur implémentation. -- [ ] Historique - documenter et tester les futures opérations historiques ajoutées à la crate. +- [ ] `0.5.3` — finaliser en `pre.004` les repositories/replay au-dessus du baseline de 16 tables : sélection autonome des CPI, lecture logique top-level+CPI, invalidation descendante, replay N1 -> N2 / N2 -> N3 / frontière N3 -> N4 et ingestion/replay du nouveau couple observation/état de compte. +- [ ] `0.5.3` — finaliser en `pre.004` pagination/limites, conversions BIGINT et plans/index justifiés par les requêtes réelles ; les 75 index du baseline `pre.003` restent optimisables avant gel. +- [ ] `0.5.3` — finaliser en `pre.005` la présentation fonctionnelle des surfaces `demo_store_*`, `demo_config` et le splash autour des modèles/compteurs du nouveau baseline sans noms d'objets physiques. +- [ ] `0.5.3` — produire le snapshot machine-readable du baseline N1/N2/N3 réellement stabilisé, ajouter les canaris de gel/anti-`kb_sol_*`/anti-backend leak/anti-secret, confirmer le canari future-decoder sur transactions + comptes et documenter la politique N4 plus souple. +- [ ] `0.5.3` — valider PostgreSQL réel sur base vide/recréée : 16 tables, index attendus, réouverture idempotente, refus de l'ancien namespace, rollback et roundtrips raw/Core/decode/materialization. +- [ ] `0.5.x` — concevoir les projections N4 metadata : asset/token metadata commune Metaplex + Token-2022 et program metadata séparée pour SPM, toutes deux reconstructibles depuis `k_sol_mat_outputs`. + +## Plus tard + +- [ ] définir une stratégie de pool/réessai configurable commune aux futurs backends sans exposer leurs types de connexion ; +- [ ] décider si un journal append-only des tentatives de processing doit compléter le ledger courant ; +- [ ] ajouter des projections spécialisées uniquement lorsqu'un décodeur/materializer et un besoin queryable réel en démontrent les invariants. diff --git a/ks-store/USAGE.md b/ks-store/USAGE.md index 5a7cf72..5700bbd 100644 --- a/ks-store/USAGE.md +++ b/ks-store/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de ks-store @@ -27,7 +27,30 @@ let store = match ks_store::Store::open(options).await { }; ``` -`Store::open` interprète les options du backend actif, crée la connexion, exécute l'auto-initialisation lorsqu'elle est configurée et capture un résumé d'initialisation sanitisé. PostgreSQL reste le seul backend opérationnel exigé en `0.5.3-pre.002`; un code backend inconnu est refusé proprement. +`Store::open` interprète les options du backend actif, crée la connexion, exécute l'auto-initialisation lorsqu'elle est configurée et capture un résumé d'initialisation sanitisé. PostgreSQL reste le seul backend opérationnel exigé en `0.5.3`; un code backend inconnu est refusé proprement. + +Avec PostgreSQL, l'auto-initialisation utilise un **seul orchestrateur transactionnel global**. Il ne faut pas initialiser séparément raw/Core/decode : la cohérence du baseline est vérifiée comme un contrat unique. + +## Baseline PostgreSQL actif + +`0.5.3-pre.003` utilise directement le namespace `k_sol_*` et un baseline candidat au gel de 16 tables N1-N3. Une base contenant encore des objets `kb_sol_*` est refusée ; elle doit être supprimée/reconstruite explicitement. + +Les ressources actives sont sous : + +```text +ks-store/migrations/postgres/ + schema/ + tables/ + constraints/ + indexes/ + maintenance/ + truncate/ + drop/ +``` + +Chaque fichier contient une seule instruction SQL. Les fonctions privées du backend l'embarquent par `include_str!`; le DDL n'est pas dupliqué dans des chaînes Rust. + +Le baseline courant compte 236 ressources SQL atomiques et 75 index attendus. ## Résumés sûrs @@ -42,7 +65,7 @@ let runtime = match store.runtime_summary().await { Ces contrats ne contiennent pas les options backend brutes. Le descripteur de connexion éventuellement exposé par `runtime.backend` est masqué par le backend propriétaire. -Le résumé d'initialisation classe les ressources par modèles logiques et fournit des compteurs. Il expose aussi `objects`, une liste de catégories physiques sans noms d'objets : PostgreSQL publie déjà le total `table`, tandis que `pre.003` ajoutera le total `index` avec le nouveau baseline. Les noms physiques de tables, index et contraintes restent internes au backend. +Le résumé d'initialisation classe les ressources par modèles logiques et fournit des compteurs. `objects` expose des catégories physiques sans noms d'objets. PostgreSQL publie actuellement les catégories `table` et `index`; un futur backend peut publier d'autres catégories sans modifier le contrat desktop. ## Diagnostics de ressources @@ -61,7 +84,7 @@ for resource in raw_resources { } ``` -Les méthodes `core_resource_diagnostics`, `decode_resource_diagnostics` et `known_resource_diagnostics` suivent le même contrat backend-agnostique. +Les méthodes `core_resource_diagnostics`, `decode_resource_diagnostics` et `known_resource_diagnostics` suivent le même contrat backend-agnostique. Les codes de ressources publics sont logiques ; les noms physiques PostgreSQL restent internes. ## Contrats de repositories @@ -73,7 +96,33 @@ Les traits publics effectivement implémentés sont : - `CoreExtractionStore` ; - `DecodePipelineStore`. -`Store` implémente ces traits et délègue au backend privé. Les anciens traits historiques sans implémentation réelle ont été retirés avant gel de l'API. +`Store` implémente ces traits et délègue au backend privé. + +Le schéma `pre.003` réserve aussi les contrats génériques d'observation/état de comptes nécessaires au gel N1/N2. Leur ingestion/replay opérationnel est raccordé dans la tranche repositories/replay suivante ; leur présence dans le baseline évite une modification structurelle ultérieure pour un futur décodeur de comptes. + +## Core et replay N2 -> N3 + +Le replay d'instruction Core transporte désormais : + +- `block_time` nullable ; +- `stack_height` nullable ; +- `parent_instruction_path` pour les CPI ; +- le contexte ordonné des instructions top-level ; +- la `returnData` transactionnelle lorsqu'elle existe. + +Le contrat de replay Core est en version 3. Les anciens constructeurs restent utilisables pour les tests/sources synthétiques ; `ks-store` enrichit les inputs issus du Core persistant avec le contexte disponible. + +Les instructions top-level et CPI restent deux tables physiques. La sélection logique commune et le replay autonome des CPI sont finalisés dans la tranche repositories/replay. + +## Matérialisation N3 + +Le journal générique est `k_sol_mat_outputs`. L'API publique utilise désormais : + +- `MaterializedOutputFilter` ; +- `MaterializedOutputQueryRow` ; +- `DecodePipelineStore::list_materialized_outputs`. + +`k_sol_mat_outputs` reste obligatoire même lorsque des projections N4 spécialisées existeront. ## Replay et requêtes bornées @@ -92,7 +141,7 @@ let entities = match store.replay_entity_summaries(entity_filter).await { }; ``` -Les contrats publics utilisent `ReplayTransaction*`, `ReplayProgram*` et `ReplayEntity*`. Le scope d'instruction utilise désormais `TopLevel`, `Inner` et `Logs`; le terme historique `outer` n'est pas introduit dans la nouvelle API. +Les contrats publics utilisent `ReplayTransaction*`, `ReplayProgram*` et `ReplayEntity*`. Le scope d'instruction utilise `TopLevel`, `Inner` et `Logs`; le terme historique `outer` n'est pas introduit dans la nouvelle API store. ## Pagination bornée @@ -106,7 +155,7 @@ assert_eq!(first_page.limit, ks_store::DEFAULT_PAGE_SIZE); assert!(next_page.limit <= ks_store::MAX_PAGE_SIZE); ``` -La refonte cursorisée prévue par le plan `0.5.3` reste dans la tranche repositories/replay ultérieure. +La refonte cursorisée et la suppression des gros offsets comme contrat durable appartiennent à la tranche repositories/replay. ## Transactions atomiques @@ -116,9 +165,20 @@ Les bundles `CoreExtractionBundle`, `DecodePersistenceBundle` et `Materializatio Les tests opt-in utilisent `KS_SECRET_POSTGRES_TEST_URL`. Aucun credential réel ne doit être enregistré dans les fixtures ou logs. Les erreurs d'ouverture de connexion sont volontairement sanitisées. -## Limites de `pre.002` +Pour `pre.003`, les validations PostgreSQL à exécuter après compilation sont particulièrement importantes : -- les tables restent temporairement sous leur namespace historique jusqu'à `pre.003` ; -- les migrations PostgreSQL ne sont pas encore déplacées sous `migrations/postgres/` ; -- la pagination cursorisée et l'invalidation descendante sont traitées dans la tranche repositories/replay ; -- la refonte complète des fenêtres desktop `demo_store_*` est prévue dans la tranche consommateurs. +- init sur base vide ; +- réouverture idempotente ; +- présence des 16 tables et 75 index attendus ; +- refus d'un schéma historique `kb_sol_*` ; +- roundtrip raw/Core/decode/materialization ; +- rollback transactionnel Core/decode. + +## Travaux encore ouverts après `pre.003` + +- sélection/replay autonome des CPI et lecture logique top-level+CPI ; +- invalidation descendante N1 -> N2 -> N3 -> N4 ; +- replay opérationnel du nouveau flux d'observations/états de comptes ; +- pagination cursorisée et revue finale des index par plans réels ; +- snapshot machine-readable et gel formel en tranche de réconciliation technique ; +- refonte desktop fonctionnelle finale autour du nouveau baseline. diff --git a/ks-store/maintenance/drop_raw_core_store.sql b/ks-store/maintenance/drop_raw_core_store.sql deleted file mode 100644 index 454f857..0000000 --- a/ks-store/maintenance/drop_raw_core_store.sql +++ /dev/null @@ -1,21 +0,0 @@ --- file: ks-store/maintenance/drop_raw_core_store.sql --- version: 2 - --- Drop raw and core Solana storage tables in reverse dependency order. --- This script is intended for local development and test databases only. --- It does not drop unrelated schemas, migrations, roles or configuration state. --- It intentionally avoids CASCADE so unexpected dependencies fail loudly. --- No custom PostgreSQL enum/domain types are owned by these 0.2.3/0.2.4 tables. - -BEGIN; - -DROP TABLE IF EXISTS kb_sol_core_balance_changes; -DROP TABLE IF EXISTS kb_sol_core_logs; -DROP TABLE IF EXISTS kb_sol_core_inner_instructions; -DROP TABLE IF EXISTS kb_sol_core_instructions; -DROP TABLE IF EXISTS kb_sol_core_account_keys; -DROP TABLE IF EXISTS kb_sol_core_transactions; -DROP TABLE IF EXISTS kb_sol_obs_transaction_observations; -DROP TABLE IF EXISTS kb_sol_raw_transactions; - -COMMIT; diff --git a/ks-store/maintenance/reset_derived_store_keep_raw.sql b/ks-store/maintenance/reset_derived_store_keep_raw.sql deleted file mode 100644 index 1a7d4f5..0000000 --- a/ks-store/maintenance/reset_derived_store_keep_raw.sql +++ /dev/null @@ -1,30 +0,0 @@ --- file: ks-store/maintenance/reset_derived_store_keep_raw.sql --- version: 3 - --- Reset every reconstructible Solana projection while preserving acquisition data. --- Intended for controlled local/research rebuilds only. --- Preserved tables: --- - kb_sol_raw_transactions --- - kb_sol_obs_transaction_observations --- No CASCADE is used: unexpected dependencies fail loudly. - -BEGIN; -TRUNCATE TABLE - kb_sol_mat_events, - kb_sol_decode_coverage_observations, - kb_sol_decode_coverage_declarations, - kb_sol_decode_events, - kb_sol_ops_processing_ledger, - kb_sol_core_balance_changes, - kb_sol_core_logs, - kb_sol_core_inner_instructions, - kb_sol_core_instructions, - kb_sol_core_account_keys, - kb_sol_core_transactions -RESTART IDENTITY; -UPDATE kb_sol_raw_transactions -SET - processing_state = 'received', - lifecycle_reason = NULL, - updated_at = NOW(); -COMMIT; diff --git a/ks-store/migrations/0001_canonical_transaction_store.sql b/ks-store/migrations/0001_canonical_transaction_store.sql deleted file mode 100644 index e7a7ea1..0000000 --- a/ks-store/migrations/0001_canonical_transaction_store.sql +++ /dev/null @@ -1,94 +0,0 @@ --- file: ks-store/migrations/0001_canonical_transaction_store.sql --- version: 2 - --- Current canonical transaction and acquisition observation store. --- This baseline contains only active tables and uses the current PostgreSQL schema. --- Historical 0.2.x upgrade logic was validated before this baseline was consolidated. - -CREATE TABLE IF NOT EXISTS kb_sol_raw_transactions ( - id BIGSERIAL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - canonical_json JSONB, - canonical_json_hash TEXT, - canonical_format_version INTEGER NOT NULL DEFAULT 1, - retention_state TEXT NOT NULL DEFAULT 'full', - processing_state TEXT NOT NULL DEFAULT 'received', - lifecycle_reason TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_raw_transactions PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_raw_transactions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_raw_transactions_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_raw_transactions_format_version_positive CHECK (canonical_format_version > 0), - CONSTRAINT ck_kb_sol_raw_transactions_retention_state CHECK (retention_state IN ('full', 'compacted', 'archived', 'purged')), - CONSTRAINT ck_kb_sol_raw_transactions_processing_state CHECK (processing_state IN ('received', 'core_extracted', 'decoded', 'materialized', 'failed')), - CONSTRAINT ck_kb_sol_raw_transactions_full_has_json CHECK (retention_state <> 'full' OR canonical_json IS NOT NULL) - ); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_raw_transactions_signature ON kb_sol_raw_transactions (signature); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_slot ON kb_sol_raw_transactions (slot); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_created_at ON kb_sol_raw_transactions (created_at); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_processing ON kb_sol_raw_transactions (processing_state); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_canonical_hash ON kb_sol_raw_transactions (canonical_json_hash) WHERE canonical_json_hash IS NOT NULL; - -CREATE TABLE IF NOT EXISTS kb_sol_obs_transaction_observations ( - id BIGSERIAL, - raw_transaction_id BIGINT, - observation_key TEXT NOT NULL, - signature TEXT, - slot BIGINT, - provider TEXT NOT NULL, - endpoint_code TEXT, - protocol TEXT NOT NULL, - acquisition_method TEXT NOT NULL, - origin TEXT NOT NULL, - commitment TEXT, - capture_session_id TEXT, - filter_code TEXT, - detected_at TIMESTAMPTZ, - received_at TIMESTAMPTZ NOT NULL, - normalized_at TIMESTAMPTZ, - persisted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - payload_size_bytes BIGINT, - source_payload_hash TEXT, - status TEXT NOT NULL DEFAULT 'received', - error_code TEXT, - error_message TEXT, - CONSTRAINT pk_kb_sol_obs_transaction_observations PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_obs_transaction_observations_raw_transaction FOREIGN KEY (raw_transaction_id) REFERENCES kb_sol_raw_transactions(id) ON DELETE SET NULL, - CONSTRAINT ck_kb_sol_obs_transaction_observations_key_not_empty CHECK (length(btrim(observation_key)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_signature_not_empty CHECK (signature IS NULL OR length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_slot_non_negative CHECK (slot IS NULL OR slot >= 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_provider_not_empty CHECK (length(btrim(provider)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_endpoint_not_empty CHECK (endpoint_code IS NULL OR length(btrim(endpoint_code)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_protocol_not_empty CHECK (length(btrim(protocol)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_method_not_empty CHECK (length(btrim(acquisition_method)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_origin CHECK (origin IN ('live', 'backfill', 'replay', 'repair', 'migration')), - CONSTRAINT ck_kb_sol_obs_transaction_observations_commitment_not_empty CHECK (commitment IS NULL OR length(btrim(commitment)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_session_not_empty CHECK (capture_session_id IS NULL OR length(btrim(capture_session_id)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_filter_not_empty CHECK (filter_code IS NULL OR length(btrim(filter_code)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_payload_size_non_negative CHECK (payload_size_bytes IS NULL OR payload_size_bytes >= 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_hash_not_empty CHECK (source_payload_hash IS NULL OR length(btrim(source_payload_hash)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_status CHECK (status IN ('detected', 'received', 'normalized', 'persisted', 'failed', 'missing')), - CONSTRAINT ck_kb_sol_obs_transaction_observations_error_code_not_empty CHECK (error_code IS NULL OR length(btrim(error_code)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_error_message_not_empty CHECK (error_message IS NULL OR length(btrim(error_message)) > 0) - ); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_obs_transaction_observations_key ON kb_sol_obs_transaction_observations (observation_key); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_signature ON kb_sol_obs_transaction_observations (signature) WHERE signature IS NOT NULL; - -CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_slot ON kb_sol_obs_transaction_observations (slot) WHERE slot IS NOT NULL; - -CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_provider_method ON kb_sol_obs_transaction_observations (provider, acquisition_method); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_received_at ON kb_sol_obs_transaction_observations (received_at); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_raw_transaction ON kb_sol_obs_transaction_observations (raw_transaction_id) WHERE raw_transaction_id IS NOT NULL; - -CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_status ON kb_sol_obs_transaction_observations (status); diff --git a/ks-store/migrations/0002_core_store.sql b/ks-store/migrations/0002_core_store.sql deleted file mode 100644 index e6ccad1..0000000 --- a/ks-store/migrations/0002_core_store.sql +++ /dev/null @@ -1,196 +0,0 @@ --- file: ks-store/migrations/0002_core_store.sql --- version: 2 - --- Current normalized Solana core store. --- This migration uses the current PostgreSQL schema from the active profile. --- It must not create application schemas such as raw, core, obs, decode, mat or catalog. --- Object naming convention: pk_ for primary keys, fk_ for foreign keys, ux_ for unique indexes and ix_ for non-unique indexes. - -CREATE TABLE IF NOT EXISTS kb_sol_core_transactions ( - id BIGSERIAL, - raw_transaction_id BIGINT, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - failed BOOLEAN NOT NULL DEFAULT FALSE, - err_json JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_transactions PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_transactions_raw_transaction FOREIGN KEY (raw_transaction_id) REFERENCES kb_sol_raw_transactions(id) ON DELETE SET NULL, - CONSTRAINT ck_kb_sol_core_transactions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_transactions_slot_non_negative CHECK (slot >= 0) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_transactions_signature - ON kb_sol_core_transactions (signature); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_transactions_slot - ON kb_sol_core_transactions (slot); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_transactions_created_at - ON kb_sol_core_transactions (created_at); - -CREATE TABLE IF NOT EXISTS kb_sol_core_account_keys ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - account_index INTEGER NOT NULL, - account_key TEXT NOT NULL, - source TEXT NOT NULL, - writable BOOLEAN NOT NULL, - signer BOOLEAN NOT NULL, - executable BOOLEAN, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_account_keys PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_account_keys_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_account_keys_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_account_keys_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_account_keys_index_non_negative CHECK (account_index >= 0), - CONSTRAINT ck_kb_sol_core_account_keys_key_not_empty CHECK (length(btrim(account_key)) > 0), - CONSTRAINT ck_kb_sol_core_account_keys_source CHECK (source IN ('static', 'loaded_writable', 'loaded_readonly')) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_account_keys_sig_index - ON kb_sol_core_account_keys (signature, account_index); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_account_keys_key - ON kb_sol_core_account_keys (account_key); - -CREATE TABLE IF NOT EXISTS kb_sol_core_instructions ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - instruction_path TEXT NOT NULL, - program_id TEXT NOT NULL, - accounts_json JSONB NOT NULL, - payload_json JSONB, - payload_json_hash TEXT, - processing_state TEXT NOT NULL DEFAULT 'pending', - processor_name TEXT, - processor_version TEXT, - lifecycle_reason TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_instructions PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_instructions_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_instructions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_instructions_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_instructions_path_not_empty CHECK (length(btrim(instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_instructions_program_not_empty CHECK (length(btrim(program_id)) > 0), - CONSTRAINT ck_kb_sol_core_instructions_processing_state CHECK (processing_state IN ('pending', 'decoded', 'materialized', 'ignored', 'failed', 'replay_requested')) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_instructions_sig_path - ON kb_sol_core_instructions (signature, instruction_path); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_instructions_program - ON kb_sol_core_instructions (program_id); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_instructions_slot - ON kb_sol_core_instructions (slot); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_instructions_processing - ON kb_sol_core_instructions (processing_state); - -CREATE TABLE IF NOT EXISTS kb_sol_core_inner_instructions ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - parent_instruction_path TEXT NOT NULL, - instruction_path TEXT NOT NULL, - program_id TEXT NOT NULL, - accounts_json JSONB NOT NULL, - payload_json JSONB, - payload_json_hash TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_inner_instructions PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_inner_instructions_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_inner_instructions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_parent_not_empty CHECK (length(btrim(parent_instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_path_not_empty CHECK (length(btrim(instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_program_not_empty CHECK (length(btrim(program_id)) > 0) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_inner_instructions_sig_path - ON kb_sol_core_inner_instructions (signature, instruction_path); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_inner_instructions_parent - ON kb_sol_core_inner_instructions (signature, parent_instruction_path); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_inner_instructions_program - ON kb_sol_core_inner_instructions (program_id); - -CREATE TABLE IF NOT EXISTS kb_sol_core_logs ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - log_index INTEGER NOT NULL, - instruction_path TEXT, - program_id TEXT, - log_text TEXT, - log_text_hash TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_logs PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_logs_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_logs_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_logs_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_logs_index_non_negative CHECK (log_index >= 0), - CONSTRAINT ck_kb_sol_core_logs_text_not_empty CHECK (log_text IS NULL OR length(btrim(log_text)) > 0), - CONSTRAINT ck_kb_sol_core_logs_path_not_empty CHECK (instruction_path IS NULL OR length(btrim(instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_logs_program_not_empty CHECK (program_id IS NULL OR length(btrim(program_id)) > 0) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_logs_sig_index - ON kb_sol_core_logs (signature, log_index); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_logs_program - ON kb_sol_core_logs (program_id) - WHERE program_id IS NOT NULL; - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_logs_path - ON kb_sol_core_logs (signature, instruction_path) - WHERE instruction_path IS NOT NULL; - -CREATE TABLE IF NOT EXISTS kb_sol_core_balance_changes ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - balance_change_index INTEGER NOT NULL, - balance_kind TEXT NOT NULL, - account_index INTEGER, - account_key TEXT, - mint TEXT, - owner TEXT, - pre_balance_json JSONB, - post_balance_json JSONB, - delta_json JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_balance_changes PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_balance_changes_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_balance_changes_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_balance_changes_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_balance_changes_index_non_negative CHECK (balance_change_index >= 0), - CONSTRAINT ck_kb_sol_core_balance_changes_account_index_non_negative CHECK (account_index IS NULL OR account_index >= 0), - CONSTRAINT ck_kb_sol_core_balance_changes_kind CHECK (balance_kind IN ('native_lamports', 'token_amount')), - CONSTRAINT ck_kb_sol_core_balance_changes_account_not_empty CHECK (account_key IS NULL OR length(btrim(account_key)) > 0), - CONSTRAINT ck_kb_sol_core_balance_changes_mint_not_empty CHECK (mint IS NULL OR length(btrim(mint)) > 0), - CONSTRAINT ck_kb_sol_core_balance_changes_owner_not_empty CHECK (owner IS NULL OR length(btrim(owner)) > 0) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_balance_changes_sig_index - ON kb_sol_core_balance_changes (signature, balance_change_index); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_balance_changes_account - ON kb_sol_core_balance_changes (account_key) - WHERE account_key IS NOT NULL; - -CREATE INDEX IF NOT EXISTS ix_kb_sol_core_balance_changes_mint - ON kb_sol_core_balance_changes (mint) - WHERE mint IS NOT NULL; - diff --git a/ks-store/migrations/0003_processing_ledger.sql b/ks-store/migrations/0003_processing_ledger.sql deleted file mode 100644 index 52b5b01..0000000 --- a/ks-store/migrations/0003_processing_ledger.sql +++ /dev/null @@ -1,41 +0,0 @@ --- file: ks-store/migrations/0003_processing_ledger.sql --- version: 2 - --- Current processing ledger for canonical to core extraction and later pipeline stages. --- This migration uses the current PostgreSQL schema from the active profile. - -CREATE TABLE IF NOT EXISTS kb_sol_ops_processing_ledger ( - id BIGSERIAL, - stage TEXT NOT NULL, - processor_name TEXT NOT NULL, - processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, - input_hash TEXT NOT NULL, - status TEXT NOT NULL, - attempt_count INTEGER NOT NULL DEFAULT 0, - started_at TIMESTAMPTZ, - finished_at TIMESTAMPTZ, - error_code TEXT, - error_message TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_ops_processing_ledger PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_ops_processing_ledger_stage_not_empty CHECK (length(btrim(stage)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_processor_name_not_empty CHECK (length(btrim(processor_name)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_processor_version_not_empty CHECK (length(btrim(processor_version)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_input_key_not_empty CHECK (length(btrim(input_key)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_input_hash_not_empty CHECK (length(btrim(input_hash)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_status CHECK (status IN ('running', 'succeeded', 'failed')), - CONSTRAINT ck_kb_sol_ops_processing_ledger_attempt_count_non_negative CHECK (attempt_count >= 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_error_code_not_empty CHECK (error_code IS NULL OR length(btrim(error_code)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_error_message_not_empty CHECK (error_message IS NULL OR length(btrim(error_message)) > 0) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_ops_processing_ledger_identity - ON kb_sol_ops_processing_ledger (stage, processor_name, processor_version, input_key); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_ops_processing_ledger_status - ON kb_sol_ops_processing_ledger (stage, processor_name, status, updated_at); - -CREATE INDEX IF NOT EXISTS ix_kb_sol_ops_processing_ledger_input_hash - ON kb_sol_ops_processing_ledger (input_hash); diff --git a/ks-store/migrations/0004_decode_materialization_store.sql b/ks-store/migrations/0004_decode_materialization_store.sql deleted file mode 100644 index e7d9359..0000000 --- a/ks-store/migrations/0004_decode_materialization_store.sql +++ /dev/null @@ -1,132 +0,0 @@ --- file: ks-store/migrations/0004_decode_materialization_store.sql --- version: 2 - --- Common versioned decode, coverage and materialization store. --- This migration uses the current PostgreSQL schema from the active profile. - -CREATE TABLE IF NOT EXISTS kb_sol_decode_events ( - id BIGSERIAL, - processor_name TEXT NOT NULL, - processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, - input_hash TEXT NOT NULL, - event_key TEXT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - instruction_path TEXT NOT NULL, - program_id TEXT NOT NULL, - protocol_code TEXT NOT NULL, - surface_code TEXT NOT NULL, - event_code TEXT NOT NULL, - event_name TEXT NOT NULL, - event_family TEXT NOT NULL, - source_kind TEXT NOT NULL, - confidence TEXT NOT NULL, - proof_kind TEXT NOT NULL, - proof_jsonb JSONB NOT NULL, - payload_jsonb JSONB NOT NULL, - transaction_failed BOOLEAN NOT NULL, - transaction_error_jsonb JSONB, - observation_committed BOOLEAN NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_decode_events PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_decode_events_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_decode_events_failed_not_committed CHECK (NOT transaction_failed OR NOT observation_committed) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_decode_events_processor_input_event - ON kb_sol_decode_events (processor_name, processor_version, input_key, event_key); -CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_events_signature_path - ON kb_sol_decode_events (signature, instruction_path); -CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_events_program_surface - ON kb_sol_decode_events (program_id, surface_code, event_code); -CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_events_family_commit - ON kb_sol_decode_events (event_family, transaction_failed, observation_committed); - -CREATE TABLE IF NOT EXISTS kb_sol_decode_coverage_declarations ( - id BIGSERIAL, - processor_name TEXT NOT NULL, - processor_version TEXT NOT NULL, - program_id TEXT NOT NULL, - surface_code TEXT, - entry_kind TEXT NOT NULL, - entry_code TEXT NOT NULL, - discriminator_hex TEXT, - historical BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_decode_coverage_declarations PRIMARY KEY (id) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_decode_coverage_declarations_identity - ON kb_sol_decode_coverage_declarations ( - processor_name, - processor_version, - program_id, - COALESCE(surface_code, ''), - entry_kind, - entry_code, - COALESCE(discriminator_hex, '') - ); -CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_coverage_declarations_program - ON kb_sol_decode_coverage_declarations (program_id, processor_name, processor_version); - -CREATE TABLE IF NOT EXISTS kb_sol_decode_coverage_observations ( - id BIGSERIAL, - processor_name TEXT NOT NULL, - processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, - input_hash TEXT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - instruction_path TEXT NOT NULL, - program_id TEXT NOT NULL, - surface_code TEXT, - entry_code TEXT, - discriminator_hex TEXT, - status TEXT NOT NULL, - recognized BOOLEAN NOT NULL, - decoded_count INTEGER NOT NULL DEFAULT 0, - materialized_count INTEGER NOT NULL DEFAULT 0, - error_count INTEGER NOT NULL DEFAULT 0, - transaction_failed BOOLEAN NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_decode_coverage_observations PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_decode_coverage_observations_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_decode_coverage_observations_counts_non_negative CHECK (decoded_count >= 0 AND materialized_count >= 0 AND error_count >= 0) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_decode_coverage_observations_identity - ON kb_sol_decode_coverage_observations (processor_name, processor_version, input_key); -CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_coverage_observations_program_status - ON kb_sol_decode_coverage_observations (program_id, status, transaction_failed); -CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_coverage_observations_entry - ON kb_sol_decode_coverage_observations (processor_name, processor_version, surface_code, entry_code); - -CREATE TABLE IF NOT EXISTS kb_sol_mat_events ( - id BIGSERIAL, - processor_name TEXT NOT NULL, - processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, - input_hash TEXT NOT NULL, - output_key TEXT NOT NULL, - source_event_key TEXT NOT NULL, - source_decoder_name TEXT NOT NULL, - source_decoder_version TEXT NOT NULL, - source_decode_input_key TEXT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - materialized_family TEXT NOT NULL, - payload_jsonb JSONB NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_mat_events PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_mat_events_slot_non_negative CHECK (slot >= 0) -); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_mat_events_processor_input_output - ON kb_sol_mat_events (processor_name, processor_version, input_key, output_key); -CREATE INDEX IF NOT EXISTS ix_kb_sol_mat_events_signature_family - ON kb_sol_mat_events (source_decoder_name, source_decoder_version, source_decode_input_key, signature, materialized_family); diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_keys.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_keys.sql new file mode 100644 index 0000000..eca809a --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_keys.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_keys.sql +-- version: 1 + +-- Drop k_sol_core_account_keys explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_account_keys CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_states.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_states.sql new file mode 100644 index 0000000..381d2c4 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_states.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_states.sql +-- version: 1 + +-- Drop k_sol_core_account_states explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_account_states CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_balance_changes.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_balance_changes.sql new file mode 100644 index 0000000..225b34a --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_balance_changes.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_balance_changes.sql +-- version: 1 + +-- Drop k_sol_core_balance_changes explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_balance_changes CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_inner_instructions.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_inner_instructions.sql new file mode 100644 index 0000000..b9f44b1 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_inner_instructions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_inner_instructions.sql +-- version: 1 + +-- Drop k_sol_core_inner_instructions explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_inner_instructions CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_instructions.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_instructions.sql new file mode 100644 index 0000000..e988284 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_instructions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_instructions.sql +-- version: 1 + +-- Drop k_sol_core_instructions explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_instructions CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_logs.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_logs.sql new file mode 100644 index 0000000..5dc2e59 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_logs.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_logs.sql +-- version: 1 + +-- Drop k_sol_core_logs explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_logs CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_return_data.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_return_data.sql new file mode 100644 index 0000000..4a1c85f --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_return_data.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_return_data.sql +-- version: 1 + +-- Drop k_sol_core_return_data explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_return_data CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_transactions.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_transactions.sql new file mode 100644 index 0000000..7aeebc5 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_transactions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_transactions.sql +-- version: 1 + +-- Drop k_sol_core_transactions explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_core_transactions CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_declarations.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_declarations.sql new file mode 100644 index 0000000..52ee97b --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_declarations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_declarations.sql +-- version: 1 + +-- Drop k_sol_decode_coverage_declarations explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_decode_coverage_declarations CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_observations.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_observations.sql new file mode 100644 index 0000000..7e7d92b --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_observations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_observations.sql +-- version: 1 + +-- Drop k_sol_decode_coverage_observations explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_decode_coverage_observations CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_events.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_events.sql new file mode 100644 index 0000000..d83ce74 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_events.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_events.sql +-- version: 1 + +-- Drop k_sol_decode_events explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_decode_events CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_mat_outputs.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_mat_outputs.sql new file mode 100644 index 0000000..c89d857 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_mat_outputs.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_mat_outputs.sql +-- version: 1 + +-- Drop k_sol_mat_outputs explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_mat_outputs CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_account_observations.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_account_observations.sql new file mode 100644 index 0000000..693a977 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_account_observations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_account_observations.sql +-- version: 1 + +-- Drop k_sol_obs_account_observations explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_obs_account_observations CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_transaction_observations.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_transaction_observations.sql new file mode 100644 index 0000000..690e6b7 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_transaction_observations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_transaction_observations.sql +-- version: 1 + +-- Drop k_sol_obs_transaction_observations explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_obs_transaction_observations CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_ops_processing_ledger.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_ops_processing_ledger.sql new file mode 100644 index 0000000..8c6ab7b --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_ops_processing_ledger.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_ops_processing_ledger.sql +-- version: 1 + +-- Drop k_sol_ops_processing_ledger explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_ops_processing_ledger CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_raw_transactions.sql b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_raw_transactions.sql new file mode 100644 index 0000000..81be279 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_raw_transactions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_raw_transactions.sql +-- version: 1 + +-- Drop k_sol_raw_transactions explicitly for maintenance. +DROP TABLE IF EXISTS k_sol_raw_transactions CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_keys.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_keys.sql new file mode 100644 index 0000000..8b52918 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_keys.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_keys.sql +-- version: 1 + +-- Truncate k_sol_core_account_keys explicitly for maintenance. +TRUNCATE TABLE k_sol_core_account_keys RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_states.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_states.sql new file mode 100644 index 0000000..f57e077 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_states.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_states.sql +-- version: 1 + +-- Truncate k_sol_core_account_states explicitly for maintenance. +TRUNCATE TABLE k_sol_core_account_states RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_balance_changes.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_balance_changes.sql new file mode 100644 index 0000000..1e3c1f0 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_balance_changes.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_balance_changes.sql +-- version: 1 + +-- Truncate k_sol_core_balance_changes explicitly for maintenance. +TRUNCATE TABLE k_sol_core_balance_changes RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_inner_instructions.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_inner_instructions.sql new file mode 100644 index 0000000..bce7f79 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_inner_instructions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_inner_instructions.sql +-- version: 1 + +-- Truncate k_sol_core_inner_instructions explicitly for maintenance. +TRUNCATE TABLE k_sol_core_inner_instructions RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_instructions.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_instructions.sql new file mode 100644 index 0000000..b99ac63 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_instructions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_instructions.sql +-- version: 1 + +-- Truncate k_sol_core_instructions explicitly for maintenance. +TRUNCATE TABLE k_sol_core_instructions RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_logs.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_logs.sql new file mode 100644 index 0000000..c8123b0 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_logs.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_logs.sql +-- version: 1 + +-- Truncate k_sol_core_logs explicitly for maintenance. +TRUNCATE TABLE k_sol_core_logs RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_return_data.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_return_data.sql new file mode 100644 index 0000000..a04d9c6 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_return_data.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_return_data.sql +-- version: 1 + +-- Truncate k_sol_core_return_data explicitly for maintenance. +TRUNCATE TABLE k_sol_core_return_data RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_transactions.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_transactions.sql new file mode 100644 index 0000000..1ed6d14 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_transactions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_transactions.sql +-- version: 1 + +-- Truncate k_sol_core_transactions explicitly for maintenance. +TRUNCATE TABLE k_sol_core_transactions RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_declarations.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_declarations.sql new file mode 100644 index 0000000..67403be --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_declarations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_declarations.sql +-- version: 1 + +-- Truncate k_sol_decode_coverage_declarations explicitly for maintenance. +TRUNCATE TABLE k_sol_decode_coverage_declarations RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_observations.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_observations.sql new file mode 100644 index 0000000..4b8bc94 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_observations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_observations.sql +-- version: 1 + +-- Truncate k_sol_decode_coverage_observations explicitly for maintenance. +TRUNCATE TABLE k_sol_decode_coverage_observations RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_events.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_events.sql new file mode 100644 index 0000000..bdc9e51 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_events.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_events.sql +-- version: 1 + +-- Truncate k_sol_decode_events explicitly for maintenance. +TRUNCATE TABLE k_sol_decode_events RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_mat_outputs.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_mat_outputs.sql new file mode 100644 index 0000000..fc7df28 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_mat_outputs.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_mat_outputs.sql +-- version: 1 + +-- Truncate k_sol_mat_outputs explicitly for maintenance. +TRUNCATE TABLE k_sol_mat_outputs RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_account_observations.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_account_observations.sql new file mode 100644 index 0000000..cbb454b --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_account_observations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_account_observations.sql +-- version: 1 + +-- Truncate k_sol_obs_account_observations explicitly for maintenance. +TRUNCATE TABLE k_sol_obs_account_observations RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_transaction_observations.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_transaction_observations.sql new file mode 100644 index 0000000..a0c88cf --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_transaction_observations.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_transaction_observations.sql +-- version: 1 + +-- Truncate k_sol_obs_transaction_observations explicitly for maintenance. +TRUNCATE TABLE k_sol_obs_transaction_observations RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_ops_processing_ledger.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_ops_processing_ledger.sql new file mode 100644 index 0000000..8793ab2 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_ops_processing_ledger.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_ops_processing_ledger.sql +-- version: 1 + +-- Truncate k_sol_ops_processing_ledger explicitly for maintenance. +TRUNCATE TABLE k_sol_ops_processing_ledger RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_raw_transactions.sql b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_raw_transactions.sql new file mode 100644 index 0000000..5d5b8c4 --- /dev/null +++ b/ks-store/migrations/postgres/maintenance/truncate/truncate_table_k_sol_raw_transactions.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/maintenance/truncate/truncate_table_k_sol_raw_transactions.sql +-- version: 1 + +-- Truncate k_sol_raw_transactions explicitly for maintenance. +TRUNCATE TABLE k_sol_raw_transactions RESTART IDENTITY CASCADE; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_index_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_index_non_negative.sql new file mode 100644 index 0000000..49ec601 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_index_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_index_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_account_keys_index_non_negative to k_sol_core_account_keys when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_keys_index_non_negative' + AND conrelid = to_regclass('k_sol_core_account_keys') + ) THEN + ALTER TABLE k_sol_core_account_keys ADD CONSTRAINT ck_k_sol_core_account_keys_index_non_negative CHECK (account_index >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_key_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_key_not_empty.sql new file mode 100644 index 0000000..de1a72b --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_key_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_key_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_account_keys_key_not_empty to k_sol_core_account_keys when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_keys_key_not_empty' + AND conrelid = to_regclass('k_sol_core_account_keys') + ) THEN + ALTER TABLE k_sol_core_account_keys ADD CONSTRAINT ck_k_sol_core_account_keys_key_not_empty CHECK (length(btrim(account_key)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_signature_not_empty.sql new file mode 100644 index 0000000..8d2f3ad --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_account_keys_signature_not_empty to k_sol_core_account_keys when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_keys_signature_not_empty' + AND conrelid = to_regclass('k_sol_core_account_keys') + ) THEN + ALTER TABLE k_sol_core_account_keys ADD CONSTRAINT ck_k_sol_core_account_keys_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_slot_non_negative.sql new file mode 100644 index 0000000..90b5215 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_account_keys_slot_non_negative to k_sol_core_account_keys when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_keys_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_account_keys') + ) THEN + ALTER TABLE k_sol_core_account_keys ADD CONSTRAINT ck_k_sol_core_account_keys_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_source.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_source.sql new file mode 100644 index 0000000..bc583d2 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_source.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_source.sql +-- version: 1 + +-- Add ck_k_sol_core_account_keys_source to k_sol_core_account_keys when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_keys_source' + AND conrelid = to_regclass('k_sol_core_account_keys') + ) THEN + ALTER TABLE k_sol_core_account_keys ADD CONSTRAINT ck_k_sol_core_account_keys_source CHECK (source IN ('static', 'loaded_writable', 'loaded_readonly')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_account_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_account_not_empty.sql new file mode 100644 index 0000000..55e0cbf --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_account_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_account_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_account_states_account_not_empty to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_states_account_not_empty' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT ck_k_sol_core_account_states_account_not_empty CHECK (length(btrim(account_key)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_hash_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_hash_not_empty.sql new file mode 100644 index 0000000..bb44a07 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_hash_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_hash_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_account_states_hash_not_empty to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_states_hash_not_empty' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT ck_k_sol_core_account_states_hash_not_empty CHECK (length(btrim(data_hash)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_lamports_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_lamports_non_negative.sql new file mode 100644 index 0000000..91351c2 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_lamports_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_lamports_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_account_states_lamports_non_negative to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_states_lamports_non_negative' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT ck_k_sol_core_account_states_lamports_non_negative CHECK (lamports >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_owner_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_owner_not_empty.sql new file mode 100644 index 0000000..3de5993 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_owner_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_owner_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_account_states_owner_not_empty to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_states_owner_not_empty' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT ck_k_sol_core_account_states_owner_not_empty CHECK (length(btrim(owner)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_rent_epoch_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_rent_epoch_non_negative.sql new file mode 100644 index 0000000..f34dec3 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_rent_epoch_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_rent_epoch_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_account_states_rent_epoch_non_negative to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_states_rent_epoch_non_negative' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT ck_k_sol_core_account_states_rent_epoch_non_negative CHECK (rent_epoch >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_slot_non_negative.sql new file mode 100644 index 0000000..1d326fd --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_account_states_slot_non_negative to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_states_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT ck_k_sol_core_account_states_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_space_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_space_non_negative.sql new file mode 100644 index 0000000..afa7c93 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_space_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_space_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_account_states_space_non_negative to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_account_states_space_non_negative' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT ck_k_sol_core_account_states_space_non_negative CHECK (space >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_index_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_index_non_negative.sql new file mode 100644 index 0000000..0db64d6 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_index_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_index_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_account_index_non_negative to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_account_index_non_negative' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_account_index_non_negative CHECK (account_index IS NULL OR account_index >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_not_empty.sql new file mode 100644 index 0000000..006072b --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_account_not_empty to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_account_not_empty' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_account_not_empty CHECK (account_key IS NULL OR length(btrim(account_key)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_index_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_index_non_negative.sql new file mode 100644 index 0000000..e34ca4a --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_index_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_index_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_index_non_negative to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_index_non_negative' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_index_non_negative CHECK (balance_change_index >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_kind.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_kind.sql new file mode 100644 index 0000000..ae26591 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_kind.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_kind.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_kind to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_kind' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_kind CHECK (balance_kind IN ('native_lamports', 'token_amount')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_mint_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_mint_not_empty.sql new file mode 100644 index 0000000..193ac7c --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_mint_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_mint_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_mint_not_empty to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_mint_not_empty' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_mint_not_empty CHECK (mint IS NULL OR length(btrim(mint)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_owner_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_owner_not_empty.sql new file mode 100644 index 0000000..743a700 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_owner_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_owner_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_owner_not_empty to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_owner_not_empty' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_owner_not_empty CHECK (owner IS NULL OR length(btrim(owner)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_signature_not_empty.sql new file mode 100644 index 0000000..c37bf9f --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_signature_not_empty to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_signature_not_empty' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_slot_non_negative.sql new file mode 100644 index 0000000..74b1eba --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_balance_changes_slot_non_negative to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_balance_changes_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT ck_k_sol_core_balance_changes_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_parent_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_parent_not_empty.sql new file mode 100644 index 0000000..e848c04 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_parent_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_parent_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_parent_not_empty to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_parent_not_empty' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_parent_not_empty CHECK (length(btrim(parent_instruction_path)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_path_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_path_not_empty.sql new file mode 100644 index 0000000..87ae30c --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_path_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_path_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_path_not_empty to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_path_not_empty' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_path_not_empty CHECK (length(btrim(instruction_path)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processing_state.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processing_state.sql new file mode 100644 index 0000000..9ac794e --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processing_state.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processing_state.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_processing_state to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_processing_state' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_processing_state CHECK (processing_state IN ('pending', 'decoded', 'materialized', 'ignored', 'failed', 'replay_requested')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processor_pair.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processor_pair.sql new file mode 100644 index 0000000..de26a02 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processor_pair.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processor_pair.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_processor_pair to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_processor_pair' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_processor_pair CHECK ((processor_name IS NULL AND processor_version IS NULL) OR (length(btrim(processor_name)) > 0 AND length(btrim(processor_version)) > 0)); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_program_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_program_not_empty.sql new file mode 100644 index 0000000..f2dfeda --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_program_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_program_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_program_not_empty to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_program_not_empty' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_program_not_empty CHECK (length(btrim(program_id)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_signature_not_empty.sql new file mode 100644 index 0000000..ec6f971 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_signature_not_empty to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_signature_not_empty' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_slot_non_negative.sql new file mode 100644 index 0000000..d6452a8 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_slot_non_negative to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_stack_height_positive.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_stack_height_positive.sql new file mode 100644 index 0000000..72045a7 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_stack_height_positive.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_stack_height_positive.sql +-- version: 1 + +-- Add ck_k_sol_core_inner_instructions_stack_height_positive to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_inner_instructions_stack_height_positive' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT ck_k_sol_core_inner_instructions_stack_height_positive CHECK (stack_height IS NULL OR stack_height > 1); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_path_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_path_not_empty.sql new file mode 100644 index 0000000..07d26ea --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_path_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_path_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_instructions_path_not_empty to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_instructions_path_not_empty' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT ck_k_sol_core_instructions_path_not_empty CHECK (length(btrim(instruction_path)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processing_state.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processing_state.sql new file mode 100644 index 0000000..6c4904d --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processing_state.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processing_state.sql +-- version: 1 + +-- Add ck_k_sol_core_instructions_processing_state to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_instructions_processing_state' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT ck_k_sol_core_instructions_processing_state CHECK (processing_state IN ('pending', 'decoded', 'materialized', 'ignored', 'failed', 'replay_requested')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processor_pair.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processor_pair.sql new file mode 100644 index 0000000..a4254f8 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processor_pair.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processor_pair.sql +-- version: 1 + +-- Add ck_k_sol_core_instructions_processor_pair to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_instructions_processor_pair' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT ck_k_sol_core_instructions_processor_pair CHECK ((processor_name IS NULL AND processor_version IS NULL) OR (length(btrim(processor_name)) > 0 AND length(btrim(processor_version)) > 0)); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_program_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_program_not_empty.sql new file mode 100644 index 0000000..42eefc3 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_program_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_program_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_instructions_program_not_empty to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_instructions_program_not_empty' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT ck_k_sol_core_instructions_program_not_empty CHECK (length(btrim(program_id)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_signature_not_empty.sql new file mode 100644 index 0000000..119da91 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_instructions_signature_not_empty to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_instructions_signature_not_empty' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT ck_k_sol_core_instructions_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_slot_non_negative.sql new file mode 100644 index 0000000..02f94ad --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_instructions_slot_non_negative to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_instructions_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT ck_k_sol_core_instructions_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_stack_height_positive.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_stack_height_positive.sql new file mode 100644 index 0000000..7a6d2a5 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_stack_height_positive.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_stack_height_positive.sql +-- version: 1 + +-- Add ck_k_sol_core_instructions_stack_height_positive to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_instructions_stack_height_positive' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT ck_k_sol_core_instructions_stack_height_positive CHECK (stack_height IS NULL OR stack_height > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_index_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_index_non_negative.sql new file mode 100644 index 0000000..8b19880 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_index_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_index_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_logs_index_non_negative to k_sol_core_logs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_logs_index_non_negative' + AND conrelid = to_regclass('k_sol_core_logs') + ) THEN + ALTER TABLE k_sol_core_logs ADD CONSTRAINT ck_k_sol_core_logs_index_non_negative CHECK (log_index >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_path_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_path_not_empty.sql new file mode 100644 index 0000000..4b9edc5 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_path_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_path_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_logs_path_not_empty to k_sol_core_logs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_logs_path_not_empty' + AND conrelid = to_regclass('k_sol_core_logs') + ) THEN + ALTER TABLE k_sol_core_logs ADD CONSTRAINT ck_k_sol_core_logs_path_not_empty CHECK (instruction_path IS NULL OR length(btrim(instruction_path)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_program_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_program_not_empty.sql new file mode 100644 index 0000000..56fc3de --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_program_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_program_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_logs_program_not_empty to k_sol_core_logs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_logs_program_not_empty' + AND conrelid = to_regclass('k_sol_core_logs') + ) THEN + ALTER TABLE k_sol_core_logs ADD CONSTRAINT ck_k_sol_core_logs_program_not_empty CHECK (program_id IS NULL OR length(btrim(program_id)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_signature_not_empty.sql new file mode 100644 index 0000000..dbead85 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_logs_signature_not_empty to k_sol_core_logs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_logs_signature_not_empty' + AND conrelid = to_regclass('k_sol_core_logs') + ) THEN + ALTER TABLE k_sol_core_logs ADD CONSTRAINT ck_k_sol_core_logs_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_slot_non_negative.sql new file mode 100644 index 0000000..10b6d14 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_logs_slot_non_negative to k_sol_core_logs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_logs_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_logs') + ) THEN + ALTER TABLE k_sol_core_logs ADD CONSTRAINT ck_k_sol_core_logs_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_program_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_program_not_empty.sql new file mode 100644 index 0000000..8bed928 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_program_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_program_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_return_data_program_not_empty to k_sol_core_return_data when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_return_data_program_not_empty' + AND conrelid = to_regclass('k_sol_core_return_data') + ) THEN + ALTER TABLE k_sol_core_return_data ADD CONSTRAINT ck_k_sol_core_return_data_program_not_empty CHECK (length(btrim(program_id)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_signature_not_empty.sql new file mode 100644 index 0000000..543f5a4 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_return_data_signature_not_empty to k_sol_core_return_data when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_return_data_signature_not_empty' + AND conrelid = to_regclass('k_sol_core_return_data') + ) THEN + ALTER TABLE k_sol_core_return_data ADD CONSTRAINT ck_k_sol_core_return_data_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_slot_non_negative.sql new file mode 100644 index 0000000..8bb1638 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_return_data_slot_non_negative to k_sol_core_return_data when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_return_data_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_return_data') + ) THEN + ALTER TABLE k_sol_core_return_data ADD CONSTRAINT ck_k_sol_core_return_data_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_signature_not_empty.sql new file mode 100644 index 0000000..c796c27 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_core_transactions_signature_not_empty to k_sol_core_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_transactions_signature_not_empty' + AND conrelid = to_regclass('k_sol_core_transactions') + ) THEN + ALTER TABLE k_sol_core_transactions ADD CONSTRAINT ck_k_sol_core_transactions_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_slot_non_negative.sql new file mode 100644 index 0000000..cf60632 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_core_transactions_slot_non_negative to k_sol_core_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_core_transactions_slot_non_negative' + AND conrelid = to_regclass('k_sol_core_transactions') + ) THEN + ALTER TABLE k_sol_core_transactions ADD CONSTRAINT ck_k_sol_core_transactions_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_counts_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_counts_non_negative.sql new file mode 100644 index 0000000..e7b1b05 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_counts_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_counts_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_decode_coverage_observations_counts_non_negative to k_sol_decode_coverage_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_decode_coverage_observations_counts_non_negative' + AND conrelid = to_regclass('k_sol_decode_coverage_observations') + ) THEN + ALTER TABLE k_sol_decode_coverage_observations ADD CONSTRAINT ck_k_sol_decode_coverage_observations_counts_non_negative CHECK (decoded_count >= 0 AND materialized_count >= 0 AND error_count >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_slot_non_negative.sql new file mode 100644 index 0000000..acd1e4d --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_decode_coverage_observations_slot_non_negative to k_sol_decode_coverage_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_decode_coverage_observations_slot_non_negative' + AND conrelid = to_regclass('k_sol_decode_coverage_observations') + ) THEN + ALTER TABLE k_sol_decode_coverage_observations ADD CONSTRAINT ck_k_sol_decode_coverage_observations_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_failed_not_committed.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_failed_not_committed.sql new file mode 100644 index 0000000..f48b8ad --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_failed_not_committed.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_failed_not_committed.sql +-- version: 1 + +-- Add ck_k_sol_decode_events_failed_not_committed to k_sol_decode_events when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_decode_events_failed_not_committed' + AND conrelid = to_regclass('k_sol_decode_events') + ) THEN + ALTER TABLE k_sol_decode_events ADD CONSTRAINT ck_k_sol_decode_events_failed_not_committed CHECK (NOT transaction_failed OR NOT observation_committed); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_schema_provenance.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_schema_provenance.sql new file mode 100644 index 0000000..7300a29 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_schema_provenance.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_schema_provenance.sql +-- version: 1 + +-- Add ck_k_sol_decode_events_schema_provenance to k_sol_decode_events when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_decode_events_schema_provenance' + AND conrelid = to_regclass('k_sol_decode_events') + ) THEN + ALTER TABLE k_sol_decode_events ADD CONSTRAINT ck_k_sol_decode_events_schema_provenance CHECK ((schema_kind IS NULL AND schema_id IS NULL AND schema_version IS NULL AND schema_hash IS NULL) OR (length(btrim(schema_kind)) > 0 AND length(btrim(schema_id)) > 0 AND length(btrim(schema_hash)) > 0 AND (schema_version IS NULL OR length(btrim(schema_version)) > 0))); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_slot_non_negative.sql new file mode 100644 index 0000000..bb2b850 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_decode_events_slot_non_negative to k_sol_decode_events when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_decode_events_slot_non_negative' + AND conrelid = to_regclass('k_sol_decode_events') + ) THEN + ALTER TABLE k_sol_decode_events ADD CONSTRAINT ck_k_sol_decode_events_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_mat_outputs_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_mat_outputs_slot_non_negative.sql new file mode 100644 index 0000000..04f6fe3 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_mat_outputs_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_mat_outputs_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_mat_outputs_slot_non_negative to k_sol_mat_outputs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_mat_outputs_slot_non_negative' + AND conrelid = to_regclass('k_sol_mat_outputs') + ) THEN + ALTER TABLE k_sol_mat_outputs ADD CONSTRAINT ck_k_sol_mat_outputs_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_account_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_account_not_empty.sql new file mode 100644 index 0000000..625a2f0 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_account_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_account_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_account_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_account_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_account_not_empty CHECK (length(btrim(account_key)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_commitment_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_commitment_not_empty.sql new file mode 100644 index 0000000..d1d914c --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_commitment_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_commitment_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_commitment_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_commitment_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_commitment_not_empty CHECK (commitment IS NULL OR length(btrim(commitment)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_endpoint_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_endpoint_not_empty.sql new file mode 100644 index 0000000..754d944 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_endpoint_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_endpoint_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_endpoint_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_endpoint_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_endpoint_not_empty CHECK (endpoint_code IS NULL OR length(btrim(endpoint_code)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_code_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_code_not_empty.sql new file mode 100644 index 0000000..44e2963 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_code_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_code_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_error_code_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_error_code_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_error_code_not_empty CHECK (error_code IS NULL OR length(btrim(error_code)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_message_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_message_not_empty.sql new file mode 100644 index 0000000..148ad56 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_message_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_message_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_error_message_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_error_message_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_error_message_not_empty CHECK (error_message IS NULL OR length(btrim(error_message)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_filter_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_filter_not_empty.sql new file mode 100644 index 0000000..37f9808 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_filter_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_filter_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_filter_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_filter_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_filter_not_empty CHECK (filter_code IS NULL OR length(btrim(filter_code)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_hash_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_hash_not_empty.sql new file mode 100644 index 0000000..a73f942 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_hash_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_hash_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_hash_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_hash_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_hash_not_empty CHECK (data_hash IS NULL OR length(btrim(data_hash)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_key_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_key_not_empty.sql new file mode 100644 index 0000000..1e6d0c2 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_key_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_key_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_key_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_key_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_key_not_empty CHECK (length(btrim(observation_key)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_lamports_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_lamports_non_negative.sql new file mode 100644 index 0000000..02ee908 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_lamports_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_lamports_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_lamports_non_negative to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_lamports_non_negative' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_lamports_non_negative CHECK (lamports IS NULL OR lamports >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_method_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_method_not_empty.sql new file mode 100644 index 0000000..04904d9 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_method_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_method_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_method_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_method_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_method_not_empty CHECK (length(btrim(acquisition_method)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_origin.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_origin.sql new file mode 100644 index 0000000..59a4bc7 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_origin.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_origin.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_origin to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_origin' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_origin CHECK (origin IN ('live', 'backfill', 'replay', 'repair', 'migration')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_owner_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_owner_not_empty.sql new file mode 100644 index 0000000..292e265 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_owner_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_owner_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_owner_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_owner_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_owner_not_empty CHECK (owner IS NULL OR length(btrim(owner)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_payload_size_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_payload_size_non_negative.sql new file mode 100644 index 0000000..5c8e7c0 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_payload_size_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_payload_size_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_payload_size_non_negative to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_payload_size_non_negative' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_payload_size_non_negative CHECK (payload_size_bytes IS NULL OR payload_size_bytes >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_protocol_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_protocol_not_empty.sql new file mode 100644 index 0000000..3dc69fe --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_protocol_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_protocol_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_protocol_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_protocol_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_protocol_not_empty CHECK (length(btrim(protocol)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_provider_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_provider_not_empty.sql new file mode 100644 index 0000000..8cbc73b --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_provider_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_provider_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_provider_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_provider_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_provider_not_empty CHECK (length(btrim(provider)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_rent_epoch_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_rent_epoch_non_negative.sql new file mode 100644 index 0000000..60252b4 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_rent_epoch_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_rent_epoch_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_rent_epoch_non_negative to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_rent_epoch_non_negative' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_rent_epoch_non_negative CHECK (rent_epoch IS NULL OR rent_epoch >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_session_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_session_not_empty.sql new file mode 100644 index 0000000..2f93611 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_session_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_session_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_session_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_session_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_session_not_empty CHECK (capture_session_id IS NULL OR length(btrim(capture_session_id)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_slot_non_negative.sql new file mode 100644 index 0000000..266df88 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_slot_non_negative to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_slot_non_negative' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_slot_non_negative CHECK (context_slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_source_hash_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_source_hash_not_empty.sql new file mode 100644 index 0000000..694c253 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_source_hash_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_source_hash_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_source_hash_not_empty to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_source_hash_not_empty' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_source_hash_not_empty CHECK (source_payload_hash IS NULL OR length(btrim(source_payload_hash)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_space_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_space_non_negative.sql new file mode 100644 index 0000000..12c3555 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_space_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_space_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_space_non_negative to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_space_non_negative' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_space_non_negative CHECK (space IS NULL OR space >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_state_shape.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_state_shape.sql new file mode 100644 index 0000000..bae5b2a --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_state_shape.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_state_shape.sql +-- version: 1 + +-- Require a captured account state to be either complete or absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_state_shape' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_state_shape CHECK ((owner IS NULL AND lamports IS NULL AND executable IS NULL AND rent_epoch IS NULL AND space IS NULL AND data_base64 IS NULL AND data_hash IS NULL) OR (owner IS NOT NULL AND lamports IS NOT NULL AND executable IS NOT NULL AND rent_epoch IS NOT NULL AND space IS NOT NULL AND data_base64 IS NOT NULL AND data_hash IS NOT NULL)); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_status.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_status.sql new file mode 100644 index 0000000..c63b36d --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_status.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_status.sql +-- version: 1 + +-- Add ck_k_sol_obs_account_observations_status to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_account_observations_status' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT ck_k_sol_obs_account_observations_status CHECK (status IN ('detected', 'received', 'normalized', 'persisted', 'failed', 'missing')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_commitment_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_commitment_not_empty.sql new file mode 100644 index 0000000..d960427 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_commitment_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_commitment_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_commitment_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_commitment_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_commitment_not_empty CHECK (commitment IS NULL OR length(btrim(commitment)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_endpoint_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_endpoint_not_empty.sql new file mode 100644 index 0000000..9a6abef --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_endpoint_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_endpoint_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_endpoint_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_endpoint_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_endpoint_not_empty CHECK (endpoint_code IS NULL OR length(btrim(endpoint_code)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_code_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_code_not_empty.sql new file mode 100644 index 0000000..a060cbb --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_code_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_code_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_error_code_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_error_code_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_error_code_not_empty CHECK (error_code IS NULL OR length(btrim(error_code)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_message_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_message_not_empty.sql new file mode 100644 index 0000000..0e810c9 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_message_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_message_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_error_message_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_error_message_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_error_message_not_empty CHECK (error_message IS NULL OR length(btrim(error_message)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_filter_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_filter_not_empty.sql new file mode 100644 index 0000000..8f1c274 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_filter_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_filter_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_filter_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_filter_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_filter_not_empty CHECK (filter_code IS NULL OR length(btrim(filter_code)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_hash_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_hash_not_empty.sql new file mode 100644 index 0000000..f6bc074 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_hash_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_hash_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_hash_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_hash_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_hash_not_empty CHECK (source_payload_hash IS NULL OR length(btrim(source_payload_hash)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_key_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_key_not_empty.sql new file mode 100644 index 0000000..35c0912 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_key_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_key_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_key_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_key_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_key_not_empty CHECK (length(btrim(observation_key)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_method_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_method_not_empty.sql new file mode 100644 index 0000000..94fbd12 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_method_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_method_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_method_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_method_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_method_not_empty CHECK (length(btrim(acquisition_method)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_origin.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_origin.sql new file mode 100644 index 0000000..01a7493 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_origin.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_origin.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_origin to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_origin' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_origin CHECK (origin IN ('live', 'backfill', 'replay', 'repair', 'migration')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_payload_size_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_payload_size_non_negative.sql new file mode 100644 index 0000000..532fe00 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_payload_size_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_payload_size_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_payload_size_non_negative to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_payload_size_non_negative' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_payload_size_non_negative CHECK (payload_size_bytes IS NULL OR payload_size_bytes >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_protocol_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_protocol_not_empty.sql new file mode 100644 index 0000000..bacbcb6 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_protocol_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_protocol_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_protocol_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_protocol_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_protocol_not_empty CHECK (length(btrim(protocol)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_provider_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_provider_not_empty.sql new file mode 100644 index 0000000..c2ca939 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_provider_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_provider_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_provider_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_provider_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_provider_not_empty CHECK (length(btrim(provider)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_session_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_session_not_empty.sql new file mode 100644 index 0000000..9b5b377 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_session_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_session_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_session_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_session_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_session_not_empty CHECK (capture_session_id IS NULL OR length(btrim(capture_session_id)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_signature_not_empty.sql new file mode 100644 index 0000000..a96d713 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_signature_not_empty to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_signature_not_empty' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_signature_not_empty CHECK (signature IS NULL OR length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_slot_non_negative.sql new file mode 100644 index 0000000..8ed4250 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_slot_non_negative to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_slot_non_negative' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_slot_non_negative CHECK (slot IS NULL OR slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_status.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_status.sql new file mode 100644 index 0000000..3bf27ac --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_status.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_status.sql +-- version: 1 + +-- Add ck_k_sol_obs_transaction_observations_status to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_obs_transaction_observations_status' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT ck_k_sol_obs_transaction_observations_status CHECK (status IN ('detected', 'received', 'normalized', 'persisted', 'failed', 'missing')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_attempt_count_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_attempt_count_non_negative.sql new file mode 100644 index 0000000..d716b41 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_attempt_count_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_attempt_count_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_attempt_count_non_negative to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_attempt_count_non_negative' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_attempt_count_non_negative CHECK (attempt_count >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_code_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_code_not_empty.sql new file mode 100644 index 0000000..2af653d --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_code_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_code_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_error_code_not_empty to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_error_code_not_empty' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_error_code_not_empty CHECK (error_code IS NULL OR length(btrim(error_code)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_message_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_message_not_empty.sql new file mode 100644 index 0000000..e131edc --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_message_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_message_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_error_message_not_empty to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_error_message_not_empty' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_error_message_not_empty CHECK (error_message IS NULL OR length(btrim(error_message)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_hash_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_hash_not_empty.sql new file mode 100644 index 0000000..119d994 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_hash_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_hash_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_input_hash_not_empty to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_input_hash_not_empty' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_input_hash_not_empty CHECK (length(btrim(input_hash)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_key_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_key_not_empty.sql new file mode 100644 index 0000000..86cc15f --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_key_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_key_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_input_key_not_empty to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_input_key_not_empty' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_input_key_not_empty CHECK (length(btrim(input_key)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_name_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_name_not_empty.sql new file mode 100644 index 0000000..644858e --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_name_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_name_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_processor_name_not_empty to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_processor_name_not_empty' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_processor_name_not_empty CHECK (length(btrim(processor_name)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_version_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_version_not_empty.sql new file mode 100644 index 0000000..81ba4d7 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_version_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_version_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_processor_version_not_empty to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_processor_version_not_empty' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_processor_version_not_empty CHECK (length(btrim(processor_version)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_stage_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_stage_not_empty.sql new file mode 100644 index 0000000..d7a1ead --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_stage_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_stage_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_stage_not_empty to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_stage_not_empty' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_stage_not_empty CHECK (length(btrim(stage)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_status.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_status.sql new file mode 100644 index 0000000..c155bb8 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_status.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_status.sql +-- version: 1 + +-- Add ck_k_sol_ops_processing_ledger_status to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_ops_processing_ledger_status' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT ck_k_sol_ops_processing_ledger_status CHECK (status IN ('running', 'succeeded', 'failed')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_format_version_positive.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_format_version_positive.sql new file mode 100644 index 0000000..c75a3a7 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_format_version_positive.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_format_version_positive.sql +-- version: 1 + +-- Add ck_k_sol_raw_transactions_format_version_positive to k_sol_raw_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_raw_transactions_format_version_positive' + AND conrelid = to_regclass('k_sol_raw_transactions') + ) THEN + ALTER TABLE k_sol_raw_transactions ADD CONSTRAINT ck_k_sol_raw_transactions_format_version_positive CHECK (canonical_format_version > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_full_has_json.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_full_has_json.sql new file mode 100644 index 0000000..abc62c5 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_full_has_json.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_full_has_json.sql +-- version: 1 + +-- Add ck_k_sol_raw_transactions_full_has_json to k_sol_raw_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_raw_transactions_full_has_json' + AND conrelid = to_regclass('k_sol_raw_transactions') + ) THEN + ALTER TABLE k_sol_raw_transactions ADD CONSTRAINT ck_k_sol_raw_transactions_full_has_json CHECK (retention_state <> 'full' OR canonical_json IS NOT NULL); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_processing_state.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_processing_state.sql new file mode 100644 index 0000000..d423c7f --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_processing_state.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_processing_state.sql +-- version: 1 + +-- Add ck_k_sol_raw_transactions_processing_state to k_sol_raw_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_raw_transactions_processing_state' + AND conrelid = to_regclass('k_sol_raw_transactions') + ) THEN + ALTER TABLE k_sol_raw_transactions ADD CONSTRAINT ck_k_sol_raw_transactions_processing_state CHECK (processing_state IN ('received', 'core_extracted', 'decoded', 'materialized', 'failed')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_retention_state.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_retention_state.sql new file mode 100644 index 0000000..3af8fbc --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_retention_state.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_retention_state.sql +-- version: 1 + +-- Add ck_k_sol_raw_transactions_retention_state to k_sol_raw_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_raw_transactions_retention_state' + AND conrelid = to_regclass('k_sol_raw_transactions') + ) THEN + ALTER TABLE k_sol_raw_transactions ADD CONSTRAINT ck_k_sol_raw_transactions_retention_state CHECK (retention_state IN ('full', 'compacted', 'archived', 'purged')); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_signature_not_empty.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_signature_not_empty.sql new file mode 100644 index 0000000..6a6a207 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_signature_not_empty.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_signature_not_empty.sql +-- version: 1 + +-- Add ck_k_sol_raw_transactions_signature_not_empty to k_sol_raw_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_raw_transactions_signature_not_empty' + AND conrelid = to_regclass('k_sol_raw_transactions') + ) THEN + ALTER TABLE k_sol_raw_transactions ADD CONSTRAINT ck_k_sol_raw_transactions_signature_not_empty CHECK (length(btrim(signature)) > 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_slot_non_negative.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_slot_non_negative.sql new file mode 100644 index 0000000..707d26f --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_slot_non_negative.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_slot_non_negative.sql +-- version: 1 + +-- Add ck_k_sol_raw_transactions_slot_non_negative to k_sol_raw_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'ck_k_sol_raw_transactions_slot_non_negative' + AND conrelid = to_regclass('k_sol_raw_transactions') + ) THEN + ALTER TABLE k_sol_raw_transactions ADD CONSTRAINT ck_k_sol_raw_transactions_slot_non_negative CHECK (slot >= 0); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_keys_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_keys_transaction.sql new file mode 100644 index 0000000..74dea08 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_keys_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_keys_transaction.sql +-- version: 1 + +-- Add fk_k_sol_core_account_keys_transaction to k_sol_core_account_keys when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_account_keys_transaction' + AND conrelid = to_regclass('k_sol_core_account_keys') + ) THEN + ALTER TABLE k_sol_core_account_keys ADD CONSTRAINT fk_k_sol_core_account_keys_transaction FOREIGN KEY (transaction_id) REFERENCES k_sol_core_transactions(id) ON DELETE CASCADE; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_states_observation.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_states_observation.sql new file mode 100644 index 0000000..c7e5f79 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_states_observation.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_states_observation.sql +-- version: 1 + +-- Add fk_k_sol_core_account_states_observation to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_account_states_observation' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT fk_k_sol_core_account_states_observation FOREIGN KEY (source_observation_id) REFERENCES k_sol_obs_account_observations(id) ON DELETE CASCADE; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_balance_changes_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_balance_changes_transaction.sql new file mode 100644 index 0000000..b68ef0d --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_balance_changes_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_balance_changes_transaction.sql +-- version: 1 + +-- Add fk_k_sol_core_balance_changes_transaction to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_balance_changes_transaction' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT fk_k_sol_core_balance_changes_transaction FOREIGN KEY (transaction_id) REFERENCES k_sol_core_transactions(id) ON DELETE CASCADE; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_inner_instructions_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_inner_instructions_transaction.sql new file mode 100644 index 0000000..09254cb --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_inner_instructions_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_inner_instructions_transaction.sql +-- version: 1 + +-- Add fk_k_sol_core_inner_instructions_transaction to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_inner_instructions_transaction' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT fk_k_sol_core_inner_instructions_transaction FOREIGN KEY (transaction_id) REFERENCES k_sol_core_transactions(id) ON DELETE CASCADE; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_instructions_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_instructions_transaction.sql new file mode 100644 index 0000000..b017433 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_instructions_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_instructions_transaction.sql +-- version: 1 + +-- Add fk_k_sol_core_instructions_transaction to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_instructions_transaction' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT fk_k_sol_core_instructions_transaction FOREIGN KEY (transaction_id) REFERENCES k_sol_core_transactions(id) ON DELETE CASCADE; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_logs_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_logs_transaction.sql new file mode 100644 index 0000000..223af18 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_logs_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_logs_transaction.sql +-- version: 1 + +-- Add fk_k_sol_core_logs_transaction to k_sol_core_logs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_logs_transaction' + AND conrelid = to_regclass('k_sol_core_logs') + ) THEN + ALTER TABLE k_sol_core_logs ADD CONSTRAINT fk_k_sol_core_logs_transaction FOREIGN KEY (transaction_id) REFERENCES k_sol_core_transactions(id) ON DELETE CASCADE; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_return_data_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_return_data_transaction.sql new file mode 100644 index 0000000..9ff4d7e --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_return_data_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_return_data_transaction.sql +-- version: 1 + +-- Add fk_k_sol_core_return_data_transaction to k_sol_core_return_data when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_return_data_transaction' + AND conrelid = to_regclass('k_sol_core_return_data') + ) THEN + ALTER TABLE k_sol_core_return_data ADD CONSTRAINT fk_k_sol_core_return_data_transaction FOREIGN KEY (transaction_id) REFERENCES k_sol_core_transactions(id) ON DELETE CASCADE; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_transactions_raw_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_transactions_raw_transaction.sql new file mode 100644 index 0000000..d6ce043 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_transactions_raw_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_transactions_raw_transaction.sql +-- version: 1 + +-- Add fk_k_sol_core_transactions_raw_transaction to k_sol_core_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_core_transactions_raw_transaction' + AND conrelid = to_regclass('k_sol_core_transactions') + ) THEN + ALTER TABLE k_sol_core_transactions ADD CONSTRAINT fk_k_sol_core_transactions_raw_transaction FOREIGN KEY (raw_transaction_id) REFERENCES k_sol_raw_transactions(id) ON DELETE SET NULL; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_obs_transaction_observations_raw_transaction.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_obs_transaction_observations_raw_transaction.sql new file mode 100644 index 0000000..fe046ab --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_obs_transaction_observations_raw_transaction.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_obs_transaction_observations_raw_transaction.sql +-- version: 1 + +-- Add fk_k_sol_obs_transaction_observations_raw_transaction to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'fk_k_sol_obs_transaction_observations_raw_transaction' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT fk_k_sol_obs_transaction_observations_raw_transaction FOREIGN KEY (raw_transaction_id) REFERENCES k_sol_raw_transactions(id) ON DELETE SET NULL; + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_keys.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_keys.sql new file mode 100644 index 0000000..2f7376e --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_keys.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_keys.sql +-- version: 1 + +-- Add pk_k_sol_core_account_keys to k_sol_core_account_keys when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_account_keys' + AND conrelid = to_regclass('k_sol_core_account_keys') + ) THEN + ALTER TABLE k_sol_core_account_keys ADD CONSTRAINT pk_k_sol_core_account_keys PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_states.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_states.sql new file mode 100644 index 0000000..037231a --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_states.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_states.sql +-- version: 1 + +-- Add pk_k_sol_core_account_states to k_sol_core_account_states when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_account_states' + AND conrelid = to_regclass('k_sol_core_account_states') + ) THEN + ALTER TABLE k_sol_core_account_states ADD CONSTRAINT pk_k_sol_core_account_states PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_balance_changes.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_balance_changes.sql new file mode 100644 index 0000000..c9c7303 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_balance_changes.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_balance_changes.sql +-- version: 1 + +-- Add pk_k_sol_core_balance_changes to k_sol_core_balance_changes when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_balance_changes' + AND conrelid = to_regclass('k_sol_core_balance_changes') + ) THEN + ALTER TABLE k_sol_core_balance_changes ADD CONSTRAINT pk_k_sol_core_balance_changes PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_inner_instructions.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_inner_instructions.sql new file mode 100644 index 0000000..569a5cc --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_inner_instructions.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_inner_instructions.sql +-- version: 1 + +-- Add pk_k_sol_core_inner_instructions to k_sol_core_inner_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_inner_instructions' + AND conrelid = to_regclass('k_sol_core_inner_instructions') + ) THEN + ALTER TABLE k_sol_core_inner_instructions ADD CONSTRAINT pk_k_sol_core_inner_instructions PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_instructions.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_instructions.sql new file mode 100644 index 0000000..1a59c05 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_instructions.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_instructions.sql +-- version: 1 + +-- Add pk_k_sol_core_instructions to k_sol_core_instructions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_instructions' + AND conrelid = to_regclass('k_sol_core_instructions') + ) THEN + ALTER TABLE k_sol_core_instructions ADD CONSTRAINT pk_k_sol_core_instructions PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_logs.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_logs.sql new file mode 100644 index 0000000..a37bd7e --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_logs.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_logs.sql +-- version: 1 + +-- Add pk_k_sol_core_logs to k_sol_core_logs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_logs' + AND conrelid = to_regclass('k_sol_core_logs') + ) THEN + ALTER TABLE k_sol_core_logs ADD CONSTRAINT pk_k_sol_core_logs PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_return_data.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_return_data.sql new file mode 100644 index 0000000..fea5379 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_return_data.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_return_data.sql +-- version: 1 + +-- Add pk_k_sol_core_return_data to k_sol_core_return_data when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_return_data' + AND conrelid = to_regclass('k_sol_core_return_data') + ) THEN + ALTER TABLE k_sol_core_return_data ADD CONSTRAINT pk_k_sol_core_return_data PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_transactions.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_transactions.sql new file mode 100644 index 0000000..5b0bca2 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_transactions.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_transactions.sql +-- version: 1 + +-- Add pk_k_sol_core_transactions to k_sol_core_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_core_transactions' + AND conrelid = to_regclass('k_sol_core_transactions') + ) THEN + ALTER TABLE k_sol_core_transactions ADD CONSTRAINT pk_k_sol_core_transactions PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_declarations.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_declarations.sql new file mode 100644 index 0000000..5fd2a3a --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_declarations.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_declarations.sql +-- version: 1 + +-- Add pk_k_sol_decode_coverage_declarations to k_sol_decode_coverage_declarations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_decode_coverage_declarations' + AND conrelid = to_regclass('k_sol_decode_coverage_declarations') + ) THEN + ALTER TABLE k_sol_decode_coverage_declarations ADD CONSTRAINT pk_k_sol_decode_coverage_declarations PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_observations.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_observations.sql new file mode 100644 index 0000000..2535695 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_observations.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_observations.sql +-- version: 1 + +-- Add pk_k_sol_decode_coverage_observations to k_sol_decode_coverage_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_decode_coverage_observations' + AND conrelid = to_regclass('k_sol_decode_coverage_observations') + ) THEN + ALTER TABLE k_sol_decode_coverage_observations ADD CONSTRAINT pk_k_sol_decode_coverage_observations PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_events.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_events.sql new file mode 100644 index 0000000..5c40758 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_events.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_events.sql +-- version: 1 + +-- Add pk_k_sol_decode_events to k_sol_decode_events when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_decode_events' + AND conrelid = to_regclass('k_sol_decode_events') + ) THEN + ALTER TABLE k_sol_decode_events ADD CONSTRAINT pk_k_sol_decode_events PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_mat_outputs.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_mat_outputs.sql new file mode 100644 index 0000000..5fb878a --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_mat_outputs.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_mat_outputs.sql +-- version: 1 + +-- Add pk_k_sol_mat_outputs to k_sol_mat_outputs when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_mat_outputs' + AND conrelid = to_regclass('k_sol_mat_outputs') + ) THEN + ALTER TABLE k_sol_mat_outputs ADD CONSTRAINT pk_k_sol_mat_outputs PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_account_observations.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_account_observations.sql new file mode 100644 index 0000000..e0abcc5 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_account_observations.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_account_observations.sql +-- version: 1 + +-- Add pk_k_sol_obs_account_observations to k_sol_obs_account_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_obs_account_observations' + AND conrelid = to_regclass('k_sol_obs_account_observations') + ) THEN + ALTER TABLE k_sol_obs_account_observations ADD CONSTRAINT pk_k_sol_obs_account_observations PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_transaction_observations.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_transaction_observations.sql new file mode 100644 index 0000000..a68046e --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_transaction_observations.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_transaction_observations.sql +-- version: 1 + +-- Add pk_k_sol_obs_transaction_observations to k_sol_obs_transaction_observations when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_obs_transaction_observations' + AND conrelid = to_regclass('k_sol_obs_transaction_observations') + ) THEN + ALTER TABLE k_sol_obs_transaction_observations ADD CONSTRAINT pk_k_sol_obs_transaction_observations PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_ops_processing_ledger.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_ops_processing_ledger.sql new file mode 100644 index 0000000..40e03b0 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_ops_processing_ledger.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_ops_processing_ledger.sql +-- version: 1 + +-- Add pk_k_sol_ops_processing_ledger to k_sol_ops_processing_ledger when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_ops_processing_ledger' + AND conrelid = to_regclass('k_sol_ops_processing_ledger') + ) THEN + ALTER TABLE k_sol_ops_processing_ledger ADD CONSTRAINT pk_k_sol_ops_processing_ledger PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_raw_transactions.sql b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_raw_transactions.sql new file mode 100644 index 0000000..ed81fa4 --- /dev/null +++ b/ks-store/migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_raw_transactions.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_raw_transactions.sql +-- version: 1 + +-- Add pk_k_sol_raw_transactions to k_sol_raw_transactions when absent. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pk_k_sol_raw_transactions' + AND conrelid = to_regclass('k_sol_raw_transactions') + ) THEN + ALTER TABLE k_sol_raw_transactions ADD CONSTRAINT pk_k_sol_raw_transactions PRIMARY KEY (id); + END IF; +END +$$; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_keys_key.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_keys_key.sql new file mode 100644 index 0000000..db28c30 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_keys_key.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_keys_key.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_account_keys_key when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_account_keys_key ON k_sol_core_account_keys (account_key); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_account_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_account_slot.sql new file mode 100644 index 0000000..b561f17 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_account_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_account_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_account_states_account_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_account_states_account_slot ON k_sol_core_account_states (account_key, slot DESC, id DESC); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_owner.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_owner.sql new file mode 100644 index 0000000..2a72b32 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_owner.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_owner.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_account_states_owner when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_account_states_owner ON k_sol_core_account_states (owner, slot DESC, id DESC); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_account.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_account.sql new file mode 100644 index 0000000..4e263f0 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_account.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_account.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_balance_changes_account when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_balance_changes_account ON k_sol_core_balance_changes (account_key) WHERE account_key IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_mint.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_mint.sql new file mode 100644 index 0000000..21ef8d2 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_mint.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_mint.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_balance_changes_mint when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_balance_changes_mint ON k_sol_core_balance_changes (mint) WHERE mint IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_owner.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_owner.sql new file mode 100644 index 0000000..eb3d4ec --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_owner.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_owner.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_balance_changes_owner when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_balance_changes_owner ON k_sol_core_balance_changes (owner) WHERE owner IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_parent.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_parent.sql new file mode 100644 index 0000000..bceb0d8 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_parent.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_parent.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_inner_instructions_parent when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_inner_instructions_parent ON k_sol_core_inner_instructions (signature, parent_instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_processing.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_processing.sql new file mode 100644 index 0000000..ecca886 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_processing.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_processing.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_inner_instructions_processing when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_inner_instructions_processing ON k_sol_core_inner_instructions (processing_state, slot, signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_program.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_program.sql new file mode 100644 index 0000000..5cd9c77 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_program.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_program.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_inner_instructions_program when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_inner_instructions_program ON k_sol_core_inner_instructions (program_id, slot, signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_processing.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_processing.sql new file mode 100644 index 0000000..bfd161e --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_processing.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_processing.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_instructions_processing when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_instructions_processing ON k_sol_core_instructions (processing_state, slot, signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_program.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_program.sql new file mode 100644 index 0000000..9bdae2e --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_program.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_program.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_instructions_program when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_instructions_program ON k_sol_core_instructions (program_id, slot, signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_slot.sql new file mode 100644 index 0000000..bc57b2e --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_instructions_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_instructions_slot ON k_sol_core_instructions (slot, signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_path.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_path.sql new file mode 100644 index 0000000..5fd6203 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_path.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_path.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_logs_path when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_logs_path ON k_sol_core_logs (signature, instruction_path) WHERE instruction_path IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_program.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_program.sql new file mode 100644 index 0000000..228f29d --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_program.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_program.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_logs_program when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_logs_program ON k_sol_core_logs (program_id) WHERE program_id IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_return_data_program.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_return_data_program.sql new file mode 100644 index 0000000..1380e5b --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_return_data_program.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_return_data_program.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_return_data_program when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_return_data_program ON k_sol_core_return_data (program_id, slot, signature); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_created_at.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_created_at.sql new file mode 100644 index 0000000..bd1bd2c --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_created_at.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_created_at.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_transactions_created_at when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_transactions_created_at ON k_sol_core_transactions (created_at); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_slot.sql new file mode 100644 index 0000000..8d09b64 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_core_transactions_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_core_transactions_slot ON k_sol_core_transactions (slot); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_declarations_program.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_declarations_program.sql new file mode 100644 index 0000000..bb5f76d --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_declarations_program.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_declarations_program.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_decode_coverage_declarations_program when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_decode_coverage_declarations_program ON k_sol_decode_coverage_declarations (program_id, processor_name, processor_version); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_entry.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_entry.sql new file mode 100644 index 0000000..c4a74f7 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_entry.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_entry.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_decode_coverage_observations_entry when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_decode_coverage_observations_entry ON k_sol_decode_coverage_observations (processor_name, processor_version, surface_code, entry_code); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_program_status.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_program_status.sql new file mode 100644 index 0000000..62c455b --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_program_status.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_program_status.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_decode_coverage_observations_program_status when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_decode_coverage_observations_program_status ON k_sol_decode_coverage_observations (program_id, status, transaction_failed); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_family_commit.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_family_commit.sql new file mode 100644 index 0000000..0c6c8c6 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_family_commit.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_family_commit.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_decode_events_family_commit when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_decode_events_family_commit ON k_sol_decode_events (event_family, transaction_failed, observation_committed); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_program_surface.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_program_surface.sql new file mode 100644 index 0000000..d976bbe --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_program_surface.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_program_surface.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_decode_events_program_surface when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_decode_events_program_surface ON k_sol_decode_events (program_id, surface_code, event_code); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_signature_path.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_signature_path.sql new file mode 100644 index 0000000..c6ecc1e --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_signature_path.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_signature_path.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_decode_events_signature_path when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_decode_events_signature_path ON k_sol_decode_events (signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_processor_family_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_processor_family_slot.sql new file mode 100644 index 0000000..f94c406 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_processor_family_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_processor_family_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_mat_outputs_processor_family_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_mat_outputs_processor_family_slot ON k_sol_mat_outputs (processor_name, materialized_family, slot DESC, id DESC); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_signature_family.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_signature_family.sql new file mode 100644 index 0000000..0d06a41 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_signature_family.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_signature_family.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_mat_outputs_signature_family when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_mat_outputs_signature_family ON k_sol_mat_outputs (source_decoder_name, source_decoder_version, source_decode_input_key, signature, materialized_family); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_account_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_account_slot.sql new file mode 100644 index 0000000..5531b76 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_account_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_account_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_account_observations_account_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_account_observations_account_slot ON k_sol_obs_account_observations (account_key, context_slot DESC, id DESC); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_provider_method.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_provider_method.sql new file mode 100644 index 0000000..658d337 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_provider_method.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_provider_method.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_account_observations_provider_method when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_account_observations_provider_method ON k_sol_obs_account_observations (provider, acquisition_method); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_received_at.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_received_at.sql new file mode 100644 index 0000000..f8add2a --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_received_at.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_received_at.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_account_observations_received_at when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_account_observations_received_at ON k_sol_obs_account_observations (received_at); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_status.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_status.sql new file mode 100644 index 0000000..8091ed9 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_status.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_status.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_account_observations_status when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_account_observations_status ON k_sol_obs_account_observations (status); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_provider_method.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_provider_method.sql new file mode 100644 index 0000000..5ffb79a --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_provider_method.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_provider_method.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_transaction_observations_provider_method when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_transaction_observations_provider_method ON k_sol_obs_transaction_observations (provider, acquisition_method); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_raw_transaction.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_raw_transaction.sql new file mode 100644 index 0000000..447d1d7 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_raw_transaction.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_raw_transaction.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_transaction_observations_raw_transaction when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_transaction_observations_raw_transaction ON k_sol_obs_transaction_observations (raw_transaction_id) WHERE raw_transaction_id IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_received_at.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_received_at.sql new file mode 100644 index 0000000..87482b9 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_received_at.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_received_at.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_transaction_observations_received_at when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_transaction_observations_received_at ON k_sol_obs_transaction_observations (received_at); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_signature.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_signature.sql new file mode 100644 index 0000000..a547872 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_signature.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_signature.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_transaction_observations_signature when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_transaction_observations_signature ON k_sol_obs_transaction_observations (signature) WHERE signature IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_slot.sql new file mode 100644 index 0000000..0cf867a --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_transaction_observations_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_transaction_observations_slot ON k_sol_obs_transaction_observations (slot) WHERE slot IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_status.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_status.sql new file mode 100644 index 0000000..60c9e5a --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_status.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_status.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_obs_transaction_observations_status when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_obs_transaction_observations_status ON k_sol_obs_transaction_observations (status); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_input_hash.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_input_hash.sql new file mode 100644 index 0000000..a37a9da --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_input_hash.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_input_hash.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_ops_processing_ledger_input_hash when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_ops_processing_ledger_input_hash ON k_sol_ops_processing_ledger (input_hash); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_stage_input.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_stage_input.sql new file mode 100644 index 0000000..57de282 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_stage_input.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_stage_input.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_ops_processing_ledger_stage_input when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_ops_processing_ledger_stage_input ON k_sol_ops_processing_ledger (stage, input_key, updated_at DESC, id DESC); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_status.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_status.sql new file mode 100644 index 0000000..dc95746 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_status.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_status.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_ops_processing_ledger_status when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_ops_processing_ledger_status ON k_sol_ops_processing_ledger (stage, processor_name, status, updated_at); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_canonical_hash.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_canonical_hash.sql new file mode 100644 index 0000000..54d13a5 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_canonical_hash.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_canonical_hash.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_raw_transactions_canonical_hash when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_raw_transactions_canonical_hash ON k_sol_raw_transactions (canonical_json_hash) WHERE canonical_json_hash IS NOT NULL; diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_created_at.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_created_at.sql new file mode 100644 index 0000000..40d528c --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_created_at.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_created_at.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_raw_transactions_created_at when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_raw_transactions_created_at ON k_sol_raw_transactions (created_at); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing.sql new file mode 100644 index 0000000..851d4b3 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_raw_transactions_processing when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_raw_transactions_processing ON k_sol_raw_transactions (processing_state); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing_slot.sql new file mode 100644 index 0000000..70c6caf --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_raw_transactions_processing_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_raw_transactions_processing_slot ON k_sol_raw_transactions (processing_state, slot, signature); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_slot.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_slot.sql new file mode 100644 index 0000000..ee536fb --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_slot.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_slot.sql +-- version: 1 + +-- Create PostgreSQL index ix_k_sol_raw_transactions_slot when absent. +CREATE INDEX IF NOT EXISTS ix_k_sol_raw_transactions_slot ON k_sol_raw_transactions (slot); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_keys_sig_index.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_keys_sig_index.sql new file mode 100644 index 0000000..110cc7c --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_keys_sig_index.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_keys_sig_index.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_account_keys_sig_index when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_account_keys_sig_index ON k_sol_core_account_keys (signature, account_index); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_states_observation.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_states_observation.sql new file mode 100644 index 0000000..f8ec604 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_states_observation.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_states_observation.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_account_states_observation when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_account_states_observation ON k_sol_core_account_states (source_observation_id); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_balance_changes_sig_index.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_balance_changes_sig_index.sql new file mode 100644 index 0000000..aa51740 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_balance_changes_sig_index.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_balance_changes_sig_index.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_balance_changes_sig_index when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_balance_changes_sig_index ON k_sol_core_balance_changes (signature, balance_change_index); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_inner_instructions_sig_path.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_inner_instructions_sig_path.sql new file mode 100644 index 0000000..703f2ff --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_inner_instructions_sig_path.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_inner_instructions_sig_path.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_inner_instructions_sig_path when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_inner_instructions_sig_path ON k_sol_core_inner_instructions (signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_instructions_sig_path.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_instructions_sig_path.sql new file mode 100644 index 0000000..d95694a --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_instructions_sig_path.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_instructions_sig_path.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_instructions_sig_path when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_instructions_sig_path ON k_sol_core_instructions (signature, instruction_path); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_logs_sig_index.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_logs_sig_index.sql new file mode 100644 index 0000000..81384c4 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_logs_sig_index.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_logs_sig_index.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_logs_sig_index when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_logs_sig_index ON k_sol_core_logs (signature, log_index); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_return_data_signature.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_return_data_signature.sql new file mode 100644 index 0000000..47686b0 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_return_data_signature.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_return_data_signature.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_return_data_signature when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_return_data_signature ON k_sol_core_return_data (signature); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_transactions_signature.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_transactions_signature.sql new file mode 100644 index 0000000..2bc66e5 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_transactions_signature.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_transactions_signature.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_core_transactions_signature when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_core_transactions_signature ON k_sol_core_transactions (signature); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_declarations_identity.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_declarations_identity.sql new file mode 100644 index 0000000..8f47e71 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_declarations_identity.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_declarations_identity.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_decode_coverage_declarations_identity when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_decode_coverage_declarations_identity ON k_sol_decode_coverage_declarations (processor_name, processor_version, program_id, COALESCE(surface_code, ''), entry_kind, entry_code, COALESCE(discriminator_hex, '')); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_observations_identity.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_observations_identity.sql new file mode 100644 index 0000000..77b3509 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_observations_identity.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_observations_identity.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_decode_coverage_observations_identity when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_decode_coverage_observations_identity ON k_sol_decode_coverage_observations (processor_name, processor_version, input_key); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_events_processor_input_event.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_events_processor_input_event.sql new file mode 100644 index 0000000..c2c9758 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_events_processor_input_event.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_events_processor_input_event.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_decode_events_processor_input_event when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_decode_events_processor_input_event ON k_sol_decode_events (processor_name, processor_version, input_key, event_key); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_mat_outputs_processor_input_output.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_mat_outputs_processor_input_output.sql new file mode 100644 index 0000000..765a6a4 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_mat_outputs_processor_input_output.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_mat_outputs_processor_input_output.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_mat_outputs_processor_input_output when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_mat_outputs_processor_input_output ON k_sol_mat_outputs (processor_name, processor_version, input_key, output_key); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_account_observations_key.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_account_observations_key.sql new file mode 100644 index 0000000..9fd845b --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_account_observations_key.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_account_observations_key.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_obs_account_observations_key when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_obs_account_observations_key ON k_sol_obs_account_observations (observation_key); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_transaction_observations_key.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_transaction_observations_key.sql new file mode 100644 index 0000000..cbc8208 --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_transaction_observations_key.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_transaction_observations_key.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_obs_transaction_observations_key when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_obs_transaction_observations_key ON k_sol_obs_transaction_observations (observation_key); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_ops_processing_ledger_identity.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_ops_processing_ledger_identity.sql new file mode 100644 index 0000000..4fcfe7b --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_ops_processing_ledger_identity.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_ops_processing_ledger_identity.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_ops_processing_ledger_identity when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_ops_processing_ledger_identity ON k_sol_ops_processing_ledger (stage, processor_name, processor_version, input_key); diff --git a/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_raw_transactions_signature.sql b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_raw_transactions_signature.sql new file mode 100644 index 0000000..6e471eb --- /dev/null +++ b/ks-store/migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_raw_transactions_signature.sql @@ -0,0 +1,5 @@ +-- file: migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_raw_transactions_signature.sql +-- version: 1 + +-- Create PostgreSQL index ux_k_sol_raw_transactions_signature when absent. +CREATE UNIQUE INDEX IF NOT EXISTS ux_k_sol_raw_transactions_signature ON k_sol_raw_transactions (signature); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_keys.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_keys.sql new file mode 100644 index 0000000..f539674 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_keys.sql @@ -0,0 +1,17 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_keys.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_account_keys table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_account_keys ( + id BIGSERIAL, + transaction_id BIGINT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + account_index INTEGER NOT NULL, + account_key TEXT NOT NULL, + source TEXT NOT NULL, + writable BOOLEAN NOT NULL, + signer BOOLEAN NOT NULL, + executable BOOLEAN, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_states.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_states.sql new file mode 100644 index 0000000..207a410 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_states.sql @@ -0,0 +1,18 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_states.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_account_states table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_account_states ( + id BIGSERIAL, + source_observation_id BIGINT NOT NULL, + account_key TEXT NOT NULL, + slot BIGINT NOT NULL, + owner TEXT NOT NULL, + lamports NUMERIC(20,0) NOT NULL, + executable BOOLEAN NOT NULL, + rent_epoch NUMERIC(20,0) NOT NULL, + space BIGINT NOT NULL, + data_base64 TEXT NOT NULL, + data_hash TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_balance_changes.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_balance_changes.sql new file mode 100644 index 0000000..025be45 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_balance_changes.sql @@ -0,0 +1,20 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_balance_changes.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_balance_changes table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_balance_changes ( + id BIGSERIAL, + transaction_id BIGINT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + balance_change_index INTEGER NOT NULL, + balance_kind TEXT NOT NULL, + account_index INTEGER, + account_key TEXT, + mint TEXT, + owner TEXT, + pre_balance_json JSONB, + post_balance_json JSONB, + delta_json JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_inner_instructions.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_inner_instructions.sql new file mode 100644 index 0000000..f562be1 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_inner_instructions.sql @@ -0,0 +1,23 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_inner_instructions.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_inner_instructions table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_inner_instructions ( + id BIGSERIAL, + transaction_id BIGINT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + parent_instruction_path TEXT NOT NULL, + instruction_path TEXT NOT NULL, + program_id TEXT NOT NULL, + stack_height INTEGER, + accounts_json JSONB NOT NULL, + payload_json JSONB, + payload_json_hash TEXT, + processing_state TEXT NOT NULL DEFAULT 'pending', + processor_name TEXT, + processor_version TEXT, + lifecycle_reason TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_instructions.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_instructions.sql new file mode 100644 index 0000000..d4923b5 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_instructions.sql @@ -0,0 +1,22 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_instructions.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_instructions table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_instructions ( + id BIGSERIAL, + transaction_id BIGINT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + instruction_path TEXT NOT NULL, + program_id TEXT NOT NULL, + stack_height INTEGER, + accounts_json JSONB NOT NULL, + payload_json JSONB, + payload_json_hash TEXT, + processing_state TEXT NOT NULL DEFAULT 'pending', + processor_name TEXT, + processor_version TEXT, + lifecycle_reason TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_logs.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_logs.sql new file mode 100644 index 0000000..31c46e4 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_logs.sql @@ -0,0 +1,16 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_logs.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_logs table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_logs ( + id BIGSERIAL, + transaction_id BIGINT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + log_index INTEGER NOT NULL, + instruction_path TEXT, + program_id TEXT, + log_text TEXT, + log_text_hash TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_return_data.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_return_data.sql new file mode 100644 index 0000000..1bd1ca9 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_return_data.sql @@ -0,0 +1,13 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_return_data.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_return_data table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_return_data ( + id BIGSERIAL, + transaction_id BIGINT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + program_id TEXT NOT NULL, + data_base64 TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_transactions.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_transactions.sql new file mode 100644 index 0000000..fdef369 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_transactions.sql @@ -0,0 +1,15 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_transactions.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_core_transactions table when absent. +CREATE TABLE IF NOT EXISTS k_sol_core_transactions ( + id BIGSERIAL, + raw_transaction_id BIGINT, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + block_time BIGINT, + failed BOOLEAN NOT NULL DEFAULT FALSE, + err_json JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_declarations.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_declarations.sql new file mode 100644 index 0000000..69e531c --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_declarations.sql @@ -0,0 +1,17 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_declarations.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_decode_coverage_declarations table when absent. +CREATE TABLE IF NOT EXISTS k_sol_decode_coverage_declarations ( + id BIGSERIAL, + processor_name TEXT NOT NULL, + processor_version TEXT NOT NULL, + program_id TEXT NOT NULL, + surface_code TEXT, + entry_kind TEXT NOT NULL, + entry_code TEXT NOT NULL, + discriminator_hex TEXT, + historical BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_observations.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_observations.sql new file mode 100644 index 0000000..5d97981 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_observations.sql @@ -0,0 +1,26 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_observations.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_decode_coverage_observations table when absent. +CREATE TABLE IF NOT EXISTS k_sol_decode_coverage_observations ( + id BIGSERIAL, + processor_name TEXT NOT NULL, + processor_version TEXT NOT NULL, + input_key TEXT NOT NULL, + input_hash TEXT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + instruction_path TEXT NOT NULL, + program_id TEXT NOT NULL, + surface_code TEXT, + entry_code TEXT, + discriminator_hex TEXT, + status TEXT NOT NULL, + recognized BOOLEAN NOT NULL, + decoded_count INTEGER NOT NULL DEFAULT 0, + materialized_count INTEGER NOT NULL DEFAULT 0, + error_count INTEGER NOT NULL DEFAULT 0, + transaction_failed BOOLEAN NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_events.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_events.sql new file mode 100644 index 0000000..39775f8 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_events.sql @@ -0,0 +1,35 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_events.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_decode_events table when absent. +CREATE TABLE IF NOT EXISTS k_sol_decode_events ( + id BIGSERIAL, + processor_name TEXT NOT NULL, + processor_version TEXT NOT NULL, + input_key TEXT NOT NULL, + input_hash TEXT NOT NULL, + event_key TEXT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + instruction_path TEXT NOT NULL, + program_id TEXT NOT NULL, + protocol_code TEXT NOT NULL, + surface_code TEXT NOT NULL, + event_code TEXT NOT NULL, + event_name TEXT NOT NULL, + event_family TEXT NOT NULL, + source_kind TEXT NOT NULL, + confidence TEXT NOT NULL, + proof_kind TEXT NOT NULL, + proof_jsonb JSONB NOT NULL, + payload_jsonb JSONB NOT NULL, + schema_kind TEXT, + schema_id TEXT, + schema_version TEXT, + schema_hash TEXT, + transaction_failed BOOLEAN NOT NULL, + transaction_error_jsonb JSONB, + observation_committed BOOLEAN NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_mat_outputs.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_mat_outputs.sql new file mode 100644 index 0000000..22581ad --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_mat_outputs.sql @@ -0,0 +1,22 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_mat_outputs.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_mat_outputs table when absent. +CREATE TABLE IF NOT EXISTS k_sol_mat_outputs ( + id BIGSERIAL, + processor_name TEXT NOT NULL, + processor_version TEXT NOT NULL, + input_key TEXT NOT NULL, + input_hash TEXT NOT NULL, + output_key TEXT NOT NULL, + source_event_key TEXT NOT NULL, + source_decoder_name TEXT NOT NULL, + source_decoder_version TEXT NOT NULL, + source_decode_input_key TEXT NOT NULL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + materialized_family TEXT NOT NULL, + payload_jsonb JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_account_observations.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_account_observations.sql new file mode 100644 index 0000000..74b819e --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_account_observations.sql @@ -0,0 +1,34 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_account_observations.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_obs_account_observations table when absent. +CREATE TABLE IF NOT EXISTS k_sol_obs_account_observations ( + id BIGSERIAL, + observation_key TEXT NOT NULL, + account_key TEXT NOT NULL, + context_slot BIGINT NOT NULL, + owner TEXT, + lamports NUMERIC(20,0), + executable BOOLEAN, + rent_epoch NUMERIC(20,0), + space BIGINT, + data_base64 TEXT, + data_hash TEXT, + provider TEXT NOT NULL, + endpoint_code TEXT, + protocol TEXT NOT NULL, + acquisition_method TEXT NOT NULL, + commitment TEXT, + capture_session_id TEXT, + filter_code TEXT, + origin TEXT NOT NULL, + detected_at TIMESTAMPTZ, + received_at TIMESTAMPTZ NOT NULL, + normalized_at TIMESTAMPTZ, + persisted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + payload_size_bytes BIGINT, + source_payload_hash TEXT, + status TEXT NOT NULL DEFAULT 'received', + error_code TEXT, + error_message TEXT +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_transaction_observations.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_transaction_observations.sql new file mode 100644 index 0000000..9e92ef7 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_transaction_observations.sql @@ -0,0 +1,28 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_transaction_observations.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_obs_transaction_observations table when absent. +CREATE TABLE IF NOT EXISTS k_sol_obs_transaction_observations ( + id BIGSERIAL, + raw_transaction_id BIGINT, + observation_key TEXT NOT NULL, + signature TEXT, + slot BIGINT, + provider TEXT NOT NULL, + endpoint_code TEXT, + protocol TEXT NOT NULL, + acquisition_method TEXT NOT NULL, + origin TEXT NOT NULL, + commitment TEXT, + capture_session_id TEXT, + filter_code TEXT, + detected_at TIMESTAMPTZ, + received_at TIMESTAMPTZ NOT NULL, + normalized_at TIMESTAMPTZ, + persisted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + payload_size_bytes BIGINT, + source_payload_hash TEXT, + status TEXT NOT NULL DEFAULT 'received', + error_code TEXT, + error_message TEXT +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_ops_processing_ledger.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_ops_processing_ledger.sql new file mode 100644 index 0000000..0c8aa1c --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_ops_processing_ledger.sql @@ -0,0 +1,20 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_ops_processing_ledger.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_ops_processing_ledger table when absent. +CREATE TABLE IF NOT EXISTS k_sol_ops_processing_ledger ( + id BIGSERIAL, + stage TEXT NOT NULL, + processor_name TEXT NOT NULL, + processor_version TEXT NOT NULL, + input_key TEXT NOT NULL, + input_hash TEXT NOT NULL, + status TEXT NOT NULL, + attempt_count INTEGER NOT NULL DEFAULT 0, + started_at TIMESTAMPTZ, + finished_at TIMESTAMPTZ, + error_code TEXT, + error_message TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_raw_transactions.sql b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_raw_transactions.sql new file mode 100644 index 0000000..2317003 --- /dev/null +++ b/ks-store/migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_raw_transactions.sql @@ -0,0 +1,18 @@ +-- file: migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_raw_transactions.sql +-- version: 1 + +-- Create the PostgreSQL k_sol_raw_transactions table when absent. +CREATE TABLE IF NOT EXISTS k_sol_raw_transactions ( + id BIGSERIAL, + signature TEXT NOT NULL, + slot BIGINT NOT NULL, + block_time BIGINT, + canonical_json JSONB, + canonical_json_hash TEXT, + canonical_format_version INTEGER NOT NULL DEFAULT 1, + retention_state TEXT NOT NULL DEFAULT 'full', + processing_state TEXT NOT NULL DEFAULT 'received', + lifecycle_reason TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/ks-store/src/constants.rs b/ks-store/src/constants.rs index f8d0b43..615231d 100644 --- a/ks-store/src/constants.rs +++ b/ks-store/src/constants.rs @@ -1,9 +1,9 @@ // file: ks-store/src/constants.rs -// version: 5 +// version: 6 //! Local constants for the `ks-store` crate. /// Canonical tracing target for backend-independent storage operations. pub(crate) const TRACING_TARGET: &str = "ks-store"; -/// Transitional schema contract identifier used until the `pre.003` baseline rebuild. -pub const STORE_SCHEMA_CONTRACT_VERSION: &str = "0.5.3-pre.2-legacy-schema"; +/// Candidate N1-N3 store schema contract identifier established during `0.5.3`. +pub const STORE_SCHEMA_CONTRACT_VERSION: &str = "0.5.3"; diff --git a/ks-store/src/contracts.rs b/ks-store/src/contracts.rs index 85bcf6f..e7ca5f0 100644 --- a/ks-store/src/contracts.rs +++ b/ks-store/src/contracts.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts.rs -// version: 6 +// version: 7 //! Backend-neutral storage contracts used by pipeline crates. @@ -11,10 +11,18 @@ mod pagination; mod replay; mod repository; +/// Generic account acquisition observation insert contract. +pub use self::dto::AccountObservationInsert; +/// Transaction acquisition observation origin. +pub use self::dto::AcquisitionObservationOrigin; +/// Generic account acquisition observation status. +pub use self::dto::AcquisitionObservationStatus; /// Core account key insert contract. pub use self::dto::CoreAccountKeyInsert; /// Core account key source category. pub use self::dto::CoreAccountKeySource; +/// Core account-state insert contract. +pub use self::dto::CoreAccountStateInsert; /// Core balance change insert contract. pub use self::dto::CoreBalanceChangeInsert; /// Core balance change kind. @@ -37,6 +45,8 @@ pub use self::dto::CoreInstructionProcessingState; pub use self::dto::CoreInstructionReplayFilter; /// Core log insert contract. pub use self::dto::CoreLogInsert; +/// Core return-data insert contract. +pub use self::dto::CoreReturnDataInsert; /// Core transaction insert contract. pub use self::dto::CoreTransactionInsert; /// One machine-readable decoder coverage declaration row. @@ -51,20 +61,21 @@ pub use self::dto::DecodeFailure; pub use self::dto::DecodeObservationInsert; /// Atomic persistence bundle for one decoder and one contextual input. pub use self::dto::DecodePersistenceBundle; +pub use self::dto::DecodeSchemaProvenance; /// Bounded contextual instruction selection filter for decode campaigns. pub use self::dto::DecodeSelectionFilter; /// Insert or upsert result contract returned by repositories. pub use self::dto::InsertOutcome; /// Maximum number of materialized rows returned by one bounded query. -pub use self::dto::MAX_MATERIALIZED_EVENT_QUERY_ROWS; +pub use self::dto::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS; /// Atomic persistence bundle for one materializer and one decoded observation. pub use self::dto::MaterializationPersistenceBundle; -/// Bounded read-only materialized event selection. -pub use self::dto::MaterializedEventFilter; -/// One materialized output returned by a bounded query. -pub use self::dto::MaterializedEventQueryRow; +/// Bounded read-only materialized output selection. +pub use self::dto::MaterializedOutputFilter; /// One processor-owned materialized output row. pub use self::dto::MaterializedOutputInsert; +/// One materialized output returned by a bounded query. +pub use self::dto::MaterializedOutputQueryRow; /// Stable processing ledger identity. pub use self::dto::ProcessingLedgerIdentity; /// Stable processing ledger status. @@ -105,12 +116,13 @@ pub use self::dto::StoreResourceStatistics; pub use self::dto::StoreRuntimeSummary; /// Transaction acquisition observation insert contract. pub use self::dto::TransactionObservationInsert; -/// Transaction acquisition observation origin. -pub use self::dto::TransactionObservationOrigin; /// Transaction acquisition observation status. -pub use self::dto::TransactionObservationStatus; +/// Generic account acquisition observation SQL-like row contract. +pub use self::entity::AccountObservationRow; /// Core account key SQL-like row contract. pub use self::entity::CoreAccountKeyRow; +/// Core account-state SQL-like row contract. +pub use self::entity::CoreAccountStateRow; /// Core balance change SQL-like row contract. pub use self::entity::CoreBalanceChangeRow; /// Core inner instruction SQL-like row contract. @@ -119,6 +131,8 @@ pub use self::entity::CoreInnerInstructionRow; pub use self::entity::CoreInstructionRow; /// Core log SQL-like row contract. pub use self::entity::CoreLogRow; +/// Core return-data SQL-like row contract. +pub use self::entity::CoreReturnDataRow; /// Core transaction SQL-like row contract. pub use self::entity::CoreTransactionRow; /// Canonical raw Solana transaction SQL-like row contract. diff --git a/ks-store/src/contracts/dto.rs b/ks-store/src/contracts/dto.rs index 16d4642..cbbe0e9 100644 --- a/ks-store/src/contracts/dto.rs +++ b/ks-store/src/contracts/dto.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/dto.rs -// version: 6 +// version: 7 //! Backend-neutral DTO exports for storage repository contracts. @@ -14,6 +14,8 @@ mod store; pub use self::core::CoreAccountKeyInsert; /// Core account key source category. pub use self::core::CoreAccountKeySource; +/// Core account-state insert contract. +pub use self::core::CoreAccountStateInsert; /// Core balance change insert contract. pub use self::core::CoreBalanceChangeInsert; /// Core balance change kind. @@ -30,6 +32,8 @@ pub use self::core::CoreInstructionProcessingState; pub use self::core::CoreInstructionReplayFilter; /// Core log insert contract. pub use self::core::CoreLogInsert; +/// Core return-data insert contract. +pub use self::core::CoreReturnDataInsert; /// Core transaction insert contract. pub use self::core::CoreTransactionInsert; /// Complete normalized core extraction write bundle. @@ -54,20 +58,27 @@ pub use self::decode::DecodeFailure; pub use self::decode::DecodeObservationInsert; /// Atomic persistence bundle for one decoder and one contextual input. pub use self::decode::DecodePersistenceBundle; +pub use self::decode::DecodeSchemaProvenance; /// Bounded contextual instruction selection filter for decode campaigns. pub use self::decode::DecodeSelectionFilter; /// Maximum number of materialized rows returned by one bounded query. -pub use self::decode::MAX_MATERIALIZED_EVENT_QUERY_ROWS; +pub use self::decode::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS; /// Atomic persistence bundle for one materializer and one decoded observation. pub use self::decode::MaterializationPersistenceBundle; -/// Bounded read-only materialized event selection. -pub use self::decode::MaterializedEventFilter; -/// One materialized output returned by a bounded query. -pub use self::decode::MaterializedEventQueryRow; +/// Bounded read-only materialized output selection. +pub use self::decode::MaterializedOutputFilter; /// One processor-owned materialized output row. pub use self::decode::MaterializedOutputInsert; +/// One materialized output returned by a bounded query. +pub use self::decode::MaterializedOutputQueryRow; /// Insert or upsert result contract returned by repositories. pub use self::outcome::InsertOutcome; +/// Generic account acquisition observation insert contract. +pub use self::raw::AccountObservationInsert; +/// Transaction acquisition observation origin. +pub use self::raw::AcquisitionObservationOrigin; +/// Generic account acquisition observation status. +pub use self::raw::AcquisitionObservationStatus; /// Raw payload lifecycle mark request. pub use self::raw::RawPayloadLifecycleMark; /// Raw payload processing state. @@ -78,10 +89,7 @@ pub use self::raw::RawPayloadRetentionState; pub use self::raw::RawTransactionInsert; /// Transaction acquisition observation insert contract. pub use self::raw::TransactionObservationInsert; -/// Transaction acquisition observation origin. -pub use self::raw::TransactionObservationOrigin; /// Transaction acquisition observation status. -pub use self::raw::TransactionObservationStatus; /// Store backend diagnostic contract. pub use self::store::StoreBackendDescriptor; /// Backend-neutral diagnostic snapshot. diff --git a/ks-store/src/contracts/dto/core.rs b/ks-store/src/contracts/dto/core.rs index abf1d89..cfa0166 100644 --- a/ks-store/src/contracts/dto/core.rs +++ b/ks-store/src/contracts/dto/core.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/dto/core.rs -// version: 2 +// version: 3 //! Core Solana storage DTOs. @@ -47,6 +47,8 @@ pub struct CoreTransactionInsert { pub signature: std::string::String, /// Transaction slot in Solana unsigned representation. pub slot: u64, + /// Optional block timestamp as Unix seconds supplied by Solana. + pub block_time: std::option::Option, /// Whether the transaction failed on-chain. pub failed: bool, /// Optional raw error JSON extracted from transaction metadata. @@ -55,11 +57,12 @@ pub struct CoreTransactionInsert { pub raw_transaction_id: std::option::Option, } -impl CoreTransactionInsert { +impl crate::CoreTransactionInsert { /// Builds a core transaction insert contract after minimal validation. pub fn new( signature: impl std::convert::Into, slot: u64, + block_time: std::option::Option, failed: bool, err_json: std::option::Option, ) -> ks_core::Result { @@ -74,6 +77,7 @@ impl CoreTransactionInsert { return std::result::Result::Ok(Self { signature: signature_value, slot, + block_time, failed, err_json, raw_transaction_id: std::option::Option::None, @@ -99,7 +103,7 @@ pub struct CoreAccountKeyInsert { /// Account public key as non-empty base58 text. pub account_key: std::string::String, /// Source category for this account key. - pub source: CoreAccountKeySource, + pub source: crate::CoreAccountKeySource, /// Whether the resolved account is writable for the transaction. pub writable: bool, /// Whether the resolved account signed the transaction. @@ -108,7 +112,7 @@ pub struct CoreAccountKeyInsert { pub executable: std::option::Option, } -impl CoreAccountKeyInsert { +impl crate::CoreAccountKeyInsert { /// Builds a core account key insert contract after minimal validation. #[allow(clippy::too_many_arguments)] pub fn new( @@ -116,7 +120,7 @@ impl CoreAccountKeyInsert { slot: u64, account_index: u32, account_key: impl std::convert::Into, - source: CoreAccountKeySource, + source: crate::CoreAccountKeySource, writable: bool, signer: bool, executable: std::option::Option, @@ -148,6 +152,49 @@ impl CoreAccountKeyInsert { } } +/// Canonical Core account-state observation produced from a raw account observation. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct CoreAccountStateInsert { + /// Source raw account observation technical id. + pub source_observation_id: i64, + /// Observed account public key. + pub account_key: std::string::String, + /// Context slot of the normalized account state. + pub slot: u64, + /// Owning program id. + pub owner: std::string::String, + /// Exact lamports value. + pub lamports: u64, + /// Whether the account is executable. + pub executable: bool, + /// Exact rent epoch. + pub rent_epoch: u64, + /// Complete account-data length. + pub space: u64, + /// Exact account bytes encoded as standard base64. + pub data_base64: std::string::String, + /// Deterministic digest of decoded account bytes. + pub data_hash: std::string::String, +} + +impl crate::CoreAccountStateInsert { + /// Validates a complete source-independent account state before persistence. + pub fn validate(&self) -> ks_core::Result<()> { + if self.source_observation_id <= 0 { + return std::result::Result::Err(ks_core::Error::db( + "core account state source observation id must be positive", + )); + } + let required = [self.account_key.as_str(), self.owner.as_str(), self.data_hash.as_str()]; + if required.iter().any(|value| return value.trim().is_empty()) { + return std::result::Result::Err(ks_core::Error::db( + "core account state identity fields must not be empty", + )); + } + return std::result::Result::Ok(()); + } +} + /// Core instruction insert contract. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct CoreInstructionInsert { @@ -159,6 +206,8 @@ pub struct CoreInstructionInsert { pub instruction_path: std::string::String, /// Program id as non-empty base58 text. pub program_id: std::string::String, + /// Optional runtime invocation stack height. + pub stack_height: std::option::Option, /// Instruction accounts as JSON, preserving unresolved forms when needed. pub accounts_json: serde_json::Value, /// Instruction payload JSON, preserving raw and partially decoded forms when needed. @@ -166,16 +215,17 @@ pub struct CoreInstructionInsert { /// Optional deterministic payload JSON hash. pub payload_json_hash: std::option::Option, /// Initial processing state used by replay schedulers. - pub processing_state: CoreInstructionProcessingState, + pub processing_state: crate::CoreInstructionProcessingState, } -impl CoreInstructionInsert { +impl crate::CoreInstructionInsert { /// Builds a core instruction insert contract after minimal validation. pub fn new( signature: impl std::convert::Into, slot: u64, instruction_path: impl std::convert::Into, program_id: impl std::convert::Into, + stack_height: std::option::Option, accounts_json: serde_json::Value, payload_json: serde_json::Value, ) -> ks_core::Result { @@ -191,15 +241,21 @@ impl CoreInstructionInsert { if let std::result::Result::Err(error) = validation_result { return std::result::Result::Err(error); } + if stack_height.is_some_and(|value| return value == 0) { + return std::result::Result::Err(ks_core::Error::db( + "core instruction stack height must be greater than zero when present", + )); + } return std::result::Result::Ok(Self { signature: signature_value, slot, instruction_path: instruction_path_value, program_id: program_id_value, + stack_height, accounts_json, payload_json, payload_json_hash: std::option::Option::None, - processing_state: CoreInstructionProcessingState::Pending, + processing_state: crate::CoreInstructionProcessingState::Pending, }); } @@ -232,15 +288,19 @@ pub struct CoreInnerInstructionInsert { pub instruction_path: std::string::String, /// Program id as non-empty base58 text. pub program_id: std::string::String, + /// Optional runtime invocation stack height. + pub stack_height: std::option::Option, /// Inner instruction accounts as JSON. pub accounts_json: serde_json::Value, /// Inner instruction payload JSON. pub payload_json: serde_json::Value, /// Optional deterministic payload JSON hash. pub payload_json_hash: std::option::Option, + /// Initial processing state used by CPI replay schedulers. + pub processing_state: crate::CoreInstructionProcessingState, } -impl CoreInnerInstructionInsert { +impl crate::CoreInnerInstructionInsert { /// Builds a core inner instruction insert contract after minimal validation. pub fn new( signature: impl std::convert::Into, @@ -248,6 +308,7 @@ impl CoreInnerInstructionInsert { parent_instruction_path: impl std::convert::Into, instruction_path: impl std::convert::Into, program_id: impl std::convert::Into, + stack_height: std::option::Option, accounts_json: serde_json::Value, payload_json: serde_json::Value, ) -> ks_core::Result { @@ -271,15 +332,22 @@ impl CoreInnerInstructionInsert { if let std::result::Result::Err(error) = validation_result { return std::result::Result::Err(error); } + if stack_height.is_some_and(|value| return value <= 1) { + return std::result::Result::Err(ks_core::Error::db( + "core inner instruction stack height must be greater than one when present", + )); + } return std::result::Result::Ok(Self { signature: signature_value, slot, parent_instruction_path: parent_instruction_path_value, instruction_path: instruction_path_value, program_id: program_id_value, + stack_height, accounts_json, payload_json, payload_json_hash: std::option::Option::None, + processing_state: crate::CoreInstructionProcessingState::Pending, }); } @@ -299,6 +367,52 @@ impl CoreInnerInstructionInsert { } } +/// Transaction return-data fact retained in Core for future decoders. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct CoreReturnDataInsert { + /// Transaction signature. + pub signature: std::string::String, + /// Transaction slot. + pub slot: u64, + /// Program id that produced the return data. + pub program_id: std::string::String, + /// Exact return bytes encoded as standard base64. + pub data_base64: std::string::String, +} + +impl crate::CoreReturnDataInsert { + /// Builds a validated Core return-data fact. + pub fn new( + signature: impl std::convert::Into, + slot: u64, + program_id: impl std::convert::Into, + data_base64: impl std::convert::Into, + ) -> ks_core::Result { + let signature_value = signature.into(); + let program_id_value = program_id.into(); + let signature_result = validate_required_text( + &signature_value, + "core return-data signature must not be empty", + ); + if let std::result::Result::Err(error) = signature_result { + return std::result::Result::Err(error); + } + let program_result = validate_required_text( + &program_id_value, + "core return-data program id must not be empty", + ); + if let std::result::Result::Err(error) = program_result { + return std::result::Result::Err(error); + } + return std::result::Result::Ok(Self { + signature: signature_value, + slot, + program_id: program_id_value, + data_base64: data_base64.into(), + }); + } +} + /// Core log insert contract. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct CoreLogInsert { @@ -318,7 +432,7 @@ pub struct CoreLogInsert { pub log_text_hash: std::option::Option, } -impl CoreLogInsert { +impl crate::CoreLogInsert { /// Builds a core log insert contract after minimal validation. pub fn new( signature: impl std::convert::Into, @@ -391,7 +505,7 @@ pub struct CoreBalanceChangeInsert { /// Stable balance change index preserving extraction order. pub balance_change_index: u32, /// Balance change family. - pub balance_kind: CoreBalanceChangeKind, + pub balance_kind: crate::CoreBalanceChangeKind, /// Optional account index when available. pub account_index: std::option::Option, /// Optional account public key when available. @@ -408,14 +522,14 @@ pub struct CoreBalanceChangeInsert { pub delta_json: std::option::Option, } -impl CoreBalanceChangeInsert { +impl crate::CoreBalanceChangeInsert { /// Builds a core balance change insert contract after minimal validation. #[allow(clippy::too_many_arguments)] pub fn new( signature: impl std::convert::Into, slot: u64, balance_change_index: u32, - balance_kind: CoreBalanceChangeKind, + balance_kind: crate::CoreBalanceChangeKind, account_index: std::option::Option, account_key: std::option::Option, mint: std::option::Option, @@ -473,7 +587,7 @@ impl CoreBalanceChangeInsert { #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct CoreInstructionReplayFilter { /// Optional processing state to select, usually `Pending`, `Failed` or `ReplayRequested`. - pub processing_state: std::option::Option, + pub processing_state: std::option::Option, /// Optional program id filter. pub program_id: std::option::Option, /// Optional inclusive minimum slot. @@ -482,10 +596,10 @@ pub struct CoreInstructionReplayFilter { pub max_slot: std::option::Option, } -impl CoreInstructionReplayFilter { +impl crate::CoreInstructionReplayFilter { /// Builds a replay filter after minimal validation. pub fn new( - processing_state: std::option::Option, + processing_state: std::option::Option, program_id: std::option::Option, min_slot: std::option::Option, max_slot: std::option::Option, @@ -519,7 +633,9 @@ impl CoreInstructionReplayFilter { /// Builds the default pending instruction replay filter. pub fn pending() -> Self { return Self { - processing_state: std::option::Option::Some(CoreInstructionProcessingState::Pending), + processing_state: std::option::Option::Some( + crate::CoreInstructionProcessingState::Pending, + ), program_id: std::option::Option::None, min_slot: std::option::Option::None, max_slot: std::option::Option::None, @@ -535,7 +651,7 @@ pub struct CoreInstructionLifecycleMark { /// Stable instruction path, for example `0` or `2/1`. pub instruction_path: std::string::String, /// New processing state. - pub processing_state: CoreInstructionProcessingState, + pub processing_state: crate::CoreInstructionProcessingState, /// Optional processor name that produced the lifecycle transition. pub processor_name: std::option::Option, /// Optional processor version that produced the lifecycle transition. @@ -544,12 +660,12 @@ pub struct CoreInstructionLifecycleMark { pub reason: std::option::Option, } -impl CoreInstructionLifecycleMark { +impl crate::CoreInstructionLifecycleMark { /// Builds a core instruction lifecycle mark after minimal validation. pub fn new( signature: impl std::convert::Into, instruction_path: impl std::convert::Into, - processing_state: CoreInstructionProcessingState, + processing_state: crate::CoreInstructionProcessingState, processor_name: std::option::Option, processor_version: std::option::Option, reason: std::option::Option, @@ -658,13 +774,25 @@ fn validate_optional_text( mod tests { #[test] fn core_transaction_rejects_empty_signature() { - let result = crate::CoreTransactionInsert::new(" ", 1, false, std::option::Option::None); + let result = crate::CoreTransactionInsert::new( + " ", + 1, + std::option::Option::None, + false, + std::option::Option::None, + ); assert!(result.is_err()); } #[test] fn core_transaction_accepts_optional_raw_lineage() { - let result = crate::CoreTransactionInsert::new("abc", 1, false, std::option::Option::None); + let result = crate::CoreTransactionInsert::new( + "abc", + 1, + std::option::Option::None, + false, + std::option::Option::None, + ); let input = match result { std::result::Result::Ok(value) => value.with_raw_transaction_id(7), std::result::Result::Err(error) => panic!("unexpected transaction error: {error}"), @@ -694,6 +822,7 @@ mod tests { 1, "0", " ", + std::option::Option::None, serde_json::json!([]), serde_json::json!({}), ); @@ -707,6 +836,7 @@ mod tests { 1, "0", "program", + std::option::Option::None, serde_json::json!([]), serde_json::json!({}), ); @@ -725,6 +855,36 @@ mod tests { " ", "0/0", "program", + std::option::Option::None, + serde_json::json!([]), + serde_json::json!({}), + ); + assert!(result.is_err()); + } + + #[test] + fn core_instruction_rejects_zero_stack_height() { + let result = crate::CoreInstructionInsert::new( + "abc", + 1, + "0", + "program", + std::option::Option::Some(0), + serde_json::json!([]), + serde_json::json!({}), + ); + assert!(result.is_err()); + } + + #[test] + fn core_inner_instruction_rejects_top_level_stack_height() { + let result = crate::CoreInnerInstructionInsert::new( + "abc", + 1, + "0", + "0/0", + "program", + std::option::Option::Some(1), serde_json::json!([]), serde_json::json!({}), ); @@ -762,6 +922,23 @@ mod tests { assert!(result.is_err()); } + #[test] + fn core_account_state_requires_positive_lineage() { + let state = crate::CoreAccountStateInsert { + source_observation_id: 0, + account_key: "11111111111111111111111111111111".to_string(), + slot: 1, + owner: "11111111111111111111111111111111".to_string(), + lamports: 0, + executable: false, + rent_epoch: 0, + space: 0, + data_base64: std::string::String::new(), + data_hash: "hash".to_string(), + }; + assert!(state.validate().is_err()); + } + #[test] fn replay_input_rejects_empty_key() { let result = crate::MdCoreInstructionReplayInput::new( @@ -785,7 +962,7 @@ mod tests { } #[test] - fn replay_input_accepts_ordered_outer_instruction_array() { + fn replay_input_accepts_ordered_top_level_instruction_array() { let result = crate::MdCoreInstructionReplayInput::new( "abc:2", "abc", @@ -822,15 +999,15 @@ mod tests { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("unexpected replay input error: {error}"), }; - assert_eq!(input.core_contract_version, 2); + assert_eq!(input.core_contract_version, crate::MD_CORE_REPLAY_INPUT_CONTRACT_VERSION); assert_eq!( - input.outer_instructions_json.as_array().map(std::vec::Vec::len), + input.top_level_instructions_json.as_array().map(std::vec::Vec::len), std::option::Option::Some(2) ); } #[test] - fn replay_input_rejects_non_array_outer_instruction_context() { + fn replay_input_rejects_non_array_top_level_instruction_context() { let result = crate::MdCoreInstructionReplayInput::new( "abc:0", "abc", diff --git a/ks-store/src/contracts/dto/core_extraction.rs b/ks-store/src/contracts/dto/core_extraction.rs index bf326ca..7f74c6a 100644 --- a/ks-store/src/contracts/dto/core_extraction.rs +++ b/ks-store/src/contracts/dto/core_extraction.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/dto/core_extraction.rs -// version: 2 +// version: 3 //! Canonical transaction to core extraction storage contracts. @@ -31,7 +31,7 @@ pub struct CoreExtractionSelectionFilter { pub limit: u32, } -impl CoreExtractionSelectionFilter { +impl crate::CoreExtractionSelectionFilter { /// Builds a validated extraction selection filter. pub fn new( signatures: std::vec::Vec, @@ -105,7 +105,7 @@ pub struct ProcessingLedgerIdentity { pub input_hash: std::string::String, } -impl ProcessingLedgerIdentity { +impl crate::ProcessingLedgerIdentity { /// Builds a validated processing ledger identity. pub fn new( stage: impl std::convert::Into, @@ -154,9 +154,11 @@ pub struct CoreExtractionBundle { pub logs: std::vec::Vec, /// Native and token balance changes. pub balance_changes: std::vec::Vec, + /// Optional transaction return data retained by Core. + pub return_data: std::option::Option, } -impl CoreExtractionBundle { +impl crate::CoreExtractionBundle { /// Validates lineage and stable signature consistency across the bundle. pub fn validate(&self) -> ks_core::Result<()> { if self.raw_transaction_id <= 0 { @@ -211,6 +213,13 @@ impl CoreExtractionBundle { )); } } + if let std::option::Option::Some(return_data) = &self.return_data { + if return_data.signature != signature { + return std::result::Result::Err(ks_core::Error::db( + "core extraction return-data signature mismatch", + )); + } + } return std::result::Result::Ok(()); } } @@ -228,7 +237,7 @@ pub struct CoreExtractionFailure { pub error_message: std::string::String, } -impl CoreExtractionFailure { +impl crate::CoreExtractionFailure { /// Builds a validated failure record. pub fn new( raw_transaction_id: i64, diff --git a/ks-store/src/contracts/dto/decode.rs b/ks-store/src/contracts/dto/decode.rs index 156edf8..14e533c 100644 --- a/ks-store/src/contracts/dto/decode.rs +++ b/ks-store/src/contracts/dto/decode.rs @@ -1,14 +1,14 @@ // file: ks-store/src/contracts/dto/decode.rs -// version: 5 +// version: 6 //! Backend-neutral decode, coverage and materialization persistence DTOs. /// Maximum number of materialized rows returned by one bounded query. -pub const MAX_MATERIALIZED_EVENT_QUERY_ROWS: u32 = 500; +pub const MAX_MATERIALIZED_OUTPUT_QUERY_ROWS: u32 = 500; -/// Bounded read-only materialized event selection. +/// Bounded read-only materialized output selection. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct MaterializedEventFilter { +pub struct MaterializedOutputFilter { /// Optional exact materializer processor name. pub processor_name: std::option::Option, /// Optional exact materialized family code. @@ -19,18 +19,18 @@ pub struct MaterializedEventFilter { pub limit: u32, } -impl MaterializedEventFilter { - /// Builds and validates a bounded materialized event filter. +impl crate::MaterializedOutputFilter { + /// Builds and validates a bounded materialized output filter. pub fn new( processor_name: std::option::Option, materialized_family: std::option::Option, signature_contains: std::option::Option, limit: u32, ) -> ks_core::Result { - if limit == 0 || limit > crate::MAX_MATERIALIZED_EVENT_QUERY_ROWS { + if limit == 0 || 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 ))); } return std::result::Result::Ok(Self { @@ -48,7 +48,7 @@ impl MaterializedEventFilter { /// One materialized output returned by the common bounded query contract. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct MaterializedEventQueryRow { +pub struct MaterializedOutputQueryRow { /// Materializer processor name. pub processor_name: std::string::String, /// Materializer processor version. @@ -98,7 +98,7 @@ pub struct DecodeSelectionFilter { pub limit: u32, } -impl DecodeSelectionFilter { +impl crate::DecodeSelectionFilter { /// Builds a validated bounded decode selection filter. #[allow(clippy::too_many_arguments)] pub fn new( @@ -165,6 +165,38 @@ impl DecodeSelectionFilter { } } +/// Optional external schema provenance used by a decoder for one observation. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DecodeSchemaProvenance { + /// Stable schema mechanism code, for example an IDL family or another external schema system. + pub schema_kind: std::string::String, + /// Stable schema identity within the selected mechanism. + pub schema_id: std::string::String, + /// Optional source schema version when one is published independently from the decoder. + pub schema_version: std::option::Option, + /// Deterministic schema content hash used to reproduce the decode. + pub schema_hash: std::string::String, +} + +impl crate::DecodeSchemaProvenance { + /// Validates the source-neutral schema identity without assuming a specific IDL mechanism. + pub fn validate(&self) -> ks_core::Result<()> { + if self.schema_kind.trim().is_empty() + || self.schema_id.trim().is_empty() + || self.schema_hash.trim().is_empty() + || self + .schema_version + .as_deref() + .is_some_and(|value| return value.trim().is_empty()) + { + return std::result::Result::Err(ks_core::Error::db( + "decode schema provenance fields must not be empty", + )); + } + return std::result::Result::Ok(()); + } +} + /// One processor-owned decoded observation row. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct DecodeObservationInsert { @@ -206,6 +238,8 @@ pub struct DecodeObservationInsert { pub proof_json: serde_json::Value, /// Typed decoded payload JSON. pub payload_json: serde_json::Value, + /// Optional external schema provenance when decoding depends on a schema independent of decoder code. + pub schema_provenance: std::option::Option, /// Whether the source transaction failed on-chain. pub transaction_failed: bool, /// Optional source transaction error JSON. @@ -214,7 +248,7 @@ pub struct DecodeObservationInsert { pub observation_committed: bool, } -impl DecodeObservationInsert { +impl crate::DecodeObservationInsert { /// Validates stable identities and failed transaction commit semantics. pub fn validate(&self) -> ks_core::Result<()> { let fields = [ @@ -240,6 +274,12 @@ impl DecodeObservationInsert { "decoded observation identity fields must not be empty", )); } + if let std::option::Option::Some(schema_provenance) = &self.schema_provenance { + let schema_result = schema_provenance.validate(); + if let std::result::Result::Err(error) = schema_result { + return std::result::Result::Err(error); + } + } if self.transaction_failed && self.observation_committed { return std::result::Result::Err(ks_core::Error::db( "failed transaction decoded observations must not be committed", @@ -325,12 +365,12 @@ pub struct DecodePersistenceBundle { /// Optional human-readable terminal error message. pub error_message: std::option::Option, /// Processor-owned decoded observations. - pub observations: std::vec::Vec, + pub observations: std::vec::Vec, /// Coverage observation for this attempt. - pub coverage: DecodeCoverageObservationInsert, + pub coverage: crate::DecodeCoverageObservationInsert, } -impl DecodePersistenceBundle { +impl crate::DecodePersistenceBundle { /// Validates identities shared by every atomic decode output. pub fn validate(&self) -> ks_core::Result<()> { if self.ledger_identity.stage != "instruction_decode" @@ -471,10 +511,10 @@ pub struct MaterializationPersistenceBundle { /// Optional human-readable terminal error message. pub error_message: std::option::Option, /// Processor-owned materialized outputs. - pub outputs: std::vec::Vec, + pub outputs: std::vec::Vec, } -impl MaterializationPersistenceBundle { +impl crate::MaterializationPersistenceBundle { /// Validates the materializer, source decoder and output identities. pub fn validate(&self) -> ks_core::Result<()> { if self.ledger_identity.stage != "event_materialization" @@ -630,12 +670,12 @@ mod tests { } #[test] - fn materialized_event_filter_is_bounded_and_trims_optional_text() { - let result = crate::MaterializedEventFilter::new( + fn materialized_output_filter_is_bounded_and_trims_optional_text() { + let result = crate::MaterializedOutputFilter::new( std::option::Option::Some(" materializer.transaction.annotations ".to_string()), std::option::Option::Some(" transaction_annotation ".to_string()), std::option::Option::Some(" signature ".to_string()), - crate::MAX_MATERIALIZED_EVENT_QUERY_ROWS, + crate::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS, ); let filter = match result { std::result::Result::Ok(value) => value, @@ -647,7 +687,7 @@ mod tests { ); assert_eq!(filter.signature_contains.as_deref(), std::option::Option::Some("signature")); assert!( - crate::MaterializedEventFilter::new( + crate::MaterializedOutputFilter::new( std::option::Option::None, std::option::Option::None, std::option::Option::None, @@ -657,6 +697,24 @@ mod tests { ); } + #[test] + fn decode_schema_provenance_requires_reproducible_identity() { + let valid = crate::DecodeSchemaProvenance { + schema_kind: "idl".to_string(), + schema_id: "program-schema".to_string(), + schema_version: std::option::Option::Some("1".to_string()), + schema_hash: "abc123".to_string(), + }; + assert!(valid.validate().is_ok()); + let invalid = crate::DecodeSchemaProvenance { + schema_kind: "idl".to_string(), + schema_id: " ".to_string(), + schema_version: std::option::Option::None, + schema_hash: "abc123".to_string(), + }; + assert!(invalid.validate().is_err()); + } + #[test] fn failed_decoded_observation_cannot_be_committed() { let input = crate::DecodeObservationInsert { @@ -679,6 +737,7 @@ mod tests { proof_kind: "exact_layout".to_string(), proof_json: serde_json::json!({}), payload_json: serde_json::json!({}), + schema_provenance: std::option::Option::None, transaction_failed: true, transaction_error: std::option::Option::Some(serde_json::json!({})), observation_committed: true, diff --git a/ks-store/src/contracts/dto/raw.rs b/ks-store/src/contracts/dto/raw.rs index 21253a7..b839dd5 100644 --- a/ks-store/src/contracts/dto/raw.rs +++ b/ks-store/src/contracts/dto/raw.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/dto/raw.rs -// version: 2 +// version: 3 //! Canonical Solana transaction and acquisition observation storage DTOs. @@ -31,35 +31,35 @@ pub enum RawPayloadProcessingState { Failed, } -/// Origin of one transaction acquisition observation. +/// Origin of one source acquisition observation. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub enum TransactionObservationOrigin { - /// Transaction was observed from a current live stream. +pub enum AcquisitionObservationOrigin { + /// Data was observed from a current live stream. Live, - /// Transaction was acquired by an explicit historical backfill. + /// Data was acquired by an explicit historical backfill. Backfill, - /// Transaction was replayed from an already captured source. + /// Data was replayed from an already captured source. Replay, - /// Transaction was fetched to repair an acquisition gap. + /// Data was fetched to repair an acquisition gap. Repair, /// Observation was converted from a historical storage table. Migration, } -/// Technical status of one transaction acquisition observation. +/// Technical status of one source acquisition observation. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub enum TransactionObservationStatus { - /// A transaction candidate was detected before a complete payload was received. +pub enum AcquisitionObservationStatus { + /// A source candidate was detected before a complete payload was received. Detected, /// A source payload was received. Received, - /// A source payload was normalized into the canonical transaction contract. + /// A source payload was normalized into its canonical N1 contract. Normalized, - /// The observation and any linked canonical transaction were persisted. + /// The observation and any linked canonical payload were persisted. Persisted, /// Acquisition or normalization failed. Failed, - /// The source reported or implied a transaction that was temporarily unavailable. + /// The source reported that the requested source object was unavailable. Missing, } @@ -70,6 +70,8 @@ pub struct RawTransactionInsert { pub signature: std::string::String, /// Transaction slot in Solana unsigned representation. pub slot: u64, + /// Optional block timestamp as Unix seconds supplied by Solana. + pub block_time: std::option::Option, /// Canonical source-independent transaction document. pub canonical_json: serde_json::Value, /// Optional deterministic digest of the canonical document. @@ -78,11 +80,12 @@ pub struct RawTransactionInsert { pub canonical_format_version: u32, } -impl RawTransactionInsert { +impl crate::RawTransactionInsert { /// Builds a canonical raw transaction insert contract after minimal validation. pub fn new( signature: impl std::convert::Into, slot: u64, + block_time: std::option::Option, canonical_json: serde_json::Value, canonical_format_version: u32, ) -> ks_core::Result { @@ -100,6 +103,7 @@ impl RawTransactionInsert { return std::result::Result::Ok(Self { signature: signature_value, slot, + block_time, canonical_json, canonical_json_hash: std::option::Option::None, canonical_format_version, @@ -121,6 +125,7 @@ impl RawTransactionInsert { let insert_result = Self::new( transaction.primary_signature.clone(), transaction.slot, + transaction.block_time, canonical_json, transaction.format_version, ); @@ -147,6 +152,115 @@ impl RawTransactionInsert { } } +/// Generic Solana account acquisition observation retained for future account decoders. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct AccountObservationInsert { + /// Stable observation key used for idempotence. + pub observation_key: std::string::String, + /// Observed account public key. + pub account_key: std::string::String, + /// RPC or stream context slot associated with this observation. + pub context_slot: u64, + /// Account owner when the account exists. + pub owner: std::option::Option, + /// Account lamports when the account exists. + pub lamports: std::option::Option, + /// Executable flag when the account exists. + pub executable: std::option::Option, + /// Rent epoch when the account exists. + pub rent_epoch: std::option::Option, + /// Complete account-data size when known. + pub space: std::option::Option, + /// Exact account bytes encoded as standard base64 when captured. + pub data_base64: std::option::Option, + /// Deterministic digest of the decoded account bytes when captured. + pub data_hash: std::option::Option, + /// Acquisition provider code. + pub provider: std::string::String, + /// Optional configured endpoint code. + pub endpoint_code: std::option::Option, + /// Acquisition protocol. + pub protocol: std::string::String, + /// Acquisition method. + pub acquisition_method: std::string::String, + /// Optional commitment used for the read or subscription. + pub commitment: std::option::Option, + /// Optional capture session identifier. + pub capture_session_id: std::option::Option, + /// Optional configured filter code. + pub filter_code: std::option::Option, + /// Acquisition origin. + pub origin: crate::AcquisitionObservationOrigin, + /// Optional first detection timestamp. + pub detected_at: std::option::Option>, + /// Timestamp at which the observation was received locally. + pub received_at: chrono::DateTime, + /// Optional timestamp at which generic normalization completed. + pub normalized_at: std::option::Option>, + /// Optional uncompressed source payload size in bytes. + pub payload_size_bytes: std::option::Option, + /// Optional digest of the source-specific payload without retaining that payload. + pub source_payload_hash: std::option::Option, + /// Technical observation status. + pub status: crate::AcquisitionObservationStatus, + /// Optional stable error code. + pub error_code: std::option::Option, + /// Optional bounded diagnostic message. + pub error_message: std::option::Option, +} + +impl crate::AccountObservationInsert { + /// Validates the replayable account observation shape without assuming a decoder schema. + pub fn validate(&self) -> ks_core::Result<()> { + let required_result = validate_required_observation_texts( + self.observation_key.as_str(), + self.provider.as_str(), + self.protocol.as_str(), + self.acquisition_method.as_str(), + ); + if let std::result::Result::Err(error) = required_result { + return std::result::Result::Err(error); + } + if self.account_key.trim().is_empty() { + return std::result::Result::Err(ks_core::Error::db( + "account observation account key must not be empty", + )); + } + let optional_texts = [ + self.owner.as_deref(), + self.endpoint_code.as_deref(), + self.commitment.as_deref(), + self.capture_session_id.as_deref(), + self.filter_code.as_deref(), + self.data_hash.as_deref(), + self.source_payload_hash.as_deref(), + self.error_code.as_deref(), + self.error_message.as_deref(), + ]; + if optional_texts.iter().flatten().any(|value| return value.trim().is_empty()) { + return std::result::Result::Err(ks_core::Error::db( + "account observation optional text fields must not be empty when present", + )); + } + let state_fields_present = [ + self.owner.is_some(), + self.lamports.is_some(), + self.executable.is_some(), + self.rent_epoch.is_some(), + self.space.is_some(), + self.data_base64.is_some(), + self.data_hash.is_some(), + ]; + let present_count = state_fields_present.iter().filter(|present| return **present).count(); + if present_count != 0 && present_count != state_fields_present.len() { + return std::result::Result::Err(ks_core::Error::db( + "account observation state fields must be complete or entirely absent", + )); + } + return std::result::Result::Ok(()); + } +} + /// Lightweight transaction acquisition observation insert contract. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct TransactionObservationInsert { @@ -167,7 +281,7 @@ pub struct TransactionObservationInsert { /// Source acquisition method. pub acquisition_method: std::string::String, /// Observation origin. - pub origin: TransactionObservationOrigin, + pub origin: crate::AcquisitionObservationOrigin, /// Optional Solana commitment. pub commitment: std::option::Option, /// Optional capture session identifier. @@ -185,21 +299,21 @@ pub struct TransactionObservationInsert { /// Optional digest of the source-specific payload without retaining that payload. pub source_payload_hash: std::option::Option, /// Observation status. - pub status: TransactionObservationStatus, + pub status: crate::AcquisitionObservationStatus, /// Optional machine-readable error code. pub error_code: std::option::Option, /// Optional diagnostic error message. pub error_message: std::option::Option, } -impl TransactionObservationInsert { +impl crate::TransactionObservationInsert { /// Builds a lightweight transaction observation after validating required source metadata. pub fn new( observation_key: impl std::convert::Into, provider: impl std::convert::Into, protocol: impl std::convert::Into, acquisition_method: impl std::convert::Into, - origin: TransactionObservationOrigin, + origin: crate::AcquisitionObservationOrigin, received_at: chrono::DateTime, ) -> ks_core::Result { let observation_key_value = observation_key.into(); @@ -233,7 +347,7 @@ impl TransactionObservationInsert { normalized_at: std::option::Option::None, payload_size_bytes: std::option::Option::None, source_payload_hash: std::option::Option::None, - status: TransactionObservationStatus::Received, + status: crate::AcquisitionObservationStatus::Received, error_code: std::option::Option::None, error_message: std::option::Option::None, }); @@ -356,7 +470,7 @@ impl TransactionObservationInsert { } /// Replaces the current observation status. - pub fn with_status(mut self, status: TransactionObservationStatus) -> Self { + pub fn with_status(mut self, status: crate::AcquisitionObservationStatus) -> Self { self.status = status; return self; } @@ -384,7 +498,7 @@ impl TransactionObservationInsert { } self.error_code = std::option::Option::Some(error_code_value); self.error_message = error_message; - self.status = TransactionObservationStatus::Failed; + self.status = crate::AcquisitionObservationStatus::Failed; return std::result::Result::Ok(self); } } @@ -392,25 +506,25 @@ impl TransactionObservationInsert { /// Canonical raw transaction lifecycle mark request. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct RawPayloadLifecycleMark { - /// Physical canonical raw table name using the `kb_sol__` convention. + /// Physical canonical raw table name using the `k_sol__` convention. pub raw_table_name: std::string::String, /// Stable canonical raw row key, currently the transaction signature. pub raw_row_key: std::string::String, /// Retention state to record for the canonical payload. - pub retention_state: RawPayloadRetentionState, + pub retention_state: crate::RawPayloadRetentionState, /// Processing state to record for the canonical payload. - pub processing_state: RawPayloadProcessingState, + pub processing_state: crate::RawPayloadProcessingState, /// Optional reason visible in diagnostics. pub reason: std::option::Option, } -impl RawPayloadLifecycleMark { +impl crate::RawPayloadLifecycleMark { /// Builds a canonical raw payload lifecycle mark after minimal validation. pub fn new( raw_table_name: impl std::convert::Into, raw_row_key: impl std::convert::Into, - retention_state: RawPayloadRetentionState, - processing_state: RawPayloadProcessingState, + retention_state: crate::RawPayloadRetentionState, + processing_state: crate::RawPayloadProcessingState, reason: std::option::Option, ) -> ks_core::Result { let raw_table_name_value = raw_table_name.into(); @@ -488,13 +602,25 @@ fn validate_optional_owned_text( mod tests { #[test] fn raw_transaction_rejects_empty_signature() { - let result = crate::RawTransactionInsert::new(" ", 1, serde_json::json!({"ok": true}), 1); + let result = crate::RawTransactionInsert::new( + " ", + 1, + std::option::Option::None, + serde_json::json!({"ok": true}), + 1, + ); assert!(result.is_err()); } #[test] fn raw_transaction_rejects_zero_format_version() { - let result = crate::RawTransactionInsert::new("abc", 1, serde_json::json!({"ok": true}), 0); + let result = crate::RawTransactionInsert::new( + "abc", + 1, + std::option::Option::None, + serde_json::json!({"ok": true}), + 0, + ); assert!(result.is_err()); } @@ -565,10 +691,49 @@ mod tests { #[test] fn raw_transaction_accepts_canonical_payload() { - let result = crate::RawTransactionInsert::new("abc", 1, serde_json::json!({"ok": true}), 1); + let result = crate::RawTransactionInsert::new( + "abc", + 1, + std::option::Option::None, + serde_json::json!({"ok": true}), + 1, + ); assert!(result.is_ok()); } + #[test] + fn account_observation_requires_complete_or_absent_state() { + let observation = crate::AccountObservationInsert { + observation_key: "account:1".to_string(), + account_key: "11111111111111111111111111111111".to_string(), + context_slot: 1, + owner: std::option::Option::Some("11111111111111111111111111111111".to_string()), + lamports: std::option::Option::None, + executable: std::option::Option::Some(false), + rent_epoch: std::option::Option::Some(0), + space: std::option::Option::Some(0), + data_base64: std::option::Option::Some(std::string::String::new()), + data_hash: std::option::Option::Some("hash".to_string()), + provider: "rpc".to_string(), + endpoint_code: std::option::Option::None, + protocol: "solana_http".to_string(), + acquisition_method: "getAccountInfo".to_string(), + commitment: std::option::Option::None, + capture_session_id: std::option::Option::None, + filter_code: std::option::Option::None, + origin: crate::AcquisitionObservationOrigin::Backfill, + detected_at: std::option::Option::None, + received_at: chrono::Utc::now(), + normalized_at: std::option::Option::None, + payload_size_bytes: std::option::Option::None, + source_payload_hash: std::option::Option::None, + status: crate::AcquisitionObservationStatus::Received, + error_code: std::option::Option::None, + error_message: std::option::Option::None, + }; + assert!(observation.validate().is_err()); + } + #[test] fn transaction_observation_rejects_empty_provider() { let result = crate::TransactionObservationInsert::new( @@ -576,7 +741,7 @@ mod tests { " ", "solana_http", "getTransaction", - crate::TransactionObservationOrigin::Backfill, + crate::AcquisitionObservationOrigin::Backfill, chrono::Utc::now(), ); assert!(result.is_err()); @@ -589,7 +754,7 @@ mod tests { "helius", "solana_http", "getTransaction", - crate::TransactionObservationOrigin::Repair, + crate::AcquisitionObservationOrigin::Repair, chrono::Utc::now(), ); assert!(result.is_ok()); @@ -598,7 +763,7 @@ mod tests { #[test] fn raw_lifecycle_rejects_empty_reason_when_present() { let result = crate::RawPayloadLifecycleMark::new( - "kb_sol_raw_transactions", + "k_sol_raw_transactions", "signature", crate::RawPayloadRetentionState::Full, crate::RawPayloadProcessingState::Received, diff --git a/ks-store/src/contracts/dto/store.rs b/ks-store/src/contracts/dto/store.rs index 29a47b1..ac1c1ca 100644 --- a/ks-store/src/contracts/dto/store.rs +++ b/ks-store/src/contracts/dto/store.rs @@ -1,22 +1,22 @@ // file: ks-store/src/contracts/dto/store.rs -// version: 5 +// version: 6 //! Backend-agnostic store configuration and diagnostic DTOs. -/// Store migration status contract. +/// Store schema evolution status contract. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub enum StoreMigrationStatus { - /// Migration status is not known yet. + /// Schema evolution status is not known yet. Unknown, - /// Store has no migration table or migration history yet. + /// Store baseline schema is not initialized yet. NotInitialized, - /// Store migrations are current. + /// Store schema contract is current. Current, - /// Store has pending migrations. + /// Store has pending schema evolution work. Pending, - /// Store migration history is inconsistent. + /// Store schema contract is partially applied or inconsistent. Drift, - /// Store migration check failed. + /// Store schema contract verification failed. Failed, } @@ -99,21 +99,21 @@ impl crate::StoreBackendDescriptor { } } -/// Store migration diagnostic snapshot contract. +/// Store schema evolution diagnostic snapshot contract. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct StoreMigrationSnapshot { - /// Current migration status. + /// Current schema evolution status. pub status: crate::StoreMigrationStatus, - /// Last applied migration identifier when known. + /// Current schema contract version when known. pub current_version: std::option::Option, - /// Pending migration identifiers when known. + /// Pending schema evolution identifiers when known. pub pending_versions: std::vec::Vec, /// Optional human-readable diagnostic message. pub message: std::option::Option, } impl crate::StoreMigrationSnapshot { - /// Builds a migration snapshot from explicit values. + /// Builds a schema evolution snapshot from explicit values. pub fn new( status: crate::StoreMigrationStatus, current_version: std::option::Option, diff --git a/ks-store/src/contracts/entity.rs b/ks-store/src/contracts/entity.rs index 45e17e4..f2c361e 100644 --- a/ks-store/src/contracts/entity.rs +++ b/ks-store/src/contracts/entity.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/entity.rs -// version: 5 +// version: 6 //! Backend-neutral persisted entity exports for storage adapters. @@ -8,6 +8,8 @@ mod raw; /// Core account key persisted row contract. pub use self::core::CoreAccountKeyRow; +/// Core account-state persisted row contract. +pub use self::core::CoreAccountStateRow; /// Core balance change persisted row contract. pub use self::core::CoreBalanceChangeRow; /// Core inner instruction persisted row contract. @@ -16,8 +18,12 @@ pub use self::core::CoreInnerInstructionRow; pub use self::core::CoreInstructionRow; /// Core log persisted row contract. pub use self::core::CoreLogRow; +/// Core return-data persisted row contract. +pub use self::core::CoreReturnDataRow; /// Core transaction persisted row contract. pub use self::core::CoreTransactionRow; +/// Generic account acquisition observation persisted row contract. +pub use self::raw::AccountObservationRow; /// Canonical raw Solana transaction persisted row contract. pub use self::raw::RawTransactionRow; /// Transaction acquisition observation persisted row contract. diff --git a/ks-store/src/contracts/entity/core.rs b/ks-store/src/contracts/entity/core.rs index 016f2f2..ab0a188 100644 --- a/ks-store/src/contracts/entity/core.rs +++ b/ks-store/src/contracts/entity/core.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/entity/core.rs -// version: 3 +// version: 4 //! Core Solana persisted entities. @@ -12,6 +12,8 @@ pub struct CoreTransactionRow { pub signature: std::string::String, /// Transaction slot stored as SQL `BIGINT`. pub slot: i64, + /// Optional block timestamp as Unix seconds supplied by Solana. + pub block_time: std::option::Option, /// Whether the transaction failed on-chain. pub failed: bool, /// Optional canonical raw transaction row id used for lineage when available. @@ -51,6 +53,35 @@ pub struct CoreAccountKeyRow { pub created_at: chrono::DateTime, } +/// Canonical Core account-state persisted row contract. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct CoreAccountStateRow { + /// Technical primary key. + pub id: i64, + /// Source account observation technical key. + pub source_observation_id: i64, + /// Account public key. + pub account_key: std::string::String, + /// Context slot stored as SQL `BIGINT`. + pub slot: i64, + /// Owning program id. + pub owner: std::string::String, + /// Exact lamports representation returned by PostgreSQL. + pub lamports: std::string::String, + /// Whether the account is executable. + pub executable: bool, + /// Exact rent-epoch representation returned by PostgreSQL. + pub rent_epoch: std::string::String, + /// Complete account-data length. + pub space: i64, + /// Exact account bytes encoded as standard base64. + pub data_base64: std::string::String, + /// Deterministic digest of decoded account bytes. + pub data_hash: std::string::String, + /// Insert timestamp. + pub created_at: chrono::DateTime, +} + /// Core instruction persisted row contract. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct CoreInstructionRow { @@ -66,6 +97,8 @@ pub struct CoreInstructionRow { pub instruction_path: std::string::String, /// Program id as non-empty base58 text. pub program_id: std::string::String, + /// Optional runtime invocation stack height. + pub stack_height: std::option::Option, /// Instruction accounts as JSON, preserving unresolved forms when needed. pub accounts_json: serde_json::Value, /// Instruction payload JSON while it is still retained in the hot store. @@ -97,12 +130,37 @@ pub struct CoreInnerInstructionRow { pub instruction_path: std::string::String, /// Program id as non-empty base58 text. pub program_id: std::string::String, + /// Optional runtime invocation stack height. + pub stack_height: std::option::Option, /// Inner instruction accounts as JSON. pub accounts_json: serde_json::Value, /// Inner instruction payload JSON while it is retained in the hot store. pub payload_json: std::option::Option, /// Optional digest of the inner instruction payload after compaction or purge. pub payload_json_hash: std::option::Option, + /// Current processing state used by CPI replay. + pub processing_state: crate::CoreInstructionProcessingState, + /// Insert timestamp. + pub created_at: chrono::DateTime, + /// Last lifecycle update timestamp. + pub updated_at: chrono::DateTime, +} + +/// Core return-data persisted row contract. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct CoreReturnDataRow { + /// Technical primary key. + pub id: i64, + /// Parent transaction technical key. + pub transaction_id: i64, + /// Transaction signature. + pub signature: std::string::String, + /// Transaction slot stored as SQL `BIGINT`. + pub slot: i64, + /// Program id that produced the return data. + pub program_id: std::string::String, + /// Exact return bytes encoded as standard base64. + pub data_base64: std::string::String, /// Insert timestamp. pub created_at: chrono::DateTime, } diff --git a/ks-store/src/contracts/entity/raw.rs b/ks-store/src/contracts/entity/raw.rs index 4f489d5..c48ef01 100644 --- a/ks-store/src/contracts/entity/raw.rs +++ b/ks-store/src/contracts/entity/raw.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/entity/raw.rs -// version: 3 +// version: 4 //! Canonical Solana transaction and acquisition observation persisted entities. @@ -12,6 +12,8 @@ pub struct RawTransactionRow { pub signature: std::string::String, /// Transaction slot stored as SQL `BIGINT`. pub slot: i64, + /// Optional block timestamp as Unix seconds supplied by Solana. + pub block_time: std::option::Option, /// Canonical source-independent transaction document while retained in the hot store. pub canonical_json: std::option::Option, /// Optional deterministic digest of the canonical document. @@ -50,7 +52,7 @@ pub struct TransactionObservationRow { /// Acquisition method, for example `getTransaction`, `transactionSubscribe` or `transactions`. pub acquisition_method: std::string::String, /// Acquisition origin category. - pub origin: crate::TransactionObservationOrigin, + pub origin: crate::AcquisitionObservationOrigin, /// Optional Solana commitment. pub commitment: std::option::Option, /// Optional capture session identifier. @@ -70,9 +72,70 @@ pub struct TransactionObservationRow { /// Optional digest of the source-specific payload without retaining that payload. pub source_payload_hash: std::option::Option, /// Current observation status. - pub status: crate::TransactionObservationStatus, + pub status: crate::AcquisitionObservationStatus, /// Optional machine-readable error code. pub error_code: std::option::Option, /// Optional diagnostic error message. pub error_message: std::option::Option, } + +/// Generic account acquisition observation persisted row contract. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct AccountObservationRow { + /// Technical primary key. + pub id: i64, + /// Stable observation key. + pub observation_key: std::string::String, + /// Observed account public key. + pub account_key: std::string::String, + /// Context slot stored as SQL `BIGINT`. + pub context_slot: i64, + /// Account owner when present. + pub owner: std::option::Option, + /// Exact lamports rendered as decimal text when present. + pub lamports: std::option::Option, + /// Executable flag when present. + pub executable: std::option::Option, + /// Exact rent epoch rendered as decimal text when present. + pub rent_epoch: std::option::Option, + /// Account data length when present. + pub space: std::option::Option, + /// Exact account bytes encoded as standard base64 when captured. + pub data_base64: std::option::Option, + /// Deterministic digest of decoded account bytes when captured. + pub data_hash: std::option::Option, + /// Acquisition provider code. + pub provider: std::string::String, + /// Optional endpoint code. + pub endpoint_code: std::option::Option, + /// Acquisition protocol. + pub protocol: std::string::String, + /// Acquisition method. + pub acquisition_method: std::string::String, + /// Optional commitment. + pub commitment: std::option::Option, + /// Optional capture session id. + pub capture_session_id: std::option::Option, + /// Optional configured filter code. + pub filter_code: std::option::Option, + /// Acquisition origin. + pub origin: crate::AcquisitionObservationOrigin, + /// Optional detection timestamp. + pub detected_at: std::option::Option>, + /// Receive timestamp. + pub received_at: chrono::DateTime, + /// Optional normalization timestamp. + pub normalized_at: std::option::Option>, + /// Persist timestamp. + pub persisted_at: chrono::DateTime, + /// Optional uncompressed source payload size. + pub payload_size_bytes: std::option::Option, + /// Optional source-specific payload digest. + pub source_payload_hash: std::option::Option, + /// Technical observation status. + pub status: crate::AcquisitionObservationStatus, + /// Optional error code. + pub error_code: std::option::Option, + /// Optional diagnostic message. + pub error_message: std::option::Option, +} diff --git a/ks-store/src/contracts/health.rs b/ks-store/src/contracts/health.rs index 4fd68b8..27e64ae 100644 --- a/ks-store/src/contracts/health.rs +++ b/ks-store/src/contracts/health.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/health.rs -// version: 2 +// version: 3 //! Backend-neutral health contracts for storage implementations. @@ -22,16 +22,16 @@ pub struct StoreHealthSnapshot { /// Stable backend code such as `postgres` or `sqlite`. pub backend: std::string::String, /// Current health status. - pub status: StoreHealthStatus, + pub status: crate::StoreHealthStatus, /// Optional human-readable diagnostic message. pub message: std::option::Option, } -impl StoreHealthSnapshot { +impl crate::StoreHealthSnapshot { /// Builds a store health snapshot after minimal validation. pub fn new( backend: impl std::convert::Into, - status: StoreHealthStatus, + status: crate::StoreHealthStatus, message: std::option::Option, ) -> ks_core::Result { let backend_value = backend.into(); diff --git a/ks-store/src/contracts/pagination.rs b/ks-store/src/contracts/pagination.rs index 015d9ec..0805c09 100644 --- a/ks-store/src/contracts/pagination.rs +++ b/ks-store/src/contracts/pagination.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/pagination.rs -// version: 2 +// version: 3 //! Backend-neutral pagination and sorting contracts for repository operations. @@ -26,7 +26,7 @@ pub struct PageRequest { pub offset: u64, } -impl PageRequest { +impl crate::PageRequest { /// Builds a page request after minimal bounds validation. pub fn new(limit: u16, offset: u64) -> ks_core::Result { if limit == 0 { diff --git a/ks-store/src/contracts/repository.rs b/ks-store/src/contracts/repository.rs index fd92587..cf8b43c 100644 --- a/ks-store/src/contracts/repository.rs +++ b/ks-store/src/contracts/repository.rs @@ -1,5 +1,5 @@ // file: ks-store/src/contracts/repository.rs -// version: 3 +// version: 4 //! Storage trait definitions shared by concrete stores. @@ -187,8 +187,8 @@ pub trait DecodePipelineStore { ) -> ks_core::Result>; /// Lists bounded materialized outputs for read-only application views. - async fn list_materialized_events( + async fn list_materialized_outputs( &self, - filter: &crate::MaterializedEventFilter, - ) -> ks_core::Result>; + filter: &crate::MaterializedOutputFilter, + ) -> ks_core::Result>; } diff --git a/ks-store/src/lib.rs b/ks-store/src/lib.rs index f219500..30c331a 100644 --- a/ks-store/src/lib.rs +++ b/ks-store/src/lib.rs @@ -1,5 +1,5 @@ // file: ks-store/src/lib.rs -// version: 10 +// version: 12 //! Backend-agnostic Solana storage contracts and store facade. #![warn(missing_docs)] @@ -13,8 +13,12 @@ mod store; /// Canonical crate-internal tracing target shared through the crate-root facade. pub(crate) use self::constants::TRACING_TARGET; +/// Crate-internal storage symbol `ACCOUNT_OBSERVATIONS_TABLE_NAME` shared through the crate-root facade. +pub(crate) use self::postgres::ACCOUNT_OBSERVATIONS_TABLE_NAME; /// Crate-internal storage symbol `CORE_ACCOUNT_KEYS_TABLE_NAME` shared through the crate-root facade. pub(crate) use self::postgres::CORE_ACCOUNT_KEYS_TABLE_NAME; +/// Crate-internal storage symbol `CORE_ACCOUNT_STATES_TABLE_NAME` shared through the crate-root facade. +pub(crate) use self::postgres::CORE_ACCOUNT_STATES_TABLE_NAME; /// Crate-internal storage symbol `CORE_BALANCE_CHANGES_TABLE_NAME` shared through the crate-root facade. pub(crate) use self::postgres::CORE_BALANCE_CHANGES_TABLE_NAME; /// Crate-internal storage symbol `CORE_INNER_INSTRUCTIONS_TABLE_NAME` shared through the crate-root facade. @@ -23,6 +27,8 @@ pub(crate) use self::postgres::CORE_INNER_INSTRUCTIONS_TABLE_NAME; pub(crate) use self::postgres::CORE_INSTRUCTIONS_TABLE_NAME; /// Crate-internal storage symbol `CORE_LOGS_TABLE_NAME` shared through the crate-root facade. pub(crate) use self::postgres::CORE_LOGS_TABLE_NAME; +/// Crate-internal storage symbol `CORE_RETURN_DATA_TABLE_NAME` shared through the crate-root facade. +pub(crate) use self::postgres::CORE_RETURN_DATA_TABLE_NAME; /// Crate-internal storage symbol `CORE_TRANSACTIONS_TABLE_NAME` shared through the crate-root facade. pub(crate) use self::postgres::CORE_TRANSACTIONS_TABLE_NAME; /// Crate-internal storage symbol `DECODE_COVERAGE_DECLARATIONS_TABLE_NAME` shared through the crate-root facade. @@ -31,8 +37,10 @@ pub(crate) use self::postgres::DECODE_COVERAGE_DECLARATIONS_TABLE_NAME; pub(crate) use self::postgres::DECODE_COVERAGE_OBSERVATIONS_TABLE_NAME; /// Crate-internal storage symbol `DECODE_EVENTS_TABLE_NAME` shared through the crate-root facade. pub(crate) use self::postgres::DECODE_EVENTS_TABLE_NAME; -/// Crate-internal storage symbol `MATERIALIZED_EVENTS_TABLE_NAME` shared through the crate-root facade. -pub(crate) use self::postgres::MATERIALIZED_EVENTS_TABLE_NAME; +/// Crate-internal storage symbol `LEGACY_SOLANA_TABLE_PREFIX` shared through the crate-root facade. +pub(crate) use self::postgres::LEGACY_SOLANA_TABLE_PREFIX; +/// Crate-internal storage symbol `MATERIALIZED_OUTPUTS_TABLE_NAME` shared through the crate-root facade. +pub(crate) use self::postgres::MATERIALIZED_OUTPUTS_TABLE_NAME; /// Crate-internal storage symbol `PROCESSING_LEDGER_TABLE_NAME` shared through the crate-root facade. pub(crate) use self::postgres::PROCESSING_LEDGER_TABLE_NAME; /// Crate-internal storage symbol `PostgresStore` shared through the crate-root facade. @@ -47,12 +55,8 @@ pub(crate) use self::postgres::RAW_TRANSACTIONS_TABLE_NAME; pub(crate) use self::postgres::STORE_SCHEMA_ADVISORY_LOCK_ID; /// Crate-internal storage symbol `TRANSACTION_OBSERVATIONS_TABLE_NAME` shared through the crate-root facade. pub(crate) use self::postgres::TRANSACTION_OBSERVATIONS_TABLE_NAME; -/// Crate-internal storage symbol `apply_core_store_schema` shared through the crate-root facade. -pub(crate) use self::postgres::apply_core_store_schema; -/// Crate-internal storage symbol `apply_decode_store_schema` shared through the crate-root facade. -pub(crate) use self::postgres::apply_decode_store_schema; -/// Crate-internal storage symbol `apply_raw_store_schema` shared through the crate-root facade. -pub(crate) use self::postgres::apply_raw_store_schema; +/// Crate-internal complete PostgreSQL baseline initializer shared through the crate-root facade. +pub(crate) use self::postgres::apply_store_schema; /// Crate-internal storage symbol `core_store_schema_statements` shared through the crate-root facade. pub(crate) use self::postgres::core_store_schema_statements; /// Crate-internal storage symbol `core_store_table_diagnostic_specs` shared through the crate-root facade. @@ -61,6 +65,8 @@ pub(crate) use self::postgres::core_store_table_diagnostic_specs; pub(crate) use self::postgres::decode_store_schema_statements; /// Crate-internal storage symbol `decode_store_table_diagnostic_specs` shared through the crate-root facade. pub(crate) use self::postgres::decode_store_table_diagnostic_specs; +/// Crate-internal expected PostgreSQL index inventory shared through the crate-root facade. +pub(crate) use self::postgres::expected_postgres_index_names; /// Crate-internal storage symbol `has_raw_transaction_signature` shared through the crate-root facade. pub(crate) use self::postgres::has_raw_transaction_signature; /// Crate-internal storage symbol `has_transaction_observation_key` shared through the crate-root facade. @@ -95,8 +101,8 @@ pub(crate) use self::postgres::list_decode_coverage_summary; pub(crate) use self::postgres::list_decode_inputs; /// Crate-internal storage symbol `list_decode_replay_inputs` shared through the crate-root facade. pub(crate) use self::postgres::list_decode_replay_inputs; -/// Crate-internal storage symbol `list_materialized_events` shared through the crate-root facade. -pub(crate) use self::postgres::list_materialized_events; +/// Crate-internal storage symbol `list_materialized_outputs` shared through the crate-root facade. +pub(crate) use self::postgres::list_materialized_outputs; /// Crate-internal storage symbol `list_raw_transactions_for_core_extraction` shared through the crate-root facade. pub(crate) use self::postgres::list_raw_transactions_for_core_extraction; /// Crate-internal storage symbol `list_replay_entity_summaries` shared through the crate-root facade. @@ -107,10 +113,8 @@ pub(crate) use self::postgres::list_replay_program_summaries; pub(crate) use self::postgres::list_replay_transaction_candidates; /// Crate-internal storage symbol `load_current_schema` shared through the crate-root facade. pub(crate) use self::postgres::load_current_schema; -/// Crate-internal storage symbol `load_latest_migration_version` shared through the crate-root facade. -pub(crate) use self::postgres::load_latest_migration_version; -/// Crate-internal storage symbol `load_migration_table_name` shared through the crate-root facade. -pub(crate) use self::postgres::load_migration_table_name; +/// Crate-internal expected PostgreSQL index counts shared through the crate-root facade. +pub(crate) use self::postgres::load_expected_index_counts; /// Crate-internal storage symbol `load_server_version` shared through the crate-root facade. pub(crate) use self::postgres::load_server_version; /// Crate-internal storage symbol `load_table_statistics` shared through the crate-root facade. @@ -138,51 +142,65 @@ pub(crate) use self::postgres::raw_store_table_diagnostic_specs; pub(crate) use self::postgres::run_health_check; /// Crate-internal storage symbol `table_exists` shared through the crate-root facade. pub(crate) use self::postgres::table_exists; -/// Crate-internal storage symbol `table_stats_kb_sol_core_account_keys_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_core_account_keys_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_core_balance_changes_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_core_balance_changes_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_core_inner_instructions_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_core_inner_instructions_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_core_instructions_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_core_instructions_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_core_logs_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_core_logs_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_core_transactions_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_core_transactions_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_decode_coverage_declarations_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_decode_coverage_declarations_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_decode_coverage_observations_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_decode_coverage_observations_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_decode_events_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_decode_events_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_mat_events_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_mat_events_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_obs_transaction_observations_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_obs_transaction_observations_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_ops_processing_ledger_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_ops_processing_ledger_sql; -/// Crate-internal storage symbol `table_stats_kb_sol_raw_transactions_sql` shared through the crate-root facade. -pub(crate) use self::postgres::table_stats_kb_sol_raw_transactions_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_account_keys_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_account_keys_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_account_states_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_account_states_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_balance_changes_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_balance_changes_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_inner_instructions_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_inner_instructions_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_instructions_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_instructions_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_logs_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_logs_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_return_data_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_return_data_sql; +/// Crate-internal storage symbol `table_stats_k_sol_core_transactions_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_core_transactions_sql; +/// Crate-internal storage symbol `table_stats_k_sol_decode_coverage_declarations_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_decode_coverage_declarations_sql; +/// Crate-internal storage symbol `table_stats_k_sol_decode_coverage_observations_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_decode_coverage_observations_sql; +/// Crate-internal storage symbol `table_stats_k_sol_decode_events_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_decode_events_sql; +/// Crate-internal storage symbol `table_stats_k_sol_mat_outputs_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_mat_outputs_sql; +/// Crate-internal storage symbol `table_stats_k_sol_obs_account_observations_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_obs_account_observations_sql; +/// Crate-internal storage symbol `table_stats_k_sol_obs_transaction_observations_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_obs_transaction_observations_sql; +/// Crate-internal storage symbol `table_stats_k_sol_ops_processing_ledger_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_ops_processing_ledger_sql; +/// Crate-internal storage symbol `table_stats_k_sol_raw_transactions_sql` shared through the crate-root facade. +pub(crate) use self::postgres::table_stats_k_sol_raw_transactions_sql; /// Crate-internal storage symbol `update_core_instruction_lifecycle` shared through the crate-root facade. pub(crate) use self::postgres::update_core_instruction_lifecycle; /// Crate-internal storage symbol `update_raw_payload_lifecycle` shared through the crate-root facade. pub(crate) use self::postgres::update_raw_payload_lifecycle; -/// Crate-internal storage symbol `validate_core_store_table_names` shared through the crate-root facade. -pub(crate) use self::postgres::validate_core_store_table_names; -/// Crate-internal storage symbol `validate_decode_store_table_names` shared through the crate-root facade. -pub(crate) use self::postgres::validate_decode_store_table_names; -/// Crate-internal storage symbol `validate_raw_store_table_names` shared through the crate-root facade. -pub(crate) use self::postgres::validate_raw_store_table_names; +/// Crate-internal PostgreSQL SQL-resource canary shared through the crate-root facade. +pub(crate) use self::postgres::validate_postgres_sql_resources; /// Transitional logical schema contract identifier used until the `0.5.3-pre.3` rebuild. pub use self::constants::STORE_SCHEMA_CONTRACT_VERSION; +/// Generic account acquisition observation insert contract. +pub use self::contracts::AccountObservationInsert; +/// Generic account acquisition observation persisted row contract. +pub use self::contracts::AccountObservationRow; +/// Transaction acquisition observation origin. +pub use self::contracts::AcquisitionObservationOrigin; +/// Generic account acquisition observation status. +pub use self::contracts::AcquisitionObservationStatus; /// Core account key insert contract. pub use self::contracts::CoreAccountKeyInsert; /// Core account key persisted row contract. pub use self::contracts::CoreAccountKeyRow; /// Core account key source category. pub use self::contracts::CoreAccountKeySource; +/// Core account-state insert contract. +pub use self::contracts::CoreAccountStateInsert; +/// Core account-state persisted row contract. +pub use self::contracts::CoreAccountStateRow; /// Core balance change insert contract. pub use self::contracts::CoreBalanceChangeInsert; /// Core balance change kind. @@ -215,6 +233,10 @@ pub use self::contracts::CoreInstructionRow; pub use self::contracts::CoreLogInsert; /// Core log persisted row contract. pub use self::contracts::CoreLogRow; +/// Core return-data insert contract. +pub use self::contracts::CoreReturnDataInsert; +/// Core return-data persisted row contract. +pub use self::contracts::CoreReturnDataRow; /// Core transaction insert contract. pub use self::contracts::CoreTransactionInsert; /// Core transaction persisted row contract. @@ -237,24 +259,26 @@ pub use self::contracts::DecodeObservationInsert; pub use self::contracts::DecodePersistenceBundle; /// Contextual instruction decode and materialization storage behavior. pub use self::contracts::DecodePipelineStore; +/// Source-neutral external decode schema provenance. +pub use self::contracts::DecodeSchemaProvenance; /// Bounded contextual instruction selection filter for decode campaigns. pub use self::contracts::DecodeSelectionFilter; /// Insert or upsert result contract returned by repositories. pub use self::contracts::InsertOutcome; /// Maximum number of materialized rows returned by one bounded query. -pub use self::contracts::MAX_MATERIALIZED_EVENT_QUERY_ROWS; +pub use self::contracts::MAX_MATERIALIZED_OUTPUT_QUERY_ROWS; /// Maximum repository page size. pub use self::contracts::MAX_PAGE_SIZE; /// Maximum number of rows returned by one replay candidate query. pub use self::contracts::MAX_REPLAY_CANDIDATE_ROWS; /// Atomic persistence bundle for one materializer and one decoded observation. pub use self::contracts::MaterializationPersistenceBundle; -/// Bounded read-only materialized event selection. -pub use self::contracts::MaterializedEventFilter; -/// One materialized output returned by a bounded query. -pub use self::contracts::MaterializedEventQueryRow; +/// Bounded read-only materialized output selection. +pub use self::contracts::MaterializedOutputFilter; /// One processor-owned generic materialized output row. pub use self::contracts::MaterializedOutputInsert; +/// One materialized output returned by a bounded query. +pub use self::contracts::MaterializedOutputQueryRow; /// Page request contract for repository list operations. pub use self::contracts::PageRequest; /// Stable processing ledger identity. @@ -325,12 +349,8 @@ pub use self::contracts::StoreResourceStatistics; pub use self::contracts::StoreRuntimeSummary; /// Transaction acquisition observation insert contract. pub use self::contracts::TransactionObservationInsert; -/// Transaction acquisition observation origin. -pub use self::contracts::TransactionObservationOrigin; /// Transaction acquisition observation persisted row contract. pub use self::contracts::TransactionObservationRow; -/// Transaction acquisition observation status. -pub use self::contracts::TransactionObservationStatus; /// Storage error helper functions. pub use self::contracts::storage_contract_error; /// Backend-agnostic persistent store facade. diff --git a/ks-store/src/postgres.rs b/ks-store/src/postgres.rs index 117eca7..c0f831a 100644 --- a/ks-store/src/postgres.rs +++ b/ks-store/src/postgres.rs @@ -1,5 +1,5 @@ // file: ks-store/src/postgres.rs -// version: 11 +// version: 13 //! Private PostgreSQL implementation of the backend-agnostic store contracts. @@ -10,16 +10,20 @@ mod store; #[cfg(test)] mod test_serial; +pub(crate) use self::migrations::ACCOUNT_OBSERVATIONS_TABLE_NAME; pub(crate) use self::migrations::CORE_ACCOUNT_KEYS_TABLE_NAME; +pub(crate) use self::migrations::CORE_ACCOUNT_STATES_TABLE_NAME; pub(crate) use self::migrations::CORE_BALANCE_CHANGES_TABLE_NAME; pub(crate) use self::migrations::CORE_INNER_INSTRUCTIONS_TABLE_NAME; pub(crate) use self::migrations::CORE_INSTRUCTIONS_TABLE_NAME; pub(crate) use self::migrations::CORE_LOGS_TABLE_NAME; +pub(crate) use self::migrations::CORE_RETURN_DATA_TABLE_NAME; pub(crate) use self::migrations::CORE_TRANSACTIONS_TABLE_NAME; pub(crate) use self::migrations::DECODE_COVERAGE_DECLARATIONS_TABLE_NAME; pub(crate) use self::migrations::DECODE_COVERAGE_OBSERVATIONS_TABLE_NAME; pub(crate) use self::migrations::DECODE_EVENTS_TABLE_NAME; -pub(crate) use self::migrations::MATERIALIZED_EVENTS_TABLE_NAME; +pub(crate) use self::migrations::LEGACY_SOLANA_TABLE_PREFIX; +pub(crate) use self::migrations::MATERIALIZED_OUTPUTS_TABLE_NAME; pub(crate) use self::migrations::PROCESSING_LEDGER_TABLE_NAME; pub(crate) use self::migrations::PostgresTableDiagnosticSpec; pub(crate) use self::migrations::RAW_TRANSACTIONS_TABLE_NAME; @@ -29,27 +33,27 @@ pub(crate) use self::migrations::core_store_schema_statements; pub(crate) use self::migrations::core_store_table_diagnostic_specs; pub(crate) use self::migrations::decode_store_schema_statements; pub(crate) use self::migrations::decode_store_table_diagnostic_specs; +pub(crate) use self::migrations::expected_postgres_index_names; pub(crate) use self::migrations::raw_store_schema_statements; pub(crate) use self::migrations::raw_store_table_diagnostic_specs; -pub(crate) use self::migrations::table_stats_kb_sol_core_account_keys_sql; -pub(crate) use self::migrations::table_stats_kb_sol_core_balance_changes_sql; -pub(crate) use self::migrations::table_stats_kb_sol_core_inner_instructions_sql; -pub(crate) use self::migrations::table_stats_kb_sol_core_instructions_sql; -pub(crate) use self::migrations::table_stats_kb_sol_core_logs_sql; -pub(crate) use self::migrations::table_stats_kb_sol_core_transactions_sql; -pub(crate) use self::migrations::table_stats_kb_sol_decode_coverage_declarations_sql; -pub(crate) use self::migrations::table_stats_kb_sol_decode_coverage_observations_sql; -pub(crate) use self::migrations::table_stats_kb_sol_decode_events_sql; -pub(crate) use self::migrations::table_stats_kb_sol_mat_events_sql; -pub(crate) use self::migrations::table_stats_kb_sol_obs_transaction_observations_sql; -pub(crate) use self::migrations::table_stats_kb_sol_ops_processing_ledger_sql; -pub(crate) use self::migrations::table_stats_kb_sol_raw_transactions_sql; -pub(crate) use self::migrations::validate_core_store_table_names; -pub(crate) use self::migrations::validate_decode_store_table_names; -pub(crate) use self::migrations::validate_raw_store_table_names; -pub(crate) use self::query::apply_core_store_schema; -pub(crate) use self::query::apply_decode_store_schema; -pub(crate) use self::query::apply_raw_store_schema; +pub(crate) use self::migrations::table_stats_k_sol_core_account_keys_sql; +pub(crate) use self::migrations::table_stats_k_sol_core_account_states_sql; +pub(crate) use self::migrations::table_stats_k_sol_core_balance_changes_sql; +pub(crate) use self::migrations::table_stats_k_sol_core_inner_instructions_sql; +pub(crate) use self::migrations::table_stats_k_sol_core_instructions_sql; +pub(crate) use self::migrations::table_stats_k_sol_core_logs_sql; +pub(crate) use self::migrations::table_stats_k_sol_core_return_data_sql; +pub(crate) use self::migrations::table_stats_k_sol_core_transactions_sql; +pub(crate) use self::migrations::table_stats_k_sol_decode_coverage_declarations_sql; +pub(crate) use self::migrations::table_stats_k_sol_decode_coverage_observations_sql; +pub(crate) use self::migrations::table_stats_k_sol_decode_events_sql; +pub(crate) use self::migrations::table_stats_k_sol_mat_outputs_sql; +pub(crate) use self::migrations::table_stats_k_sol_obs_account_observations_sql; +pub(crate) use self::migrations::table_stats_k_sol_obs_transaction_observations_sql; +pub(crate) use self::migrations::table_stats_k_sol_ops_processing_ledger_sql; +pub(crate) use self::migrations::table_stats_k_sol_raw_transactions_sql; +pub(crate) use self::migrations::validate_postgres_sql_resources; +pub(crate) use self::query::apply_store_schema; pub(crate) use self::query::has_raw_transaction_signature; pub(crate) use self::query::has_transaction_observation_key; pub(crate) use self::query::insert_core_account_keys; @@ -67,14 +71,13 @@ pub(crate) use self::query::list_core_instructions_for_replay; pub(crate) use self::query::list_decode_coverage_summary; pub(crate) use self::query::list_decode_inputs; pub(crate) use self::query::list_decode_replay_inputs; -pub(crate) use self::query::list_materialized_events; +pub(crate) use self::query::list_materialized_outputs; pub(crate) use self::query::list_raw_transactions_for_core_extraction; pub(crate) use self::query::list_replay_entity_summaries; pub(crate) use self::query::list_replay_program_summaries; pub(crate) use self::query::list_replay_transaction_candidates; pub(crate) use self::query::load_current_schema; -pub(crate) use self::query::load_latest_migration_version; -pub(crate) use self::query::load_migration_table_name; +pub(crate) use self::query::load_expected_index_counts; pub(crate) use self::query::load_server_version; pub(crate) use self::query::load_table_statistics; pub(crate) use self::query::mark_core_extraction_failed; diff --git a/ks-store/src/postgres/migrations.rs b/ks-store/src/postgres/migrations.rs index 31b0c28..10ba774 100644 --- a/ks-store/src/postgres/migrations.rs +++ b/ks-store/src/postgres/migrations.rs @@ -1,44 +1,57 @@ // file: ks-store/src/postgres/migrations.rs -// version: 7 +// version: 10 -//! PostgreSQL migration conventions for the storage backend. +//! PostgreSQL schema resources and migration conventions for the storage backend. /// Canonical Solana table prefix. -const SOLANA_TABLE_PREFIX: &str = "kb_sol_"; +#[cfg(test)] +const SOLANA_TABLE_PREFIX: &str = "k_sol_"; +/// Historical table prefix rejected by the `0.5.3` baseline initializer. +pub(crate) const LEGACY_SOLANA_TABLE_PREFIX: &str = "kb_sol_"; /// Advisory lock id used while applying idempotent store schema statements. -pub(crate) const STORE_SCHEMA_ADVISORY_LOCK_ID: i64 = 2_024_000_204; -/// Canonical raw transaction table name. -pub(crate) const RAW_TRANSACTIONS_TABLE_NAME: &str = "kb_sol_raw_transactions"; -/// Lightweight transaction acquisition observation table name. -pub(crate) const TRANSACTION_OBSERVATIONS_TABLE_NAME: &str = "kb_sol_obs_transaction_observations"; -/// Core transaction table name. -pub(crate) const CORE_TRANSACTIONS_TABLE_NAME: &str = "kb_sol_core_transactions"; -/// Core account key table name. -pub(crate) const CORE_ACCOUNT_KEYS_TABLE_NAME: &str = "kb_sol_core_account_keys"; -/// Core instruction table name. -pub(crate) const CORE_INSTRUCTIONS_TABLE_NAME: &str = "kb_sol_core_instructions"; -/// Core inner instruction table name. -pub(crate) const CORE_INNER_INSTRUCTIONS_TABLE_NAME: &str = "kb_sol_core_inner_instructions"; -/// Core log table name. -pub(crate) const CORE_LOGS_TABLE_NAME: &str = "kb_sol_core_logs"; -/// Core balance change table name. -pub(crate) const CORE_BALANCE_CHANGES_TABLE_NAME: &str = "kb_sol_core_balance_changes"; -/// Processing ledger table name. -pub(crate) const PROCESSING_LEDGER_TABLE_NAME: &str = "kb_sol_ops_processing_ledger"; -/// Versioned decoded event table name. -pub(crate) const DECODE_EVENTS_TABLE_NAME: &str = "kb_sol_decode_events"; -/// Machine-readable decoder coverage declaration table name. +pub(crate) const STORE_SCHEMA_ADVISORY_LOCK_ID: i64 = 2_024_000_503; +/// PostgreSQL table `k_sol_raw_transactions`. +pub(crate) const RAW_TRANSACTIONS_TABLE_NAME: &str = "k_sol_raw_transactions"; +/// PostgreSQL table `k_sol_obs_transaction_observations`. +pub(crate) const TRANSACTION_OBSERVATIONS_TABLE_NAME: &str = "k_sol_obs_transaction_observations"; +/// PostgreSQL table `k_sol_obs_account_observations`. +pub(crate) const ACCOUNT_OBSERVATIONS_TABLE_NAME: &str = "k_sol_obs_account_observations"; +/// PostgreSQL table `k_sol_core_transactions`. +pub(crate) const CORE_TRANSACTIONS_TABLE_NAME: &str = "k_sol_core_transactions"; +/// PostgreSQL table `k_sol_core_account_keys`. +pub(crate) const CORE_ACCOUNT_KEYS_TABLE_NAME: &str = "k_sol_core_account_keys"; +/// PostgreSQL table `k_sol_core_instructions`. +pub(crate) const CORE_INSTRUCTIONS_TABLE_NAME: &str = "k_sol_core_instructions"; +/// PostgreSQL table `k_sol_core_inner_instructions`. +pub(crate) const CORE_INNER_INSTRUCTIONS_TABLE_NAME: &str = "k_sol_core_inner_instructions"; +/// PostgreSQL table `k_sol_core_logs`. +pub(crate) const CORE_LOGS_TABLE_NAME: &str = "k_sol_core_logs"; +/// PostgreSQL table `k_sol_core_balance_changes`. +pub(crate) const CORE_BALANCE_CHANGES_TABLE_NAME: &str = "k_sol_core_balance_changes"; +/// PostgreSQL table `k_sol_core_return_data`. +pub(crate) const CORE_RETURN_DATA_TABLE_NAME: &str = "k_sol_core_return_data"; +/// PostgreSQL table `k_sol_core_account_states`. +pub(crate) const CORE_ACCOUNT_STATES_TABLE_NAME: &str = "k_sol_core_account_states"; +/// PostgreSQL table `k_sol_ops_processing_ledger`. +pub(crate) const PROCESSING_LEDGER_TABLE_NAME: &str = "k_sol_ops_processing_ledger"; +/// PostgreSQL table `k_sol_decode_events`. +pub(crate) const DECODE_EVENTS_TABLE_NAME: &str = "k_sol_decode_events"; +/// PostgreSQL table `k_sol_decode_coverage_declarations`. pub(crate) const DECODE_COVERAGE_DECLARATIONS_TABLE_NAME: &str = - "kb_sol_decode_coverage_declarations"; -/// Observed decoder coverage classification table name. + "k_sol_decode_coverage_declarations"; +/// PostgreSQL table `k_sol_decode_coverage_observations`. pub(crate) const DECODE_COVERAGE_OBSERVATIONS_TABLE_NAME: &str = - "kb_sol_decode_coverage_observations"; -/// Versioned materialized output table name. -pub(crate) const MATERIALIZED_EVENTS_TABLE_NAME: &str = "kb_sol_mat_events"; -/// Canonical transaction acquisition table names active since `0.3.1`. -const RAW_STORE_TABLE_NAMES: &[&str] = - &[RAW_TRANSACTIONS_TABLE_NAME, TRANSACTION_OBSERVATIONS_TABLE_NAME]; -/// Core Solana table names introduced by `0.2.4`. + "k_sol_decode_coverage_observations"; +/// PostgreSQL table `k_sol_mat_outputs`. +pub(crate) const MATERIALIZED_OUTPUTS_TABLE_NAME: &str = "k_sol_mat_outputs"; + +#[cfg(test)] +const RAW_STORE_TABLE_NAMES: &[&str] = &[ + RAW_TRANSACTIONS_TABLE_NAME, + TRANSACTION_OBSERVATIONS_TABLE_NAME, + ACCOUNT_OBSERVATIONS_TABLE_NAME, +]; +#[cfg(test)] const CORE_STORE_TABLE_NAMES: &[&str] = &[ CORE_TRANSACTIONS_TABLE_NAME, CORE_ACCOUNT_KEYS_TABLE_NAME, @@ -46,18 +59,102 @@ const CORE_STORE_TABLE_NAMES: &[&str] = &[ CORE_INNER_INSTRUCTIONS_TABLE_NAME, CORE_LOGS_TABLE_NAME, CORE_BALANCE_CHANGES_TABLE_NAME, + CORE_RETURN_DATA_TABLE_NAME, + CORE_ACCOUNT_STATES_TABLE_NAME, PROCESSING_LEDGER_TABLE_NAME, ]; -/// Decode and materialization table names introduced by `0.4.0`. +#[cfg(test)] const DECODE_STORE_TABLE_NAMES: &[&str] = &[ DECODE_EVENTS_TABLE_NAME, DECODE_COVERAGE_DECLARATIONS_TABLE_NAME, DECODE_COVERAGE_OBSERVATIONS_TABLE_NAME, - MATERIALIZED_EVENTS_TABLE_NAME, + MATERIALIZED_OUTPUTS_TABLE_NAME, ]; -/// Allowed Solana table domains encoded after the `kb_sol_` prefix. +#[cfg(test)] const ALLOWED_SOLANA_TABLE_DOMAINS: &[&str] = &["raw", "core", "obs", "decode", "mat", "catalog", "agg", "ops", "wallet"]; +const EXPECTED_POSTGRES_INDEX_NAMES: &[&str] = &[ + "ix_k_sol_core_account_keys_key", + "ix_k_sol_core_account_states_account_slot", + "ix_k_sol_core_account_states_owner", + "ix_k_sol_core_balance_changes_account", + "ix_k_sol_core_balance_changes_mint", + "ix_k_sol_core_balance_changes_owner", + "ix_k_sol_core_inner_instructions_parent", + "ix_k_sol_core_inner_instructions_processing", + "ix_k_sol_core_inner_instructions_program", + "ix_k_sol_core_instructions_processing", + "ix_k_sol_core_instructions_program", + "ix_k_sol_core_instructions_slot", + "ix_k_sol_core_logs_path", + "ix_k_sol_core_logs_program", + "ix_k_sol_core_return_data_program", + "ix_k_sol_core_transactions_created_at", + "ix_k_sol_core_transactions_slot", + "ix_k_sol_decode_coverage_declarations_program", + "ix_k_sol_decode_coverage_observations_entry", + "ix_k_sol_decode_coverage_observations_program_status", + "ix_k_sol_decode_events_family_commit", + "ix_k_sol_decode_events_program_surface", + "ix_k_sol_decode_events_signature_path", + "ix_k_sol_mat_outputs_processor_family_slot", + "ix_k_sol_mat_outputs_signature_family", + "ix_k_sol_obs_account_observations_account_slot", + "ix_k_sol_obs_account_observations_provider_method", + "ix_k_sol_obs_account_observations_received_at", + "ix_k_sol_obs_account_observations_status", + "ix_k_sol_obs_transaction_observations_provider_method", + "ix_k_sol_obs_transaction_observations_raw_transaction", + "ix_k_sol_obs_transaction_observations_received_at", + "ix_k_sol_obs_transaction_observations_signature", + "ix_k_sol_obs_transaction_observations_slot", + "ix_k_sol_obs_transaction_observations_status", + "ix_k_sol_ops_processing_ledger_input_hash", + "ix_k_sol_ops_processing_ledger_stage_input", + "ix_k_sol_ops_processing_ledger_status", + "ix_k_sol_raw_transactions_canonical_hash", + "ix_k_sol_raw_transactions_created_at", + "ix_k_sol_raw_transactions_processing", + "ix_k_sol_raw_transactions_processing_slot", + "ix_k_sol_raw_transactions_slot", + "pk_k_sol_core_account_keys", + "pk_k_sol_core_account_states", + "pk_k_sol_core_balance_changes", + "pk_k_sol_core_inner_instructions", + "pk_k_sol_core_instructions", + "pk_k_sol_core_logs", + "pk_k_sol_core_return_data", + "pk_k_sol_core_transactions", + "pk_k_sol_decode_coverage_declarations", + "pk_k_sol_decode_coverage_observations", + "pk_k_sol_decode_events", + "pk_k_sol_mat_outputs", + "pk_k_sol_obs_account_observations", + "pk_k_sol_obs_transaction_observations", + "pk_k_sol_ops_processing_ledger", + "pk_k_sol_raw_transactions", + "ux_k_sol_core_account_keys_sig_index", + "ux_k_sol_core_account_states_observation", + "ux_k_sol_core_balance_changes_sig_index", + "ux_k_sol_core_inner_instructions_sig_path", + "ux_k_sol_core_instructions_sig_path", + "ux_k_sol_core_logs_sig_index", + "ux_k_sol_core_return_data_signature", + "ux_k_sol_core_transactions_signature", + "ux_k_sol_decode_coverage_declarations_identity", + "ux_k_sol_decode_coverage_observations_identity", + "ux_k_sol_decode_events_processor_input_event", + "ux_k_sol_mat_outputs_processor_input_output", + "ux_k_sol_obs_account_observations_key", + "ux_k_sol_obs_transaction_observations_key", + "ux_k_sol_ops_processing_ledger_identity", + "ux_k_sol_raw_transactions_signature", +]; + +/// Physical PostgreSQL index names expected by the baseline, kept backend-private. +pub(crate) fn expected_postgres_index_names() -> &'static [&'static str] { + return EXPECTED_POSTGRES_INDEX_NAMES; +} /// Diagnostic metadata for one expected PostgreSQL table. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -72,9 +169,10 @@ pub(crate) struct PostgresTableDiagnosticSpec { pub role: &'static str, } -/// Diagnostic metadata for canonical transaction acquisition tables. -pub(crate) fn raw_store_table_diagnostic_specs() -> [crate::PostgresTableDiagnosticSpec; 2] { - return [ +/// Diagnostic metadata for raw store tables. +pub(crate) fn raw_store_table_diagnostic_specs() -> std::vec::Vec +{ + return vec![ crate::PostgresTableDiagnosticSpec { table_name: RAW_TRANSACTIONS_TABLE_NAME, resource_code: "raw_transactions", @@ -87,12 +185,19 @@ pub(crate) fn raw_store_table_diagnostic_specs() -> [crate::PostgresTableDiagnos model_code: "raw", role: "Transaction acquisition observations", }, + crate::PostgresTableDiagnosticSpec { + table_name: ACCOUNT_OBSERVATIONS_TABLE_NAME, + resource_code: "account_observations", + model_code: "raw", + role: "Account acquisition observations", + }, ]; } -/// Diagnostic metadata for core Solana store tables. -pub(crate) fn core_store_table_diagnostic_specs() -> [crate::PostgresTableDiagnosticSpec; 7] { - return [ +/// Diagnostic metadata for core store tables. +pub(crate) fn core_store_table_diagnostic_specs() +-> std::vec::Vec { + return vec![ crate::PostgresTableDiagnosticSpec { table_name: CORE_TRANSACTIONS_TABLE_NAME, resource_code: "transactions", @@ -129,6 +234,18 @@ pub(crate) fn core_store_table_diagnostic_specs() -> [crate::PostgresTableDiagno model_code: "core", role: "Balance changes", }, + crate::PostgresTableDiagnosticSpec { + table_name: CORE_RETURN_DATA_TABLE_NAME, + resource_code: "return_data", + model_code: "core", + role: "Transaction return data", + }, + crate::PostgresTableDiagnosticSpec { + table_name: CORE_ACCOUNT_STATES_TABLE_NAME, + resource_code: "account_states", + model_code: "core", + role: "Canonical account state observations", + }, crate::PostgresTableDiagnosticSpec { table_name: PROCESSING_LEDGER_TABLE_NAME, resource_code: "processing_ledger", @@ -138,9 +255,10 @@ pub(crate) fn core_store_table_diagnostic_specs() -> [crate::PostgresTableDiagno ]; } -/// Diagnostic metadata for decode and materialization store tables. -pub(crate) fn decode_store_table_diagnostic_specs() -> [crate::PostgresTableDiagnosticSpec; 4] { - return [ +/// Diagnostic metadata for decode store tables. +pub(crate) fn decode_store_table_diagnostic_specs() +-> std::vec::Vec { + return vec![ crate::PostgresTableDiagnosticSpec { table_name: DECODE_EVENTS_TABLE_NAME, resource_code: "decoded_observations", @@ -160,7 +278,7 @@ pub(crate) fn decode_store_table_diagnostic_specs() -> [crate::PostgresTableDiag role: "Observed decoder coverage", }, crate::PostgresTableDiagnosticSpec { - table_name: MATERIALIZED_EVENTS_TABLE_NAME, + table_name: MATERIALIZED_OUTPUTS_TABLE_NAME, resource_code: "materialized_outputs", model_code: "materialization", role: "Versioned materialized outputs", @@ -168,978 +286,1985 @@ pub(crate) fn decode_store_table_diagnostic_specs() -> [crate::PostgresTableDiag ]; } -/// Idempotent SQL statements creating or upgrading the canonical transaction acquisition store. +fn create_pg_table_if_not_exists_k_sol_core_account_keys() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_keys.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_core_account_states() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_account_states.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_core_balance_changes() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_balance_changes.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_core_inner_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_inner_instructions.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_core_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_instructions.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_core_logs() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_logs.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_core_return_data() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_return_data.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_core_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_core_transactions.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_decode_coverage_declarations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_declarations.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_decode_coverage_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_coverage_observations.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_decode_events() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_decode_events.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_mat_outputs() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_mat_outputs.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_obs_account_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_account_observations.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_obs_transaction_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_obs_transaction_observations.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_ops_processing_ledger() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_ops_processing_ledger.sql" + ); +} + +fn create_pg_table_if_not_exists_k_sol_raw_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/tables/create_table_if_not_exists_k_sol_raw_transactions.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_index_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_index_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_key_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_key_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_signature_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_source() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_keys_source.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_account_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_account_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_hash_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_hash_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_lamports_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_lamports_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_owner_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_owner_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_rent_epoch_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_rent_epoch_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_slot_non_negative() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_space_non_negative() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_account_states_space_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_index_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_index_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_index_non_negative() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_index_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_kind() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_kind.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_mint_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_mint_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_owner_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_owner_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_signature_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_slot_non_negative() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_balance_changes_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_parent_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_parent_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_path_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_path_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processing_state() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processing_state.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processor_pair() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processor_pair.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_program_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_program_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_signature_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_slot_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_stack_height_positive() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_inner_instructions_stack_height_positive.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_path_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_path_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_processing_state() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processing_state.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_processor_pair() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_processor_pair.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_program_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_program_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_signature_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_stack_height_positive() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_instructions_stack_height_positive.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_logs_index_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_index_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_logs_path_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_path_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_logs_program_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_program_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_logs_signature_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_logs_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_logs_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_return_data_program_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_program_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_return_data_signature_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_return_data_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_return_data_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_transactions_signature_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_core_transactions_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_core_transactions_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_counts_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_counts_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_slot_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_decode_events_failed_not_committed() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_failed_not_committed.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_decode_events_schema_provenance() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_schema_provenance.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_decode_events_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_decode_events_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_mat_outputs_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_mat_outputs_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_account_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_account_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_commitment_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_commitment_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_endpoint_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_endpoint_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_code_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_code_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_message_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_message_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_filter_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_filter_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_hash_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_hash_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_key_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_key_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_lamports_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_lamports_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_method_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_method_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_origin() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_origin.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_owner_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_owner_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_payload_size_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_payload_size_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_protocol_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_protocol_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_provider_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_provider_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_rent_epoch_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_rent_epoch_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_session_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_session_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_slot_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_source_hash_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_source_hash_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_space_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_space_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_state_shape() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_state_shape.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_status() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_account_observations_status.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_commitment_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_commitment_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_endpoint_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_endpoint_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_code_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_code_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_message_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_message_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_filter_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_filter_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_hash_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_hash_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_key_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_key_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_method_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_method_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_origin() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_origin.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_payload_size_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_payload_size_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_protocol_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_protocol_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_provider_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_provider_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_session_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_session_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_signature_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_slot_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_status() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_status.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_attempt_count_non_negative() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_attempt_count_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_code_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_code_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_message_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_message_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_hash_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_hash_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_key_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_key_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_name_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_name_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_version_not_empty() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_version_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_stage_not_empty() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_stage_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_status() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_status.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_format_version_positive() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_format_version_positive.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_full_has_json() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_full_has_json.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_processing_state() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_processing_state.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_retention_state() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_retention_state.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_signature_not_empty() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_signature_not_empty.sql" + ); +} + +fn add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_slot_non_negative() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_ck_k_sol_raw_transactions_slot_non_negative.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_account_keys_transaction() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_keys_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_account_states_observation() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_account_states_observation.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_balance_changes_transaction() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_balance_changes_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_inner_instructions_transaction() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_inner_instructions_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_instructions_transaction() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_instructions_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_logs_transaction() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_logs_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_return_data_transaction() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_return_data_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_core_transactions_raw_transaction() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_core_transactions_raw_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_fk_k_sol_obs_transaction_observations_raw_transaction() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_fk_k_sol_obs_transaction_observations_raw_transaction.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_account_keys() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_keys.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_account_states() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_account_states.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_balance_changes() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_balance_changes.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_inner_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_inner_instructions.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_instructions.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_logs() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_logs.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_return_data() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_return_data.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_core_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_core_transactions.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_decode_coverage_declarations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_declarations.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_decode_coverage_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_coverage_observations.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_decode_events() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_decode_events.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_mat_outputs() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_mat_outputs.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_obs_account_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_account_observations.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_obs_transaction_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_obs_transaction_observations.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_ops_processing_ledger() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_ops_processing_ledger.sql" + ); +} + +fn add_pg_constraint_if_not_exists_pk_k_sol_raw_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/constraints/add_constraint_if_not_exists_pk_k_sol_raw_transactions.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_account_keys_key() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_keys_key.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_account_states_account_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_account_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_account_states_owner() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_account_states_owner.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_balance_changes_account() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_account.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_balance_changes_mint() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_mint.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_balance_changes_owner() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_balance_changes_owner.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_inner_instructions_parent() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_parent.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_inner_instructions_processing() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_processing.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_inner_instructions_program() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_inner_instructions_program.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_instructions_processing() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_processing.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_instructions_program() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_program.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_instructions_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_instructions_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_logs_path() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_path.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_logs_program() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_logs_program.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_return_data_program() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_return_data_program.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_transactions_created_at() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_created_at.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_core_transactions_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_core_transactions_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_decode_coverage_declarations_program() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_declarations_program.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_decode_coverage_observations_entry() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_entry.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_decode_coverage_observations_program_status() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_coverage_observations_program_status.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_decode_events_family_commit() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_family_commit.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_decode_events_program_surface() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_program_surface.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_decode_events_signature_path() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_decode_events_signature_path.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_mat_outputs_processor_family_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_processor_family_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_mat_outputs_signature_family() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_mat_outputs_signature_family.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_account_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_account_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_provider_method() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_provider_method.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_received_at() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_received_at.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_status() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_account_observations_status.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_provider_method() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_provider_method.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_raw_transaction() +-> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_raw_transaction.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_received_at() -> &'static str +{ + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_received_at.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_signature() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_signature.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_status() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_obs_transaction_observations_status.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_ops_processing_ledger_input_hash() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_input_hash.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_ops_processing_ledger_stage_input() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_stage_input.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_ops_processing_ledger_status() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_ops_processing_ledger_status.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_raw_transactions_canonical_hash() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_canonical_hash.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_raw_transactions_created_at() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_created_at.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_raw_transactions_processing() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_raw_transactions_processing_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_processing_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ix_k_sol_raw_transactions_slot() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ix_k_sol_raw_transactions_slot.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_account_keys_sig_index() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_keys_sig_index.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_account_states_observation() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_account_states_observation.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_balance_changes_sig_index() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_balance_changes_sig_index.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_inner_instructions_sig_path() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_inner_instructions_sig_path.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_instructions_sig_path() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_instructions_sig_path.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_logs_sig_index() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_logs_sig_index.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_return_data_signature() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_return_data_signature.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_core_transactions_signature() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_core_transactions_signature.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_decode_coverage_declarations_identity() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_declarations_identity.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_decode_coverage_observations_identity() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_coverage_observations_identity.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_decode_events_processor_input_event() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_decode_events_processor_input_event.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_mat_outputs_processor_input_output() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_mat_outputs_processor_input_output.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_obs_account_observations_key() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_account_observations_key.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_obs_transaction_observations_key() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_obs_transaction_observations_key.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_ops_processing_ledger_identity() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_ops_processing_ledger_identity.sql" + ); +} + +fn create_pg_index_if_not_exists_ux_k_sol_raw_transactions_signature() -> &'static str { + return include_str!( + "../../migrations/postgres/schema/indexes/create_index_if_not_exists_ux_k_sol_raw_transactions_signature.sql" + ); +} + +fn truncate_pg_table_k_sol_core_account_keys() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_keys.sql" + ); +} + +fn truncate_pg_table_k_sol_core_account_states() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_account_states.sql" + ); +} + +fn truncate_pg_table_k_sol_core_balance_changes() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_balance_changes.sql" + ); +} + +fn truncate_pg_table_k_sol_core_inner_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_inner_instructions.sql" + ); +} + +fn truncate_pg_table_k_sol_core_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_instructions.sql" + ); +} + +fn truncate_pg_table_k_sol_core_logs() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_logs.sql" + ); +} + +fn truncate_pg_table_k_sol_core_return_data() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_return_data.sql" + ); +} + +fn truncate_pg_table_k_sol_core_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_core_transactions.sql" + ); +} + +fn truncate_pg_table_k_sol_decode_coverage_declarations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_declarations.sql" + ); +} + +fn truncate_pg_table_k_sol_decode_coverage_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_coverage_observations.sql" + ); +} + +fn truncate_pg_table_k_sol_decode_events() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_decode_events.sql" + ); +} + +fn truncate_pg_table_k_sol_mat_outputs() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_mat_outputs.sql" + ); +} + +fn truncate_pg_table_k_sol_obs_account_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_account_observations.sql" + ); +} + +fn truncate_pg_table_k_sol_obs_transaction_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_obs_transaction_observations.sql" + ); +} + +fn truncate_pg_table_k_sol_ops_processing_ledger() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_ops_processing_ledger.sql" + ); +} + +fn truncate_pg_table_k_sol_raw_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/truncate/truncate_table_k_sol_raw_transactions.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_account_keys() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_keys.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_account_states() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_account_states.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_balance_changes() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_balance_changes.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_inner_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_inner_instructions.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_instructions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_instructions.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_logs() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_logs.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_return_data() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_return_data.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_core_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_core_transactions.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_decode_coverage_declarations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_declarations.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_decode_coverage_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_coverage_observations.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_decode_events() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_decode_events.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_mat_outputs() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_mat_outputs.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_obs_account_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_account_observations.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_obs_transaction_observations() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_obs_transaction_observations.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_ops_processing_ledger() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_ops_processing_ledger.sql" + ); +} + +fn drop_pg_table_if_exists_k_sol_raw_transactions() -> &'static str { + return include_str!( + "../../migrations/postgres/maintenance/drop/drop_table_if_exists_k_sol_raw_transactions.sql" + ); +} + +/// Idempotent PostgreSQL statements establishing the raw store schema. pub(crate) fn raw_store_schema_statements() -> std::vec::Vec<&'static str> { return vec![ - crate::postgres::migrations::migrate_legacy_raw_table_name_sql(), - crate::postgres::migrations::migrate_legacy_raw_columns_sql(), - crate::postgres::migrations::migrate_legacy_raw_constraints_sql(), - crate::postgres::migrations::migrate_legacy_raw_indexes_sql(), - crate::postgres::migrations::migrate_legacy_core_lineage_sql(), - crate::postgres::migrations::create_table_kb_sol_raw_transactions_sql(), - crate::postgres::migrations::create_ux_kb_sol_raw_transactions_signature_sql(), - crate::postgres::migrations::create_ix_kb_sol_raw_transactions_slot_sql(), - crate::postgres::migrations::create_ix_kb_sol_raw_transactions_created_at_sql(), - crate::postgres::migrations::create_ix_kb_sol_raw_transactions_processing_sql(), - crate::postgres::migrations::create_ix_kb_sol_raw_transactions_canonical_hash_sql(), - crate::postgres::migrations::create_table_kb_sol_obs_transaction_observations_sql(), - crate::postgres::migrations::create_ux_kb_sol_obs_transaction_observations_key_sql(), - crate::postgres::migrations::create_ix_kb_sol_obs_transaction_observations_signature_sql(), - crate::postgres::migrations::create_ix_kb_sol_obs_transaction_observations_slot_sql(), - crate::postgres::migrations::create_ix_kb_sol_obs_transaction_observations_provider_method_sql(), - crate::postgres::migrations::create_ix_kb_sol_obs_transaction_observations_received_at_sql(), - crate::postgres::migrations::create_ix_kb_sol_obs_transaction_observations_raw_transaction_sql(), - crate::postgres::migrations::create_ix_kb_sol_obs_transaction_observations_status_sql(), - crate::postgres::migrations::migrate_and_drop_legacy_ws_notifications_sql(), + create_pg_table_if_not_exists_k_sol_obs_account_observations(), + create_pg_table_if_not_exists_k_sol_obs_transaction_observations(), + create_pg_table_if_not_exists_k_sol_raw_transactions(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_account_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_commitment_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_endpoint_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_code_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_error_message_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_filter_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_hash_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_key_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_lamports_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_method_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_origin(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_owner_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_payload_size_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_protocol_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_provider_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_rent_epoch_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_session_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_source_hash_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_space_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_state_shape(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_account_observations_status(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_commitment_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_endpoint_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_code_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_error_message_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_filter_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_hash_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_key_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_method_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_origin(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_payload_size_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_protocol_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_provider_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_session_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_obs_transaction_observations_status(), + add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_format_version_positive(), + add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_full_has_json(), + add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_processing_state(), + add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_retention_state(), + add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_raw_transactions_slot_non_negative(), + add_pg_constraint_if_not_exists_pk_k_sol_obs_account_observations(), + add_pg_constraint_if_not_exists_pk_k_sol_obs_transaction_observations(), + add_pg_constraint_if_not_exists_pk_k_sol_raw_transactions(), + add_pg_constraint_if_not_exists_fk_k_sol_obs_transaction_observations_raw_transaction(), + create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_account_slot(), + create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_provider_method(), + create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_received_at(), + create_pg_index_if_not_exists_ix_k_sol_obs_account_observations_status(), + create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_provider_method(), + create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_raw_transaction(), + create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_received_at(), + create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_signature(), + create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_slot(), + create_pg_index_if_not_exists_ix_k_sol_obs_transaction_observations_status(), + create_pg_index_if_not_exists_ix_k_sol_raw_transactions_canonical_hash(), + create_pg_index_if_not_exists_ix_k_sol_raw_transactions_created_at(), + create_pg_index_if_not_exists_ix_k_sol_raw_transactions_processing(), + create_pg_index_if_not_exists_ix_k_sol_raw_transactions_processing_slot(), + create_pg_index_if_not_exists_ix_k_sol_raw_transactions_slot(), + create_pg_index_if_not_exists_ux_k_sol_obs_account_observations_key(), + create_pg_index_if_not_exists_ux_k_sol_obs_transaction_observations_key(), + create_pg_index_if_not_exists_ux_k_sol_raw_transactions_signature(), ]; } -/// Idempotent SQL statements creating the minimal core Solana store. -pub(crate) fn core_store_schema_statements() -> [&'static str; 28] { - return [ - crate::postgres::migrations::create_table_kb_sol_core_transactions_sql(), - crate::postgres::migrations::create_ux_kb_sol_core_transactions_signature_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_transactions_slot_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_transactions_created_at_sql(), - crate::postgres::migrations::create_table_kb_sol_core_account_keys_sql(), - crate::postgres::migrations::create_ux_kb_sol_core_account_keys_sig_index_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_account_keys_key_sql(), - crate::postgres::migrations::create_table_kb_sol_core_instructions_sql(), - crate::postgres::migrations::create_ux_kb_sol_core_instructions_sig_path_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_instructions_program_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_instructions_slot_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_instructions_processing_sql(), - crate::postgres::migrations::create_table_kb_sol_core_inner_instructions_sql(), - crate::postgres::migrations::create_ux_kb_sol_core_inner_instructions_sig_path_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_inner_instructions_parent_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_inner_instructions_program_sql(), - crate::postgres::migrations::create_table_kb_sol_core_logs_sql(), - crate::postgres::migrations::create_ux_kb_sol_core_logs_sig_index_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_logs_program_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_logs_path_sql(), - crate::postgres::migrations::create_table_kb_sol_core_balance_changes_sql(), - crate::postgres::migrations::create_ux_kb_sol_core_balance_changes_sig_index_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_balance_changes_account_sql(), - crate::postgres::migrations::create_ix_kb_sol_core_balance_changes_mint_sql(), - crate::postgres::migrations::create_table_kb_sol_ops_processing_ledger_sql(), - crate::postgres::migrations::create_ux_kb_sol_ops_processing_ledger_identity_sql(), - crate::postgres::migrations::create_ix_kb_sol_ops_processing_ledger_status_sql(), - crate::postgres::migrations::create_ix_kb_sol_ops_processing_ledger_input_hash_sql(), +/// Idempotent PostgreSQL statements establishing the core store schema. +pub(crate) fn core_store_schema_statements() -> std::vec::Vec<&'static str> { + return vec![ + create_pg_table_if_not_exists_k_sol_core_account_keys(), + create_pg_table_if_not_exists_k_sol_core_account_states(), + create_pg_table_if_not_exists_k_sol_core_balance_changes(), + create_pg_table_if_not_exists_k_sol_core_inner_instructions(), + create_pg_table_if_not_exists_k_sol_core_instructions(), + create_pg_table_if_not_exists_k_sol_core_logs(), + create_pg_table_if_not_exists_k_sol_core_return_data(), + create_pg_table_if_not_exists_k_sol_core_transactions(), + create_pg_table_if_not_exists_k_sol_ops_processing_ledger(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_index_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_key_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_keys_source(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_account_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_hash_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_lamports_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_owner_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_rent_epoch_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_account_states_space_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_index_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_account_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_index_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_kind(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_mint_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_owner_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_balance_changes_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_parent_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_path_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processing_state(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_processor_pair(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_program_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_inner_instructions_stack_height_positive(), + add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_path_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_processing_state(), + add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_processor_pair(), + add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_program_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_instructions_stack_height_positive(), + add_pg_constraint_if_not_exists_ck_k_sol_core_logs_index_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_logs_path_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_logs_program_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_logs_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_logs_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_return_data_program_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_return_data_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_return_data_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_core_transactions_signature_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_core_transactions_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_attempt_count_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_code_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_error_message_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_hash_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_input_key_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_name_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_processor_version_not_empty( + ), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_stage_not_empty(), + add_pg_constraint_if_not_exists_ck_k_sol_ops_processing_ledger_status(), + add_pg_constraint_if_not_exists_pk_k_sol_core_account_keys(), + add_pg_constraint_if_not_exists_pk_k_sol_core_account_states(), + add_pg_constraint_if_not_exists_pk_k_sol_core_balance_changes(), + add_pg_constraint_if_not_exists_pk_k_sol_core_inner_instructions(), + add_pg_constraint_if_not_exists_pk_k_sol_core_instructions(), + add_pg_constraint_if_not_exists_pk_k_sol_core_logs(), + add_pg_constraint_if_not_exists_pk_k_sol_core_return_data(), + add_pg_constraint_if_not_exists_pk_k_sol_core_transactions(), + add_pg_constraint_if_not_exists_pk_k_sol_ops_processing_ledger(), + add_pg_constraint_if_not_exists_fk_k_sol_core_account_keys_transaction(), + add_pg_constraint_if_not_exists_fk_k_sol_core_account_states_observation(), + add_pg_constraint_if_not_exists_fk_k_sol_core_balance_changes_transaction(), + add_pg_constraint_if_not_exists_fk_k_sol_core_inner_instructions_transaction(), + add_pg_constraint_if_not_exists_fk_k_sol_core_instructions_transaction(), + add_pg_constraint_if_not_exists_fk_k_sol_core_logs_transaction(), + add_pg_constraint_if_not_exists_fk_k_sol_core_return_data_transaction(), + add_pg_constraint_if_not_exists_fk_k_sol_core_transactions_raw_transaction(), + create_pg_index_if_not_exists_ix_k_sol_core_account_keys_key(), + create_pg_index_if_not_exists_ix_k_sol_core_account_states_account_slot(), + create_pg_index_if_not_exists_ix_k_sol_core_account_states_owner(), + create_pg_index_if_not_exists_ix_k_sol_core_balance_changes_account(), + create_pg_index_if_not_exists_ix_k_sol_core_balance_changes_mint(), + create_pg_index_if_not_exists_ix_k_sol_core_balance_changes_owner(), + create_pg_index_if_not_exists_ix_k_sol_core_inner_instructions_parent(), + create_pg_index_if_not_exists_ix_k_sol_core_inner_instructions_processing(), + create_pg_index_if_not_exists_ix_k_sol_core_inner_instructions_program(), + create_pg_index_if_not_exists_ix_k_sol_core_instructions_processing(), + create_pg_index_if_not_exists_ix_k_sol_core_instructions_program(), + create_pg_index_if_not_exists_ix_k_sol_core_instructions_slot(), + create_pg_index_if_not_exists_ix_k_sol_core_logs_path(), + create_pg_index_if_not_exists_ix_k_sol_core_logs_program(), + create_pg_index_if_not_exists_ix_k_sol_core_return_data_program(), + create_pg_index_if_not_exists_ix_k_sol_core_transactions_created_at(), + create_pg_index_if_not_exists_ix_k_sol_core_transactions_slot(), + create_pg_index_if_not_exists_ix_k_sol_ops_processing_ledger_input_hash(), + create_pg_index_if_not_exists_ix_k_sol_ops_processing_ledger_stage_input(), + create_pg_index_if_not_exists_ix_k_sol_ops_processing_ledger_status(), + create_pg_index_if_not_exists_ux_k_sol_core_account_keys_sig_index(), + create_pg_index_if_not_exists_ux_k_sol_core_account_states_observation(), + create_pg_index_if_not_exists_ux_k_sol_core_balance_changes_sig_index(), + create_pg_index_if_not_exists_ux_k_sol_core_inner_instructions_sig_path(), + create_pg_index_if_not_exists_ux_k_sol_core_instructions_sig_path(), + create_pg_index_if_not_exists_ux_k_sol_core_logs_sig_index(), + create_pg_index_if_not_exists_ux_k_sol_core_return_data_signature(), + create_pg_index_if_not_exists_ux_k_sol_core_transactions_signature(), + create_pg_index_if_not_exists_ux_k_sol_ops_processing_ledger_identity(), ]; } -/// Idempotent SQL statements creating the common decode and materialization store. -pub(crate) fn decode_store_schema_statements() -> [&'static str; 15] { - return [ - crate::postgres::migrations::create_table_kb_sol_decode_events_sql(), - crate::postgres::migrations::create_ux_kb_sol_decode_events_identity_sql(), - crate::postgres::migrations::create_ix_kb_sol_decode_events_signature_path_sql(), - crate::postgres::migrations::create_ix_kb_sol_decode_events_program_surface_sql(), - crate::postgres::migrations::create_ix_kb_sol_decode_events_family_commit_sql(), - crate::postgres::migrations::create_table_kb_sol_decode_coverage_declarations_sql(), - crate::postgres::migrations::create_ux_kb_sol_decode_coverage_declarations_identity_sql(), - crate::postgres::migrations::create_ix_kb_sol_decode_coverage_declarations_program_sql(), - crate::postgres::migrations::create_table_kb_sol_decode_coverage_observations_sql(), - crate::postgres::migrations::create_ux_kb_sol_decode_coverage_observations_identity_sql(), - crate::postgres::migrations::create_ix_kb_sol_decode_coverage_observations_program_status_sql(), - crate::postgres::migrations::create_ix_kb_sol_decode_coverage_observations_entry_sql(), - crate::postgres::migrations::create_table_kb_sol_mat_events_sql(), - crate::postgres::migrations::create_ux_kb_sol_mat_events_identity_sql(), - crate::postgres::migrations::create_ix_kb_sol_mat_events_signature_family_sql(), +/// Idempotent PostgreSQL statements establishing the decode store schema. +pub(crate) fn decode_store_schema_statements() -> std::vec::Vec<&'static str> { + return vec![ + create_pg_table_if_not_exists_k_sol_decode_coverage_declarations(), + create_pg_table_if_not_exists_k_sol_decode_coverage_observations(), + create_pg_table_if_not_exists_k_sol_decode_events(), + create_pg_table_if_not_exists_k_sol_mat_outputs(), + add_pg_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_counts_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_decode_coverage_observations_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_decode_events_failed_not_committed(), + add_pg_constraint_if_not_exists_ck_k_sol_decode_events_schema_provenance(), + add_pg_constraint_if_not_exists_ck_k_sol_decode_events_slot_non_negative(), + add_pg_constraint_if_not_exists_ck_k_sol_mat_outputs_slot_non_negative(), + add_pg_constraint_if_not_exists_pk_k_sol_decode_coverage_declarations(), + add_pg_constraint_if_not_exists_pk_k_sol_decode_coverage_observations(), + add_pg_constraint_if_not_exists_pk_k_sol_decode_events(), + add_pg_constraint_if_not_exists_pk_k_sol_mat_outputs(), + create_pg_index_if_not_exists_ix_k_sol_decode_coverage_declarations_program(), + create_pg_index_if_not_exists_ix_k_sol_decode_coverage_observations_entry(), + create_pg_index_if_not_exists_ix_k_sol_decode_coverage_observations_program_status(), + create_pg_index_if_not_exists_ix_k_sol_decode_events_family_commit(), + create_pg_index_if_not_exists_ix_k_sol_decode_events_program_surface(), + create_pg_index_if_not_exists_ix_k_sol_decode_events_signature_path(), + create_pg_index_if_not_exists_ix_k_sol_mat_outputs_processor_family_slot(), + create_pg_index_if_not_exists_ix_k_sol_mat_outputs_signature_family(), + create_pg_index_if_not_exists_ux_k_sol_decode_coverage_declarations_identity(), + create_pg_index_if_not_exists_ux_k_sol_decode_coverage_observations_identity(), + create_pg_index_if_not_exists_ux_k_sol_decode_events_processor_input_event(), + create_pg_index_if_not_exists_ux_k_sol_mat_outputs_processor_input_output(), ]; } -/// SQL conditionally renaming the historical raw RPC transaction table. -fn migrate_legacy_raw_table_name_sql() -> &'static str { - return r#"DO $$ -BEGIN - IF to_regclass('kb_sol_raw_rpc_transactions') IS NOT NULL - AND to_regclass('kb_sol_raw_transactions') IS NULL THEN - ALTER TABLE kb_sol_raw_rpc_transactions RENAME TO kb_sol_raw_transactions; - ELSIF to_regclass('kb_sol_raw_rpc_transactions') IS NOT NULL - AND to_regclass('kb_sol_raw_transactions') IS NOT NULL THEN - RAISE EXCEPTION 'both legacy and canonical raw transaction tables exist'; - END IF; -END -$$"#; +fn truncate_store_schema_statements() -> std::vec::Vec<&'static str> { + return vec![ + truncate_pg_table_k_sol_core_account_keys(), + truncate_pg_table_k_sol_core_account_states(), + truncate_pg_table_k_sol_core_balance_changes(), + truncate_pg_table_k_sol_core_inner_instructions(), + truncate_pg_table_k_sol_core_instructions(), + truncate_pg_table_k_sol_core_logs(), + truncate_pg_table_k_sol_core_return_data(), + truncate_pg_table_k_sol_core_transactions(), + truncate_pg_table_k_sol_decode_coverage_declarations(), + truncate_pg_table_k_sol_decode_coverage_observations(), + truncate_pg_table_k_sol_decode_events(), + truncate_pg_table_k_sol_mat_outputs(), + truncate_pg_table_k_sol_obs_account_observations(), + truncate_pg_table_k_sol_obs_transaction_observations(), + truncate_pg_table_k_sol_ops_processing_ledger(), + truncate_pg_table_k_sol_raw_transactions(), + ]; } -/// SQL conditionally renaming historical raw payload columns and adding the format version. -fn migrate_legacy_raw_columns_sql() -> &'static str { - return r#"DO $$ -BEGIN - IF to_regclass('kb_sol_raw_transactions') IS NOT NULL THEN - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'kb_sol_raw_transactions' AND column_name = 'raw_json') - AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'kb_sol_raw_transactions' AND column_name = 'canonical_json') THEN - ALTER TABLE kb_sol_raw_transactions RENAME COLUMN raw_json TO canonical_json; - END IF; - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'kb_sol_raw_transactions' AND column_name = 'raw_json_hash') - AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'kb_sol_raw_transactions' AND column_name = 'canonical_json_hash') THEN - ALTER TABLE kb_sol_raw_transactions RENAME COLUMN raw_json_hash TO canonical_json_hash; - END IF; - ALTER TABLE kb_sol_raw_transactions ADD COLUMN IF NOT EXISTS canonical_format_version INTEGER NOT NULL DEFAULT 1; - END IF; -END -$$"#; +fn drop_store_schema_statements() -> std::vec::Vec<&'static str> { + return vec![ + drop_pg_table_if_exists_k_sol_core_account_keys(), + drop_pg_table_if_exists_k_sol_core_account_states(), + drop_pg_table_if_exists_k_sol_core_balance_changes(), + drop_pg_table_if_exists_k_sol_core_inner_instructions(), + drop_pg_table_if_exists_k_sol_core_instructions(), + drop_pg_table_if_exists_k_sol_core_logs(), + drop_pg_table_if_exists_k_sol_core_return_data(), + drop_pg_table_if_exists_k_sol_core_transactions(), + drop_pg_table_if_exists_k_sol_decode_coverage_declarations(), + drop_pg_table_if_exists_k_sol_decode_coverage_observations(), + drop_pg_table_if_exists_k_sol_decode_events(), + drop_pg_table_if_exists_k_sol_mat_outputs(), + drop_pg_table_if_exists_k_sol_obs_account_observations(), + drop_pg_table_if_exists_k_sol_obs_transaction_observations(), + drop_pg_table_if_exists_k_sol_ops_processing_ledger(), + drop_pg_table_if_exists_k_sol_raw_transactions(), + ]; } -/// SQL conditionally renaming historical canonical transaction constraints. -fn migrate_legacy_raw_constraints_sql() -> &'static str { - return r#"DO $$ -BEGIN - IF to_regclass('kb_sol_raw_transactions') IS NOT NULL THEN - IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_raw_transactions'::regclass AND conname = 'pk_kb_sol_raw_rpc_transactions') THEN ALTER TABLE kb_sol_raw_transactions RENAME CONSTRAINT pk_kb_sol_raw_rpc_transactions TO pk_kb_sol_raw_transactions; END IF; - IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_raw_transactions'::regclass AND conname = 'ck_kb_sol_raw_rpc_transactions_signature_not_empty') THEN ALTER TABLE kb_sol_raw_transactions RENAME CONSTRAINT ck_kb_sol_raw_rpc_transactions_signature_not_empty TO ck_kb_sol_raw_transactions_signature_not_empty; END IF; - IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_raw_transactions'::regclass AND conname = 'ck_kb_sol_raw_rpc_transactions_slot_non_negative') THEN ALTER TABLE kb_sol_raw_transactions RENAME CONSTRAINT ck_kb_sol_raw_rpc_transactions_slot_non_negative TO ck_kb_sol_raw_transactions_slot_non_negative; END IF; - IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_raw_transactions'::regclass AND conname = 'ck_kb_sol_raw_rpc_transactions_retention_state') THEN ALTER TABLE kb_sol_raw_transactions RENAME CONSTRAINT ck_kb_sol_raw_rpc_transactions_retention_state TO ck_kb_sol_raw_transactions_retention_state; END IF; - IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_raw_transactions'::regclass AND conname = 'ck_kb_sol_raw_rpc_transactions_processing_state') THEN ALTER TABLE kb_sol_raw_transactions RENAME CONSTRAINT ck_kb_sol_raw_rpc_transactions_processing_state TO ck_kb_sol_raw_transactions_processing_state; END IF; - IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_raw_transactions'::regclass AND conname = 'ck_kb_sol_raw_rpc_transactions_full_has_json') THEN ALTER TABLE kb_sol_raw_transactions RENAME CONSTRAINT ck_kb_sol_raw_rpc_transactions_full_has_json TO ck_kb_sol_raw_transactions_full_has_json; END IF; - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_raw_transactions'::regclass AND conname = 'ck_kb_sol_raw_transactions_format_version_positive') THEN ALTER TABLE kb_sol_raw_transactions ADD CONSTRAINT ck_kb_sol_raw_transactions_format_version_positive CHECK (canonical_format_version > 0); END IF; - END IF; -END -$$"#; -} - -/// SQL conditionally renaming historical canonical transaction indexes. -fn migrate_legacy_raw_indexes_sql() -> &'static str { - return r#"DO $$ -BEGIN - IF to_regclass('ux_kb_sol_raw_rpc_transactions_signature') IS NOT NULL AND to_regclass('ux_kb_sol_raw_transactions_signature') IS NULL THEN ALTER INDEX ux_kb_sol_raw_rpc_transactions_signature RENAME TO ux_kb_sol_raw_transactions_signature; END IF; - IF to_regclass('ix_kb_sol_raw_rpc_transactions_slot') IS NOT NULL AND to_regclass('ix_kb_sol_raw_transactions_slot') IS NULL THEN ALTER INDEX ix_kb_sol_raw_rpc_transactions_slot RENAME TO ix_kb_sol_raw_transactions_slot; END IF; - IF to_regclass('ix_kb_sol_raw_rpc_transactions_created_at') IS NOT NULL AND to_regclass('ix_kb_sol_raw_transactions_created_at') IS NULL THEN ALTER INDEX ix_kb_sol_raw_rpc_transactions_created_at RENAME TO ix_kb_sol_raw_transactions_created_at; END IF; - IF to_regclass('ix_kb_sol_raw_rpc_transactions_processing') IS NOT NULL AND to_regclass('ix_kb_sol_raw_transactions_processing') IS NULL THEN ALTER INDEX ix_kb_sol_raw_rpc_transactions_processing RENAME TO ix_kb_sol_raw_transactions_processing; END IF; -END -$$"#; -} - -/// SQL conditionally renaming historical core-to-raw lineage. -fn migrate_legacy_core_lineage_sql() -> &'static str { - return r#"DO $$ -BEGIN - IF to_regclass('kb_sol_core_transactions') IS NOT NULL THEN - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'kb_sol_core_transactions' AND column_name = 'raw_rpc_transaction_id') - AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'kb_sol_core_transactions' AND column_name = 'raw_transaction_id') THEN - ALTER TABLE kb_sol_core_transactions RENAME COLUMN raw_rpc_transaction_id TO raw_transaction_id; - END IF; - IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'kb_sol_core_transactions'::regclass AND conname = 'fk_kb_sol_core_transactions_raw_rpc_transaction') THEN - ALTER TABLE kb_sol_core_transactions RENAME CONSTRAINT fk_kb_sol_core_transactions_raw_rpc_transaction TO fk_kb_sol_core_transactions_raw_transaction; - END IF; - END IF; -END -$$"#; -} - -/// SQL creating `kb_sol_raw_transactions`. -fn create_table_kb_sol_raw_transactions_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_raw_transactions ( - id BIGSERIAL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - canonical_json JSONB, - canonical_json_hash TEXT, - canonical_format_version INTEGER NOT NULL DEFAULT 1, - retention_state TEXT NOT NULL DEFAULT 'full', - processing_state TEXT NOT NULL DEFAULT 'received', - lifecycle_reason TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_raw_transactions PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_raw_transactions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_raw_transactions_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_raw_transactions_format_version_positive CHECK (canonical_format_version > 0), - CONSTRAINT ck_kb_sol_raw_transactions_retention_state CHECK (retention_state IN ('full', 'compacted', 'archived', 'purged')), - CONSTRAINT ck_kb_sol_raw_transactions_processing_state CHECK (processing_state IN ('received', 'core_extracted', 'decoded', 'materialized', 'failed')), - CONSTRAINT ck_kb_sol_raw_transactions_full_has_json CHECK (retention_state <> 'full' OR canonical_json IS NOT NULL) - )"#; -} - -/// SQL creating the unique signature index for canonical raw transactions. -fn create_ux_kb_sol_raw_transactions_signature_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_raw_transactions_signature ON kb_sol_raw_transactions (signature)"; -} - -/// SQL creating the slot index for canonical raw transactions. -fn create_ix_kb_sol_raw_transactions_slot_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_slot ON kb_sol_raw_transactions (slot)"; -} - -/// SQL creating the created timestamp index for canonical raw transactions. -fn create_ix_kb_sol_raw_transactions_created_at_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_created_at ON kb_sol_raw_transactions (created_at)"; -} - -/// SQL creating the processing state index for canonical raw transactions. -fn create_ix_kb_sol_raw_transactions_processing_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_processing ON kb_sol_raw_transactions (processing_state)"; -} - -/// SQL creating the optional canonical document hash index. -fn create_ix_kb_sol_raw_transactions_canonical_hash_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_raw_transactions_canonical_hash ON kb_sol_raw_transactions (canonical_json_hash) WHERE canonical_json_hash IS NOT NULL"; -} - -/// SQL creating `kb_sol_obs_transaction_observations`. -fn create_table_kb_sol_obs_transaction_observations_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_obs_transaction_observations ( - id BIGSERIAL, - raw_transaction_id BIGINT, - observation_key TEXT NOT NULL, - signature TEXT, - slot BIGINT, - provider TEXT NOT NULL, - endpoint_code TEXT, - protocol TEXT NOT NULL, - acquisition_method TEXT NOT NULL, - origin TEXT NOT NULL, - commitment TEXT, - capture_session_id TEXT, - filter_code TEXT, - detected_at TIMESTAMPTZ, - received_at TIMESTAMPTZ NOT NULL, - normalized_at TIMESTAMPTZ, - persisted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - payload_size_bytes BIGINT, - source_payload_hash TEXT, - status TEXT NOT NULL DEFAULT 'received', - error_code TEXT, - error_message TEXT, - CONSTRAINT pk_kb_sol_obs_transaction_observations PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_obs_transaction_observations_raw_transaction FOREIGN KEY (raw_transaction_id) REFERENCES kb_sol_raw_transactions(id) ON DELETE SET NULL, - CONSTRAINT ck_kb_sol_obs_transaction_observations_key_not_empty CHECK (length(btrim(observation_key)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_signature_not_empty CHECK (signature IS NULL OR length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_slot_non_negative CHECK (slot IS NULL OR slot >= 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_provider_not_empty CHECK (length(btrim(provider)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_endpoint_not_empty CHECK (endpoint_code IS NULL OR length(btrim(endpoint_code)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_protocol_not_empty CHECK (length(btrim(protocol)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_method_not_empty CHECK (length(btrim(acquisition_method)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_origin CHECK (origin IN ('live', 'backfill', 'replay', 'repair', 'migration')), - CONSTRAINT ck_kb_sol_obs_transaction_observations_commitment_not_empty CHECK (commitment IS NULL OR length(btrim(commitment)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_session_not_empty CHECK (capture_session_id IS NULL OR length(btrim(capture_session_id)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_filter_not_empty CHECK (filter_code IS NULL OR length(btrim(filter_code)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_payload_size_non_negative CHECK (payload_size_bytes IS NULL OR payload_size_bytes >= 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_hash_not_empty CHECK (source_payload_hash IS NULL OR length(btrim(source_payload_hash)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_status CHECK (status IN ('detected', 'received', 'normalized', 'persisted', 'failed', 'missing')), - CONSTRAINT ck_kb_sol_obs_transaction_observations_error_code_not_empty CHECK (error_code IS NULL OR length(btrim(error_code)) > 0), - CONSTRAINT ck_kb_sol_obs_transaction_observations_error_message_not_empty CHECK (error_message IS NULL OR length(btrim(error_message)) > 0) - )"#; -} - -/// SQL creating the unique observation key index. -fn create_ux_kb_sol_obs_transaction_observations_key_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_obs_transaction_observations_key ON kb_sol_obs_transaction_observations (observation_key)"; -} - -/// SQL creating the optional signature index for transaction observations. -fn create_ix_kb_sol_obs_transaction_observations_signature_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_signature ON kb_sol_obs_transaction_observations (signature) WHERE signature IS NOT NULL"; -} - -/// SQL creating the optional slot index for transaction observations. -fn create_ix_kb_sol_obs_transaction_observations_slot_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_slot ON kb_sol_obs_transaction_observations (slot) WHERE slot IS NOT NULL"; -} - -/// SQL creating the provider and acquisition method index for transaction observations. -fn create_ix_kb_sol_obs_transaction_observations_provider_method_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_provider_method ON kb_sol_obs_transaction_observations (provider, acquisition_method)"; -} - -/// SQL creating the received timestamp index for transaction observations. -fn create_ix_kb_sol_obs_transaction_observations_received_at_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_received_at ON kb_sol_obs_transaction_observations (received_at)"; -} - -/// SQL creating the optional canonical transaction lineage index for observations. -fn create_ix_kb_sol_obs_transaction_observations_raw_transaction_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_raw_transaction ON kb_sol_obs_transaction_observations (raw_transaction_id) WHERE raw_transaction_id IS NOT NULL"; -} - -/// SQL creating the status index for transaction observations. -fn create_ix_kb_sol_obs_transaction_observations_status_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_obs_transaction_observations_status ON kb_sol_obs_transaction_observations (status)"; -} - -/// SQL migrating lightweight metadata and dropping the historical WebSocket payload table. -fn migrate_and_drop_legacy_ws_notifications_sql() -> &'static str { - return r#"DO $$ -BEGIN - IF to_regclass('kb_sol_raw_ws_notifications') IS NOT NULL THEN - INSERT INTO kb_sol_obs_transaction_observations (raw_transaction_id, observation_key, signature, slot, provider, protocol, acquisition_method, origin, received_at, persisted_at, payload_size_bytes, source_payload_hash, status, error_message) - SELECT linked_raw_rpc_transaction_id, notification_key, signature, slot, 'legacy_unknown', 'solana_websocket', subscription_kind, 'migration', created_at, created_at, CASE WHEN raw_json IS NULL THEN NULL ELSE octet_length(convert_to(raw_json::text, 'UTF8'))::BIGINT END, NULLIF(btrim(raw_json_hash), ''), CASE WHEN processing_state = 'failed' THEN 'failed' ELSE 'received' END, NULLIF(btrim(lifecycle_reason), '') - FROM kb_sol_raw_ws_notifications - ON CONFLICT (observation_key) DO NOTHING; - DROP TABLE kb_sol_raw_ws_notifications; - END IF; -END -$$"#; -} - -/// SQL creating `kb_sol_core_transactions`. -fn create_table_kb_sol_core_transactions_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_core_transactions ( - id BIGSERIAL, - raw_transaction_id BIGINT, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - failed BOOLEAN NOT NULL DEFAULT FALSE, - err_json JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_transactions PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_transactions_raw_transaction FOREIGN KEY (raw_transaction_id) REFERENCES kb_sol_raw_transactions(id) ON DELETE SET NULL, - CONSTRAINT ck_kb_sol_core_transactions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_transactions_slot_non_negative CHECK (slot >= 0) - )"#; -} - -/// SQL creating the unique signature index for core transactions. -fn create_ux_kb_sol_core_transactions_signature_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_transactions_signature ON kb_sol_core_transactions (signature)"; -} - -/// SQL creating the slot index for core transactions. -fn create_ix_kb_sol_core_transactions_slot_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_transactions_slot ON kb_sol_core_transactions (slot)"; -} - -/// SQL creating the created_at index for core transactions. -fn create_ix_kb_sol_core_transactions_created_at_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_transactions_created_at ON kb_sol_core_transactions (created_at)"; -} - -/// SQL creating `kb_sol_core_account_keys`. -fn create_table_kb_sol_core_account_keys_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_core_account_keys ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - account_index INTEGER NOT NULL, - account_key TEXT NOT NULL, - source TEXT NOT NULL, - writable BOOLEAN NOT NULL, - signer BOOLEAN NOT NULL, - executable BOOLEAN, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_account_keys PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_account_keys_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_account_keys_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_account_keys_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_account_keys_index_non_negative CHECK (account_index >= 0), - CONSTRAINT ck_kb_sol_core_account_keys_key_not_empty CHECK (length(btrim(account_key)) > 0), - CONSTRAINT ck_kb_sol_core_account_keys_source CHECK (source IN ('static', 'loaded_writable', 'loaded_readonly')) - )"#; -} - -/// SQL creating the unique signature/account index for core account keys. -fn create_ux_kb_sol_core_account_keys_sig_index_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_account_keys_sig_index ON kb_sol_core_account_keys (signature, account_index)"; -} - -/// SQL creating the account key index for core account keys. -fn create_ix_kb_sol_core_account_keys_key_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_account_keys_key ON kb_sol_core_account_keys (account_key)"; -} - -/// SQL creating `kb_sol_core_instructions`. -fn create_table_kb_sol_core_instructions_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_core_instructions ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - instruction_path TEXT NOT NULL, - program_id TEXT NOT NULL, - accounts_json JSONB NOT NULL, - payload_json JSONB, - payload_json_hash TEXT, - processing_state TEXT NOT NULL DEFAULT 'pending', - processor_name TEXT, - processor_version TEXT, - lifecycle_reason TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_instructions PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_instructions_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_instructions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_instructions_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_instructions_path_not_empty CHECK (length(btrim(instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_instructions_program_not_empty CHECK (length(btrim(program_id)) > 0), - CONSTRAINT ck_kb_sol_core_instructions_processing_state CHECK (processing_state IN ('pending', 'decoded', 'materialized', 'ignored', 'failed', 'replay_requested')) - )"#; -} - -/// SQL creating the unique signature/path index for core instructions. -fn create_ux_kb_sol_core_instructions_sig_path_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_instructions_sig_path ON kb_sol_core_instructions (signature, instruction_path)"; -} - -/// SQL creating the program index for core instructions. -fn create_ix_kb_sol_core_instructions_program_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_instructions_program ON kb_sol_core_instructions (program_id)"; -} - -/// SQL creating the slot index for core instructions. -fn create_ix_kb_sol_core_instructions_slot_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_instructions_slot ON kb_sol_core_instructions (slot)"; -} - -/// SQL creating the processing state index for core instructions. -fn create_ix_kb_sol_core_instructions_processing_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_instructions_processing ON kb_sol_core_instructions (processing_state)"; -} - -/// SQL creating `kb_sol_core_inner_instructions`. -fn create_table_kb_sol_core_inner_instructions_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_core_inner_instructions ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - parent_instruction_path TEXT NOT NULL, - instruction_path TEXT NOT NULL, - program_id TEXT NOT NULL, - accounts_json JSONB NOT NULL, - payload_json JSONB, - payload_json_hash TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_inner_instructions PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_inner_instructions_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_inner_instructions_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_parent_not_empty CHECK (length(btrim(parent_instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_path_not_empty CHECK (length(btrim(instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_inner_instructions_program_not_empty CHECK (length(btrim(program_id)) > 0) - )"#; -} - -/// SQL creating the unique signature/path index for core inner instructions. -fn create_ux_kb_sol_core_inner_instructions_sig_path_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_inner_instructions_sig_path ON kb_sol_core_inner_instructions (signature, instruction_path)"; -} - -/// SQL creating the parent instruction index for core inner instructions. -fn create_ix_kb_sol_core_inner_instructions_parent_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_inner_instructions_parent ON kb_sol_core_inner_instructions (signature, parent_instruction_path)"; -} - -/// SQL creating the program index for core inner instructions. -fn create_ix_kb_sol_core_inner_instructions_program_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_inner_instructions_program ON kb_sol_core_inner_instructions (program_id)"; -} - -/// SQL creating `kb_sol_core_logs`. -fn create_table_kb_sol_core_logs_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_core_logs ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - log_index INTEGER NOT NULL, - instruction_path TEXT, - program_id TEXT, - log_text TEXT, - log_text_hash TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_logs PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_logs_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_logs_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_logs_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_logs_index_non_negative CHECK (log_index >= 0), - CONSTRAINT ck_kb_sol_core_logs_text_not_empty CHECK (log_text IS NULL OR length(btrim(log_text)) > 0), - CONSTRAINT ck_kb_sol_core_logs_path_not_empty CHECK (instruction_path IS NULL OR length(btrim(instruction_path)) > 0), - CONSTRAINT ck_kb_sol_core_logs_program_not_empty CHECK (program_id IS NULL OR length(btrim(program_id)) > 0) - )"#; -} - -/// SQL creating the unique signature/log index for core logs. -fn create_ux_kb_sol_core_logs_sig_index_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_logs_sig_index ON kb_sol_core_logs (signature, log_index)"; -} - -/// SQL creating the program index for core logs. -fn create_ix_kb_sol_core_logs_program_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_logs_program ON kb_sol_core_logs (program_id) WHERE program_id IS NOT NULL"; -} - -/// SQL creating the instruction path index for core logs. -fn create_ix_kb_sol_core_logs_path_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_logs_path ON kb_sol_core_logs (signature, instruction_path) WHERE instruction_path IS NOT NULL"; -} - -/// SQL creating `kb_sol_core_balance_changes`. -fn create_table_kb_sol_core_balance_changes_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_core_balance_changes ( - id BIGSERIAL, - transaction_id BIGINT NOT NULL, - signature TEXT NOT NULL, - slot BIGINT NOT NULL, - balance_change_index INTEGER NOT NULL, - balance_kind TEXT NOT NULL, - account_index INTEGER, - account_key TEXT, - mint TEXT, - owner TEXT, - pre_balance_json JSONB, - post_balance_json JSONB, - delta_json JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_core_balance_changes PRIMARY KEY (id), - CONSTRAINT fk_kb_sol_core_balance_changes_transaction FOREIGN KEY (transaction_id) REFERENCES kb_sol_core_transactions(id) ON DELETE CASCADE, - CONSTRAINT ck_kb_sol_core_balance_changes_signature_not_empty CHECK (length(btrim(signature)) > 0), - CONSTRAINT ck_kb_sol_core_balance_changes_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_core_balance_changes_index_non_negative CHECK (balance_change_index >= 0), - CONSTRAINT ck_kb_sol_core_balance_changes_account_index_non_negative CHECK (account_index IS NULL OR account_index >= 0), - CONSTRAINT ck_kb_sol_core_balance_changes_kind CHECK (balance_kind IN ('native_lamports', 'token_amount')), - CONSTRAINT ck_kb_sol_core_balance_changes_account_not_empty CHECK (account_key IS NULL OR length(btrim(account_key)) > 0), - CONSTRAINT ck_kb_sol_core_balance_changes_mint_not_empty CHECK (mint IS NULL OR length(btrim(mint)) > 0), - CONSTRAINT ck_kb_sol_core_balance_changes_owner_not_empty CHECK (owner IS NULL OR length(btrim(owner)) > 0) - )"#; -} - -/// SQL creating the unique signature/balance index for core balance changes. -fn create_ux_kb_sol_core_balance_changes_sig_index_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_core_balance_changes_sig_index ON kb_sol_core_balance_changes (signature, balance_change_index)"; -} - -/// SQL creating the account key index for core balance changes. -fn create_ix_kb_sol_core_balance_changes_account_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_balance_changes_account ON kb_sol_core_balance_changes (account_key) WHERE account_key IS NOT NULL"; -} - -/// SQL creating the mint index for core balance changes. -fn create_ix_kb_sol_core_balance_changes_mint_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_core_balance_changes_mint ON kb_sol_core_balance_changes (mint) WHERE mint IS NOT NULL"; -} - -/// Returns true when a Solana table name follows the canonical prefix and domain rules. -#[cfg(test)] -fn is_valid_solana_table_name(table_name: &str) -> bool { - return validate_solana_table_name(table_name).is_ok(); -} - -/// Validates a canonical Solana table name. -fn validate_solana_table_name(table_name: &str) -> ks_core::Result<()> { - let trimmed_table_name = table_name.trim(); - if trimmed_table_name.is_empty() { - return std::result::Result::Err(ks_core::Error::db("table name must not be empty")); - } - if trimmed_table_name.contains('.') { - return std::result::Result::Err(ks_core::Error::db( - "solana table names must not contain an explicit PostgreSQL schema", - )); - } - if !trimmed_table_name.starts_with(SOLANA_TABLE_PREFIX) { - return std::result::Result::Err(ks_core::Error::db( - "solana table name must start with kb_sol_", - )); - } - let suffix = &trimmed_table_name[SOLANA_TABLE_PREFIX.len()..]; - let domain = crate::postgres::migrations::first_segment(suffix); - if domain.is_empty() { - return std::result::Result::Err(ks_core::Error::db( - "solana table name must include a domain after kb_sol_", - )); - } - if !crate::postgres::migrations::is_allowed_domain(domain) { - return std::result::Result::Err(ks_core::Error::db( - "solana table name domain is not allowed", - )); - } - if suffix.len() <= domain.len() + 1 { - return std::result::Result::Err(ks_core::Error::db( - "solana table name must include a name after the domain", - )); - } - if !crate::postgres::migrations::contains_only_table_name_chars(trimmed_table_name) { - return std::result::Result::Err(ks_core::Error::db( - "solana table name must use lowercase ascii, digits and underscores only", - )); +/// Validates that every embedded PostgreSQL schema and maintenance resource is non-empty and atomic. +pub(crate) fn validate_postgres_sql_resources() -> ks_core::Result<()> { + let groups = [ + raw_store_schema_statements(), + core_store_schema_statements(), + decode_store_schema_statements(), + truncate_store_schema_statements(), + drop_store_schema_statements(), + ]; + for group in groups { + for statement in group { + let trimmed = strip_sql_resource_header(statement); + if trimmed.trim().is_empty() { + return std::result::Result::Err(ks_core::Error::db( + "postgres SQL resource must not be empty", + )); + } + if contains_multiple_top_level_statements(trimmed) { + return std::result::Result::Err(ks_core::Error::db( + "postgres SQL resource must contain exactly one top-level statement", + )); + } + } } return std::result::Result::Ok(()); } -/// Validates the raw store table names introduced by `0.2.3`. -pub(crate) fn validate_raw_store_table_names() -> ks_core::Result<()> { - return crate::postgres::migrations::validate_table_names(RAW_STORE_TABLE_NAMES); +fn strip_sql_resource_header(sql: &str) -> &str { + let mut offset = 0_usize; + for line in sql.split_inclusive('\n') { + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with("--") { + offset += line.len(); + continue; + } + break; + } + return &sql[offset..]; } -/// Validates the core store table names introduced by `0.2.4`. -pub(crate) fn validate_core_store_table_names() -> ks_core::Result<()> { - return crate::postgres::migrations::validate_table_names(CORE_STORE_TABLE_NAMES); +fn contains_multiple_top_level_statements(sql: &str) -> bool { + let trimmed = sql.trim(); + if trimmed.starts_with("DO $$") { + return !trimmed.ends_with("$$;"); + } + let without_last = match trimmed.strip_suffix(';') { + std::option::Option::Some(value) => value, + std::option::Option::None => trimmed, + }; + return without_last.contains(';'); } -/// Validates the decode and materialization table names introduced by `0.4.0`. -pub(crate) fn validate_decode_store_table_names() -> ks_core::Result<()> { - return crate::postgres::migrations::validate_table_names(DECODE_STORE_TABLE_NAMES); +#[cfg(test)] +fn validate_raw_store_table_names() -> ks_core::Result<()> { + return validate_table_names(RAW_STORE_TABLE_NAMES); +} +#[cfg(test)] +fn validate_core_store_table_names() -> ks_core::Result<()> { + return validate_table_names(CORE_STORE_TABLE_NAMES); +} +#[cfg(test)] +fn validate_decode_store_table_names() -> ks_core::Result<()> { + return validate_table_names(DECODE_STORE_TABLE_NAMES); } +#[cfg(test)] fn validate_table_names(table_names: &[&str]) -> ks_core::Result<()> { for table_name in table_names { - let validation_result = validate_solana_table_name(table_name); - if let std::result::Result::Err(error) = validation_result { - return std::result::Result::Err(error); + if !is_valid_solana_table_name(table_name) { + return std::result::Result::Err(ks_core::Error::db(format!( + "invalid Solana table name: {table_name}" + ))); } } return std::result::Result::Ok(()); } -fn first_segment(value: &str) -> &str { - return match value.split_once('_') { - std::option::Option::Some((first, _rest)) => first, - std::option::Option::None => value, - }; -} - -fn is_allowed_domain(domain: &str) -> bool { - for allowed_domain in ALLOWED_SOLANA_TABLE_DOMAINS { - if domain == *allowed_domain { - return true; - } - } - return false; -} - -fn contains_only_table_name_chars(table_name: &str) -> bool { - for character in table_name.chars() { - if character == '_' { - continue; - } - if character.is_ascii_lowercase() { - continue; - } - if character.is_ascii_digit() { - continue; - } +#[cfg(test)] +fn is_valid_solana_table_name(table_name: &str) -> bool { + if table_name.contains('.') + || !table_name.starts_with(SOLANA_TABLE_PREFIX) + || table_name.chars().any(|value| return value.is_ascii_uppercase()) + { return false; } - return true; + let suffix = &table_name[SOLANA_TABLE_PREFIX.len()..]; + let split = suffix.split_once('_'); + let (domain, remainder) = match split { + std::option::Option::Some(value) => value, + std::option::Option::None => return false, + }; + return ALLOWED_SOLANA_TABLE_DOMAINS.contains(&domain) && !remainder.trim().is_empty(); } -/// SQL creating `kb_sol_ops_processing_ledger`. -fn create_table_kb_sol_ops_processing_ledger_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_ops_processing_ledger ( - id BIGSERIAL, - stage TEXT NOT NULL, - processor_name TEXT NOT NULL, - processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, - input_hash TEXT NOT NULL, - status TEXT NOT NULL, - attempt_count INTEGER NOT NULL DEFAULT 0, - started_at TIMESTAMPTZ, - finished_at TIMESTAMPTZ, - error_code TEXT, - error_message TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_ops_processing_ledger PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_ops_processing_ledger_stage_not_empty CHECK (length(btrim(stage)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_processor_name_not_empty CHECK (length(btrim(processor_name)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_processor_version_not_empty CHECK (length(btrim(processor_version)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_input_key_not_empty CHECK (length(btrim(input_key)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_input_hash_not_empty CHECK (length(btrim(input_hash)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_status CHECK (status IN ('running', 'succeeded', 'failed')), - CONSTRAINT ck_kb_sol_ops_processing_ledger_attempt_count_non_negative CHECK (attempt_count >= 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_error_code_not_empty CHECK (error_code IS NULL OR length(btrim(error_code)) > 0), - CONSTRAINT ck_kb_sol_ops_processing_ledger_error_message_not_empty CHECK (error_message IS NULL OR length(btrim(error_message)) > 0) - )"#; +pub(crate) fn table_stats_k_sol_raw_transactions_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_raw_transactions"; +} +pub(crate) fn table_stats_k_sol_obs_transaction_observations_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(persisted_at)::TEXT AS latest_created_at FROM k_sol_obs_transaction_observations"; +} +pub(crate) fn table_stats_k_sol_obs_account_observations_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(context_slot)::BIGINT AS min_slot, MAX(context_slot)::BIGINT AS max_slot, MAX(persisted_at)::TEXT AS latest_created_at FROM k_sol_obs_account_observations"; +} +pub(crate) fn table_stats_k_sol_core_transactions_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_transactions"; +} +pub(crate) fn table_stats_k_sol_core_account_keys_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_account_keys"; +} +pub(crate) fn table_stats_k_sol_core_instructions_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_instructions"; +} +pub(crate) fn table_stats_k_sol_core_inner_instructions_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_inner_instructions"; +} +pub(crate) fn table_stats_k_sol_core_logs_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_logs"; +} +pub(crate) fn table_stats_k_sol_core_balance_changes_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_balance_changes"; +} +pub(crate) fn table_stats_k_sol_core_return_data_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_return_data"; +} +pub(crate) fn table_stats_k_sol_core_account_states_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_core_account_states"; +} +pub(crate) fn table_stats_k_sol_ops_processing_ledger_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, NULL::BIGINT AS min_slot, NULL::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_ops_processing_ledger"; +} +pub(crate) fn table_stats_k_sol_decode_events_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_decode_events"; +} +pub(crate) fn table_stats_k_sol_decode_coverage_declarations_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, NULL::BIGINT AS min_slot, NULL::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_decode_coverage_declarations"; +} +pub(crate) fn table_stats_k_sol_decode_coverage_observations_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_decode_coverage_observations"; +} +pub(crate) fn table_stats_k_sol_mat_outputs_sql() -> &'static str { + return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot)::BIGINT AS min_slot, MAX(slot)::BIGINT AS max_slot, MAX(created_at)::TEXT AS latest_created_at FROM k_sol_mat_outputs"; } -/// SQL creating the unique processing ledger identity index. -fn create_ux_kb_sol_ops_processing_ledger_identity_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_ops_processing_ledger_identity ON kb_sol_ops_processing_ledger (stage, processor_name, processor_version, input_key)"; -} - -/// SQL creating the processing ledger status index. -fn create_ix_kb_sol_ops_processing_ledger_status_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_ops_processing_ledger_status ON kb_sol_ops_processing_ledger (stage, processor_name, status, updated_at)"; -} - -/// SQL creating the processing ledger input hash index. -fn create_ix_kb_sol_ops_processing_ledger_input_hash_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_ops_processing_ledger_input_hash ON kb_sol_ops_processing_ledger (input_hash)"; -} - -/// SQL statistics query for `kb_sol_raw_transactions`. -pub(crate) fn table_stats_kb_sol_raw_transactions_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_raw_transactions"; -} - -/// SQL statistics query for `kb_sol_obs_transaction_observations`. -pub(crate) fn table_stats_kb_sol_obs_transaction_observations_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(persisted_at)::text AS latest_created_at FROM kb_sol_obs_transaction_observations"; -} - -/// SQL statistics query for `kb_sol_core_transactions`. -pub(crate) fn table_stats_kb_sol_core_transactions_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_core_transactions"; -} - -/// SQL statistics query for `kb_sol_core_account_keys`. -pub(crate) fn table_stats_kb_sol_core_account_keys_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_core_account_keys"; -} - -/// SQL statistics query for `kb_sol_core_instructions`. -pub(crate) fn table_stats_kb_sol_core_instructions_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_core_instructions"; -} - -/// SQL statistics query for `kb_sol_core_inner_instructions`. -pub(crate) fn table_stats_kb_sol_core_inner_instructions_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_core_inner_instructions"; -} - -/// SQL statistics query for `kb_sol_core_logs`. -pub(crate) fn table_stats_kb_sol_core_logs_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_core_logs"; -} - -/// SQL statistics query for `kb_sol_core_balance_changes`. -pub(crate) fn table_stats_kb_sol_core_balance_changes_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_core_balance_changes"; -} - -/// SQL statistics query for `kb_sol_ops_processing_ledger`. -pub(crate) fn table_stats_kb_sol_ops_processing_ledger_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, NULL::BIGINT AS min_slot, NULL::BIGINT AS max_slot, MAX(updated_at)::text AS latest_created_at FROM kb_sol_ops_processing_ledger"; -} - -/// SQL statistics query for `kb_sol_decode_events`. -pub(crate) fn table_stats_kb_sol_decode_events_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_decode_events"; -} - -/// SQL statistics query for `kb_sol_decode_coverage_declarations`. -pub(crate) fn table_stats_kb_sol_decode_coverage_declarations_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, NULL::BIGINT AS min_slot, NULL::BIGINT AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_decode_coverage_declarations"; -} - -/// SQL statistics query for `kb_sol_decode_coverage_observations`. -pub(crate) fn table_stats_kb_sol_decode_coverage_observations_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_decode_coverage_observations"; -} - -/// SQL statistics query for `kb_sol_mat_events`. -pub(crate) fn table_stats_kb_sol_mat_events_sql() -> &'static str { - return "SELECT COUNT(*)::BIGINT AS row_count, MIN(slot) AS min_slot, MAX(slot) AS max_slot, MAX(created_at)::text AS latest_created_at FROM kb_sol_mat_events"; -} - -/// SQL creating `kb_sol_decode_events`. -fn create_table_kb_sol_decode_events_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_decode_events ( - id BIGSERIAL, processor_name TEXT NOT NULL, processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, input_hash TEXT NOT NULL, event_key TEXT NOT NULL, - signature TEXT NOT NULL, slot BIGINT NOT NULL, instruction_path TEXT NOT NULL, - program_id TEXT NOT NULL, protocol_code TEXT NOT NULL, surface_code TEXT NOT NULL, - event_code TEXT NOT NULL, event_name TEXT NOT NULL, event_family TEXT NOT NULL, - source_kind TEXT NOT NULL, confidence TEXT NOT NULL, proof_kind TEXT NOT NULL, - proof_jsonb JSONB NOT NULL, payload_jsonb JSONB NOT NULL, transaction_failed BOOLEAN NOT NULL, - transaction_error_jsonb JSONB, observation_committed BOOLEAN NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_decode_events PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_decode_events_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_decode_events_failed_not_committed CHECK (NOT transaction_failed OR NOT observation_committed) - )"#; -} - -/// SQL creating the decoded observation stable identity index. -fn create_ux_kb_sol_decode_events_identity_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_decode_events_processor_input_event ON kb_sol_decode_events (processor_name, processor_version, input_key, event_key)"; -} - -/// SQL creating the decoded observation signature and path index. -fn create_ix_kb_sol_decode_events_signature_path_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_events_signature_path ON kb_sol_decode_events (signature, instruction_path)"; -} - -/// SQL creating the decoded observation program and surface index. -fn create_ix_kb_sol_decode_events_program_surface_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_events_program_surface ON kb_sol_decode_events (program_id, surface_code, event_code)"; -} - -/// SQL creating the decoded observation family and commit index. -fn create_ix_kb_sol_decode_events_family_commit_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_events_family_commit ON kb_sol_decode_events (event_family, transaction_failed, observation_committed)"; -} - -/// SQL creating `kb_sol_decode_coverage_declarations`. -fn create_table_kb_sol_decode_coverage_declarations_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_decode_coverage_declarations ( - id BIGSERIAL, processor_name TEXT NOT NULL, processor_version TEXT NOT NULL, - program_id TEXT NOT NULL, surface_code TEXT, entry_kind TEXT NOT NULL, - entry_code TEXT NOT NULL, discriminator_hex TEXT, historical BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_decode_coverage_declarations PRIMARY KEY (id) - )"#; -} - -/// SQL creating the declared coverage stable identity index. -fn create_ux_kb_sol_decode_coverage_declarations_identity_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_decode_coverage_declarations_identity ON kb_sol_decode_coverage_declarations (processor_name, processor_version, program_id, COALESCE(surface_code, ''), entry_kind, entry_code, COALESCE(discriminator_hex, ''))"; -} - -/// SQL creating the declared coverage program index. -fn create_ix_kb_sol_decode_coverage_declarations_program_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_coverage_declarations_program ON kb_sol_decode_coverage_declarations (program_id, processor_name, processor_version)"; -} - -/// SQL creating `kb_sol_decode_coverage_observations`. -fn create_table_kb_sol_decode_coverage_observations_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_decode_coverage_observations ( - id BIGSERIAL, processor_name TEXT NOT NULL, processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, input_hash TEXT NOT NULL, signature TEXT NOT NULL, - slot BIGINT NOT NULL, instruction_path TEXT NOT NULL, program_id TEXT NOT NULL, - surface_code TEXT, entry_code TEXT, discriminator_hex TEXT, status TEXT NOT NULL, - recognized BOOLEAN NOT NULL, decoded_count INTEGER NOT NULL DEFAULT 0, - materialized_count INTEGER NOT NULL DEFAULT 0, error_count INTEGER NOT NULL DEFAULT 0, - transaction_failed BOOLEAN NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_decode_coverage_observations PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_decode_coverage_observations_slot_non_negative CHECK (slot >= 0), - CONSTRAINT ck_kb_sol_decode_coverage_observations_counts_non_negative CHECK (decoded_count >= 0 AND materialized_count >= 0 AND error_count >= 0) - )"#; -} - -/// SQL creating the observed coverage stable identity index. -fn create_ux_kb_sol_decode_coverage_observations_identity_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_decode_coverage_observations_identity ON kb_sol_decode_coverage_observations (processor_name, processor_version, input_key)"; -} - -/// SQL creating the observed coverage program and status index. -fn create_ix_kb_sol_decode_coverage_observations_program_status_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_coverage_observations_program_status ON kb_sol_decode_coverage_observations (program_id, status, transaction_failed)"; -} - -/// SQL creating the observed coverage entry index. -fn create_ix_kb_sol_decode_coverage_observations_entry_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_decode_coverage_observations_entry ON kb_sol_decode_coverage_observations (processor_name, processor_version, surface_code, entry_code)"; -} - -/// SQL creating `kb_sol_mat_events`. -fn create_table_kb_sol_mat_events_sql() -> &'static str { - return r#"CREATE TABLE IF NOT EXISTS kb_sol_mat_events ( - id BIGSERIAL, processor_name TEXT NOT NULL, processor_version TEXT NOT NULL, - input_key TEXT NOT NULL, input_hash TEXT NOT NULL, output_key TEXT NOT NULL, - source_event_key TEXT NOT NULL, source_decoder_name TEXT NOT NULL, - source_decoder_version TEXT NOT NULL, source_decode_input_key TEXT NOT NULL, - signature TEXT NOT NULL, slot BIGINT NOT NULL, materialized_family TEXT NOT NULL, - payload_jsonb JSONB NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT pk_kb_sol_mat_events PRIMARY KEY (id), - CONSTRAINT ck_kb_sol_mat_events_slot_non_negative CHECK (slot >= 0) - )"#; -} - -/// SQL creating the materialized output stable identity index. -fn create_ux_kb_sol_mat_events_identity_sql() -> &'static str { - return "CREATE UNIQUE INDEX IF NOT EXISTS ux_kb_sol_mat_events_processor_input_output ON kb_sol_mat_events (processor_name, processor_version, input_key, output_key)"; -} - -/// SQL creating materialized output signature and family indexes. -fn create_ix_kb_sol_mat_events_signature_family_sql() -> &'static str { - return "CREATE INDEX IF NOT EXISTS ix_kb_sol_mat_events_signature_family ON kb_sol_mat_events (source_decoder_name, source_decoder_version, source_decode_input_key, signature, materialized_family)"; +#[cfg(test)] +fn statement_position(statements: &[&str], constraint_name: &str) -> usize { + for (index, statement) in statements.iter().enumerate() { + if statement.contains(constraint_name) { + return index; + } + } + return usize::MAX; } #[cfg(test)] mod tests { #[test] - fn valid_table_name_accepts_domain_prefix() { - assert!(super::is_valid_solana_table_name("kb_sol_raw_transactions")); + fn table_names_use_the_candidate_baseline_prefix() { + assert!(super::is_valid_solana_table_name("k_sol_raw_transactions")); + assert!(!super::is_valid_solana_table_name("kb_sol_raw_transactions")); + assert!(!super::is_valid_solana_table_name("k_sol_unknown_rows")); } #[test] - fn table_name_rejects_explicit_schema() { - let invalid_name = std::string::String::from("raw") + "." + "kb_sol_rpc_transactions"; - assert!(!super::is_valid_solana_table_name(invalid_name.as_str())); - } - - #[test] - fn table_name_rejects_unknown_domain() { - assert!(!super::is_valid_solana_table_name("kb_sol_unknown_rows")); - } - - #[test] - fn table_name_rejects_missing_table_suffix() { - assert!(!super::is_valid_solana_table_name("kb_sol_raw")); - } - - #[test] - fn table_name_rejects_uppercase() { - assert!(!super::is_valid_solana_table_name("kb_sol_raw_RPC_transactions")); - } - - #[test] - fn raw_store_table_names_are_valid() { - let result = crate::validate_raw_store_table_names(); + fn embedded_sql_resources_are_atomic() { + let result = crate::validate_postgres_sql_resources(); assert!(result.is_ok()); } #[test] - fn core_store_table_names_are_valid() { - let result = crate::validate_core_store_table_names(); - assert!(result.is_ok()); + fn candidate_baseline_inventory_has_sixteen_tables_and_seventy_five_indexes() { + let table_count = super::RAW_STORE_TABLE_NAMES.len() + + super::CORE_STORE_TABLE_NAMES.len() + + super::DECODE_STORE_TABLE_NAMES.len(); + assert_eq!(table_count, 16); + assert_eq!(crate::expected_postgres_index_names().len(), 75); } #[test] - fn decode_store_table_names_are_valid() { - let result = crate::validate_decode_store_table_names(); - assert!(result.is_ok()); + fn embedded_resource_inventory_is_complete_and_uses_no_legacy_schema_name() { + let schema_groups = [ + crate::raw_store_schema_statements(), + crate::core_store_schema_statements(), + crate::decode_store_schema_statements(), + ]; + let mut schema_count = 0_usize; + for group in schema_groups { + schema_count += group.len(); + for statement in group { + assert!(!statement.contains("kb_sol_")); + } + } + let maintenance_count = super::truncate_store_schema_statements().len() + + super::drop_store_schema_statements().len(); + assert_eq!(schema_count + maintenance_count, 236); } #[test] - fn decode_store_schema_contains_no_create_schema_statement() { - let forbidden = std::string::String::from("CREATE") + " " + "SCHEMA"; - for statement in crate::decode_store_schema_statements() { - assert!(!statement.contains(forbidden.as_str())); + fn foreign_keys_are_scheduled_after_referenced_primary_keys() { + let mut statements = crate::raw_store_schema_statements(); + statements.extend(crate::core_store_schema_statements()); + statements.extend(crate::decode_store_schema_statements()); + let dependencies = [ + ( + "pk_k_sol_raw_transactions", + "fk_k_sol_obs_transaction_observations_raw_transaction", + ), + ("pk_k_sol_raw_transactions", "fk_k_sol_core_transactions_raw_transaction"), + ("pk_k_sol_obs_account_observations", "fk_k_sol_core_account_states_observation"), + ("pk_k_sol_core_transactions", "fk_k_sol_core_account_keys_transaction"), + ("pk_k_sol_core_transactions", "fk_k_sol_core_balance_changes_transaction"), + ("pk_k_sol_core_transactions", "fk_k_sol_core_inner_instructions_transaction"), + ("pk_k_sol_core_transactions", "fk_k_sol_core_instructions_transaction"), + ("pk_k_sol_core_transactions", "fk_k_sol_core_logs_transaction"), + ("pk_k_sol_core_transactions", "fk_k_sol_core_return_data_transaction"), + ]; + for (required_constraint, dependent_constraint) in dependencies { + let required_position = super::statement_position(&statements, required_constraint); + let dependent_position = super::statement_position(&statements, dependent_constraint); + assert!(required_position < dependent_position); } } #[test] - fn raw_store_schema_contains_no_create_schema_statement() { - let forbidden = std::string::String::from("CREATE") + " " + "SCHEMA"; - for statement in crate::raw_store_schema_statements() { - assert!(!statement.contains(forbidden.as_str())); - } - } - - #[test] - fn core_store_schema_contains_no_create_schema_statement() { - let forbidden = std::string::String::from("CREATE") + " " + "SCHEMA"; - for statement in crate::core_store_schema_statements() { - assert!(!statement.contains(forbidden.as_str())); - } - } - - #[test] - fn raw_store_schema_uses_prefixed_index_names() { - let statements = crate::raw_store_schema_statements(); - assert!(statements.iter().any(|statement| -> bool { - return statement.contains("ux_kb_sol_raw_transactions_signature"); - })); - assert!(statements.iter().any(|statement| -> bool { - return statement.contains("ix_kb_sol_obs_transaction_observations_signature"); - })); - } - - #[test] - fn core_store_schema_uses_prefixed_constraint_and_index_names() { - let statements = crate::core_store_schema_statements(); - assert!(statements.iter().any(|statement| -> bool { - return statement.contains("pk_kb_sol_core_transactions"); - })); - assert!(statements.iter().any(|statement| -> bool { - return statement.contains("fk_kb_sol_core_instructions_transaction"); - })); - assert!(statements.iter().any(|statement| -> bool { - return statement.contains("ux_kb_sol_core_instructions_sig_path"); - })); - assert!(statements.iter().any(|statement| -> bool { - return statement.contains("ix_kb_sol_core_instructions_program"); - })); - assert!(statements.iter().any(|statement| -> bool { - return statement.contains("ux_kb_sol_ops_processing_ledger_identity"); - })); + fn all_store_table_names_are_valid() { + assert!(super::validate_raw_store_table_names().is_ok()); + assert!(super::validate_core_store_table_names().is_ok()); + assert!(super::validate_decode_store_table_names().is_ok()); } } diff --git a/ks-store/src/postgres/query.rs b/ks-store/src/postgres/query.rs index 3c1e37f..a619b56 100644 --- a/ks-store/src/postgres/query.rs +++ b/ks-store/src/postgres/query.rs @@ -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; diff --git a/ks-store/src/postgres/query/core_extraction_queries.rs b/ks-store/src/postgres/query/core_extraction_queries.rs index 634f8e9..e38c6da 100644 --- a/ks-store/src/postgres/query/core_extraction_queries.rs +++ b/ks-store/src/postgres/query/core_extraction_queries.rs @@ -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> { - 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 { let query_result = sqlx::query_scalar::( - "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::( - "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 { - 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::( - "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::, _>("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::, _>("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, + context: &str, +) -> ks_core::Result> { + 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) -> ks_core::Result> { 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::( - "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::( - "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::( - "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::( - "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::( - "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; diff --git a/ks-store/src/postgres/query/core_queries.rs b/ks-store/src/postgres/query/core_queries.rs index 99a80f0..de8e99f 100644 --- a/ks-store/src/postgres/query/core_queries.rs +++ b/ks-store/src/postgres/query/core_queries.rs @@ -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 { - 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::( - "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::( - "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::( - "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::( - "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::( - "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::( - "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> { - 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> { - 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::>(); 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 { - 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 { - 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> { 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> { 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, + message: &str, +) -> ks_core::Result> { + 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 { 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 { let transaction_context_result = - sqlx::query_as::)>( - "SELECT failed, err_json FROM kb_sol_core_transactions WHERE signature = $1 LIMIT 1", + sqlx::query_as::, std::option::Option)>( + "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 { - 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 { - 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 { let like_pattern = format!("{instruction_path}/%"); let query_result = sqlx::query_scalar::( - "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> { + let query_result = sqlx::query_as::( + "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 { - 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 { - 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::>(); 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"}), ); diff --git a/ks-store/src/postgres/query/decode_pipeline_queries.rs b/ks-store/src/postgres/query/decode_pipeline_queries.rs index e8db070..8caca6c 100644 --- a/ks-store/src/postgres/query/decode_pipeline_queries.rs +++ b/ks-store/src/postgres/query/decode_pipeline_queries.rs @@ -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> { - 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> { + 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::( - "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::( + "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 { 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::( - "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::( - "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::( - "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; diff --git a/ks-store/src/postgres/query/health_queries.rs b/ks-store/src/postgres/query/health_queries.rs index 2eee2b0..4462698 100644 --- a/ks-store/src/postgres/query/health_queries.rs +++ b/ks-store/src/postgres/query/health_queries.rs @@ -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> { - let query_result = - sqlx::query_scalar::>( - "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> { - let query_result = sqlx::query_scalar::>( - "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}" - ))), - }; -} diff --git a/ks-store/src/postgres/query/raw_queries.rs b/ks-store/src/postgres/query/raw_queries.rs index aaff0d0..3af5df2 100644 --- a/ks-store/src/postgres/query/raw_queries.rs +++ b/ks-store/src/postgres/query/raw_queries.rs @@ -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 { - 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::( - "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 { - 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::( - "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 { - 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::( - "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 { - 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::( - "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 { fn optional_sql_slot_from_u64( slot: std::option::Option, ) -> ks_core::Result> { - 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 { - 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 { diff --git a/ks-store/src/postgres/query/replay_candidate_queries.rs b/ks-store/src/postgres/query/replay_candidate_queries.rs index 297dd73..6608759 100644 --- a/ks-store/src/postgres/query/replay_candidate_queries.rs +++ b/ks-store/src/postgres/query/replay_candidate_queries.rs @@ -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> { let query_result = sqlx::query_as::( 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, diff --git a/ks-store/src/postgres/query/schema_queries.rs b/ks-store/src/postgres/query/schema_queries.rs new file mode 100644 index 0000000..8eada31 --- /dev/null +++ b/ks-store/src/postgres/query/schema_queries.rs @@ -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 { + let pattern = format!("{}%", crate::LEGACY_SOLANA_TABLE_PREFIX); + let query_result = sqlx::query_scalar::( + "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::>(); + 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::( + "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)); + } +} diff --git a/ks-store/src/postgres/query/table_diagnostics_queries.rs b/ks-store/src/postgres/query/table_diagnostics_queries.rs index acd2926..e0c9598 100644 --- a/ks-store/src/postgres/query/table_diagnostics_queries.rs +++ b/ks-store/src/postgres/query/table_diagnostics_queries.rs @@ -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 diff --git a/ks-store/src/postgres/repository/decode_pipeline_repository.rs b/ks-store/src/postgres/repository/decode_pipeline_repository.rs index 5b5bdf7..da2b88c 100644 --- a/ks-store/src/postgres/repository/decode_pipeline_repository.rs +++ b/ks-store/src/postgres/repository/decode_pipeline_repository.rs @@ -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> { - return crate::list_materialized_events(self.pool(), filter).await; + filter: &crate::MaterializedOutputFilter, + ) -> ks_core::Result> { + return crate::list_materialized_outputs(self.pool(), filter).await; } } diff --git a/ks-store/src/postgres/store.rs b/ks-store/src/postgres/store.rs index 770f79e..e7b7dcf 100644 --- a/ks-store/src/postgres/store.rs +++ b/ks-store/src/postgres/store.rs @@ -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 { 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::(); + 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 { - 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 { - 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::()")); + 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); diff --git a/ks-store/src/store.rs b/ks-store/src/store.rs index 1502f27..19ca8e4 100644 --- a/ks-store/src/store.rs +++ b/ks-store/src/store.rs @@ -1,5 +1,5 @@ // file: ks-store/src/store.rs -// version: 9 +// version: 10 //! Backend-agnostic store facade and connection ownership. @@ -266,6 +266,15 @@ async fn open_postgres(backend_options: serde_json::Value) -> ks_core::Result value, + std::result::Result::Err(_error) => { + return std::result::Result::Err(crate::storage_contract_error( + "store_initialization_verification_failed", + "store index verification failed before initialization", + )); + }, + }; if auto_initialize_schema { let initialize_result = store.initialize_store_schema().await; if let std::result::Result::Err(_error) = initialize_result { @@ -284,10 +293,24 @@ async fn open_postgres(backend_options: serde_json::Value) -> ks_core::Result value, + std::result::Result::Err(_error) => { + return std::result::Result::Err(crate::storage_contract_error( + "store_initialization_verification_failed", + "store index verification failed after initialization", + )); + }, + }; return std::result::Result::Ok(crate::Store { backend: StoreBackend::Postgres(store), configuration, - initialization: build_initialization_summary(&before, &after), + initialization: build_initialization_summary( + &before, + &after, + before_indexes, + after_indexes, + ), }); } @@ -334,6 +357,8 @@ fn disabled_initialization_summary() -> crate::StoreInitializationSummary { fn build_initialization_summary( before: &[crate::StoreResourceDiagnostics], after: &[crate::StoreResourceDiagnostics], + before_indexes: (u32, u32), + after_indexes: (u32, u32), ) -> crate::StoreInitializationSummary { let model_codes = ["raw", "core", "processing", "decode", "materialization"]; let mut models = std::vec::Vec::new(); @@ -378,7 +403,9 @@ fn build_initialization_summary( available_resource_count += available; created_resource_count += created; } - let status = if expected_resource_count == available_resource_count { + let resources_ready = expected_resource_count == available_resource_count; + let indexes_ready = after_indexes.0 == after_indexes.1; + let status = if resources_ready && indexes_ready { crate::StoreInitializationStatus::Ready } else { crate::StoreInitializationStatus::Partial @@ -390,12 +417,20 @@ fn build_initialization_summary( expected_resource_count, available_resource_count, created_resource_count, - objects: std::vec![crate::StoreObjectVerificationSummary { - object_kind: "table".to_string(), - expected_count: expected_resource_count, - available_count: available_resource_count, - created_count: created_resource_count, - }], + objects: std::vec![ + crate::StoreObjectVerificationSummary { + object_kind: "table".to_string(), + expected_count: expected_resource_count, + available_count: available_resource_count, + created_count: created_resource_count, + }, + crate::StoreObjectVerificationSummary { + object_kind: "index".to_string(), + expected_count: after_indexes.0, + available_count: after_indexes.1, + created_count: after_indexes.1.saturating_sub(before_indexes.1), + }, + ], }; } @@ -911,14 +946,14 @@ impl crate::DecodePipelineStore for crate::Store { 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> { + filter: &crate::MaterializedOutputFilter, + ) -> ks_core::Result> { return match &self.backend { StoreBackend::Disabled => std::result::Result::Err(disabled_store_error()), StoreBackend::Postgres(store) => { - crate::DecodePipelineStore::list_materialized_events(store, filter).await + crate::DecodePipelineStore::list_materialized_outputs(store, filter).await }, }; } @@ -929,7 +964,7 @@ mod tests { #[test] fn backend_diagnostic_errors_are_sanitized_at_the_facade_boundary() { let result: ks_core::Result<()> = std::result::Result::Err(ks_core::Error::db( - "backend detail references kb_sol_private_table", + "backend detail references k_sol_private_table", )); let error = match super::sanitize_backend_result( result, @@ -939,7 +974,7 @@ mod tests { std::result::Result::Ok(()) => panic!("backend error must remain an error"), std::result::Result::Err(error) => error, }; - assert!(!error.to_string().contains("kb_sol_private_table")); + assert!(!error.to_string().contains("k_sol_private_table")); assert!(error.to_string().contains("store resource diagnostics failed")); } @@ -959,17 +994,39 @@ mod tests { available: true, statistics: std::option::Option::None, }]; - let summary = super::build_initialization_summary(&before, &after); - assert_eq!(summary.objects.len(), 1); + let summary = super::build_initialization_summary(&before, &after, (3, 1), (3, 3)); + assert_eq!(summary.objects.len(), 2); assert_eq!(summary.objects[0].object_kind, "table"); assert_eq!(summary.objects[0].expected_count, 1); assert_eq!(summary.objects[0].available_count, 1); assert_eq!(summary.objects[0].created_count, 1); + assert_eq!(summary.objects[1].object_kind, "index"); + assert_eq!(summary.objects[1].expected_count, 3); + assert_eq!(summary.objects[1].available_count, 3); + assert_eq!(summary.objects[1].created_count, 2); let serialized = match serde_json::to_string(&summary) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("summary must serialize: {error}"), }; - assert!(!serialized.contains("kb_sol_")); + assert!(!serialized.contains("k_sol_")); + } + + #[test] + fn initialization_summary_is_partial_when_expected_indexes_are_missing() { + let resource = crate::StoreResourceDiagnostics { + resource_code: "raw_transactions".to_string(), + model_code: "raw".to_string(), + role: "canonical raw transaction".to_string(), + available: true, + statistics: std::option::Option::None, + }; + let summary = super::build_initialization_summary( + std::slice::from_ref(&resource), + std::slice::from_ref(&resource), + (3, 2), + (3, 2), + ); + assert_eq!(summary.status, crate::StoreInitializationStatus::Partial); } #[test] diff --git a/scripts/audit_khadhroony_workspace_rules.py b/scripts/audit_khadhroony_workspace_rules.py index fa56027..a75c4db 100644 --- a/scripts/audit_khadhroony_workspace_rules.py +++ b/scripts/audit_khadhroony_workspace_rules.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # file: scripts/audit_khadhroony_workspace_rules.py -# version: 31 +# version: 32 """Audit mechanically verifiable rules specific to khadhroony-bot3.""" @@ -1425,6 +1425,38 @@ def audit_store_backend_boundary(root: pathlib.Path) -> list[Violation]: "PostgreSQL tracing must declare one structured `action` field", ) ) + if crate_root.is_file(): + exported_types = { + match.group(1) + for match in re.finditer( + r"(?m)^pub use self::[^;]+::([A-Z][A-Za-z0-9_]*)\s*;", + crate_root.read_text(encoding="utf-8"), + ) + } + store_source_root = root / "ks-store/src" + for path in sorted(store_source_root.rglob("*.rs")): + if path == crate_root: + continue + relative = path.relative_to(root).as_posix() + for index, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + stripped = line.lstrip() + if stripped.startswith("//") or stripped.startswith("#["): + continue + code = re.sub(r'"(?:\\.|[^"\\])*"', '""', line) + for type_name in sorted(exported_types): + declaration_pattern = rf"\bpub(?:\([^)]*\))?\s+(?:struct|enum|type|trait|const)\s+{re.escape(type_name)}\b" + if re.search(declaration_pattern, code): + continue + if re.search(rf"(?