Ptah en pratique
24 exécutions de Ptah enregistrées dans le terminal. Les sorties proviennent d’exécutions réelles. Ouvrez un exemple pour le lire ou appuyez sur Lire pour le regarder.
-
# Écrire le schéma de la base dans un fichier.$ ptah db read --db-url sqlite://app.db | tee schema.sqlCREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL); # Le contrôle de dérive compare ce fichier à la base dont il provient.$ ptah schema drift --schema-file schema.sql \ --db-url sqlite://app.dbNo schema drift detected. # Ajouter une colonne au schéma souhaité.$ cat > schema.sql <<'SQL'CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL, "created_at" TEXT);SQL # Le même contrôle a maintenant une différence à signaler.$ ptah schema drift --schema-file schema.sql \ --db-url sqlite://app.dbSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: sqlite://app.db Findings:- columns_added: 1 (warning) # Déterminer les changements nécessaires. Une simulation n’exécute rien.$ ptah schema apply --schema-file schema.sql \ --db-url sqlite://app.db --dry-runPlanned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TEXT; # Exécuter le plan revu. --auto-approve convient à un fichier jetable.$ ptah schema apply --schema-file schema.sql \ --db-url sqlite://app.db --auto-approvePlanned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TEXT;Auto-approval enabled; applying schema changes.Schema apply completed successfully. # Exécuter le même contrôle une troisième fois.$ ptah schema drift --schema-file schema.sql \ --db-url sqlite://app.dbNo schema drift detected.
-
# Transformer la déclaration en migration versionnée.$ ptah migrations generate --root-dir ./entities \ --db-url sqlite://app.db --migrations-dir ./migrations \ --name create_users_and_postsGenerated migration files for sqlite://app.db:UP: /tmp/app/migrations/1788809709_create_users_and_posts.up.sqlDOWN: /tmp/app/migrations/1788809709_create_users_and_posts.down.sql # Le fichier down est écrit en même temps que le fichier up.$ ptah migrations up --db-url sqlite://app.db \ --migrations-dir ./migrations=== MIGRATE UP ===Current version: 0Total migrations: 1Pending migrations: 1 ✅ Migrations completed successfully!Database is now at version: 1788809709 # La table des révisions confirme le résultat.$ ptah migrations status --db-url sqlite://app.db \ --migrations-dir ./migrations=== MIGRATION STATUS ===Database: ***Current Version: 1788809709Applied Migrations: 1Pending Migrations: 0
-
# Chaque migration générée possède un fichier down.$ ls migrations1788810699_create_users.down.sql1788810699_create_users.up.sql # Examiner les effets d’un retour arrière. Rien n’est exécuté.$ ptah migrations down --db-url sqlite://app.db \ --migrations-dir ./migrations --dry-run=== DRY RUN MODE ===No actual changes will be made to the database === MIGRATE DOWN ===Target version: 0Current version: 1788810699Migrations to roll back: 1 ✅ Dry run completed successfully!Would have rolled back to version: 0Would have rolled back these migrations: [1788810699] # D’abord le plan, puis le retour arrière. La même règle que pour avancer.
-
# Quel SQL permet de passer d’un fichier à l’autre ?$ ptah schema diff --from file://schema.sql \ --to file://schema-next.sql \ --dev-url "sqlite://dev.db?mode=memory"ALTER TABLE "users" ADD COLUMN "created_at" TEXT;CREATE INDEX IF NOT EXISTS "idx_users_email" ON "users" ("email"); # Deux états quelconques, sans base en cours d’exécution.
-
# Un nouveau modèle d’embeddings. Planifier d’abord les étapes nécessaires.$ ptah inference plansource.estimated_rows = 3 (measured)target.capability.vector_type = true (measured)[backfill] embed 3 in-scope source rowsConsistency mode: outbox # Construire la nouvelle génération à côté de la génération active.$ ptah inference prepareprepared run quick-start for generation b7cc9eb5c7d2 - snapshot boundary: 753$ ptah inference backfill --batch-rows 10backfill finished: 3 scanned, 3 embedded, 0 skipped # Rattraper les lignes modifiées pendant l’exécution, puis les indexer.$ ptah inference catchup --batch-rows 10caught up to transaction 761: 0 changed rows, 0 tombstoned$ ptah inference indexgeneration b7cc9eb5c7d2 has a valid index # Vérifier la candidate avant toute lecture.$ ptah inference verify3 source rows, 3 target rowsevery deterministic layer passed # Les requêtes lisent encore les anciens vecteurs. La bascule est une étape distincte.$ ptah inference cutoverplan 5bbecb32c113cutover refused: - this policy requires an approval and none was givenerror: cutover refused # Approuver ce plan exact à l’aide de son digest.$ export PTAH_APPROVE=5bbecb32c113$ ptah inference cutover --approver 'quick-start check'queries now read generation b7cc9eb5c7d2ee13ad46ba3d5da8a479ec57b128d771e4151fab5bf6d1da0866 (plan 5bbecb32c113) - this is the first generation over this target, so there is no previous one to keep current and nothing to roll back to # Le pointeur suivi par les requêtes désigne maintenant la nouvelle génération.$ psql -c 'SELECT target_table, active_generation FROM ptah_embedding_pointer;' target_table | active_generation--------------+------------------------------------------------------------------ docs | b7cc9eb5c7d2ee13ad46ba3d5da8a479ec57b128d771e4151fab5bf6d1da0866(1 row)
-
# Une pull request ajoute cette migration.$ cat migrations/1700000100_drop_email.up.sqlALTER TABLE "users" DROP COLUMN "email"; # La vérifier avant qu’elle n’atteigne une base de données.$ ptah migrations lint --dir ./migrations \ --dialect postgresmigrations/1700000100_drop_email.up.sql:1 [warning] BC104: dropping a column retires a name application versions already deployed against the old schema still select and insert, so each of them starts failing the moment this migration commits, whether or not the column held any rows; deploy readers that no longer use the column first, then drop it in a later release (dropped column breaks deployed code)migrations/1700000100_drop_email.up.sql:1 [error] DS102: DROP COLUMN permanently deletes the column's data; deploy readers that no longer use the column first, then drop it in a later release (column dropped) 2 finding(s).warning: DS110P ran without the baseline schema it reads, so this analysis is thinner than the same directory would get against a dev database the run can read # Le code de retour fait échouer le build.$ echo $?1
-
# Enregistrer le plan pour exécuter exactement ce qui a été revu.$ ptah schema plan --schema-file schema.sql \ --db-url sqlite://app.db --name add-active-users \ --output plan.jsonPlanned schema changes:CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL, "legacy_ref" TEXT);Plan saved to file://plan.json # Signer le plan. ssh-keygen lit la clé ; Ptah ne la lit jamais.$ ptah schema approve --plan plan.json --key ./release_keyApproved plan.jsonSignature: plan.json.sig$ ptah schema verify-approval --plan plan.jsonApproved by release@example.comPlan digest: cb74d5837d3efb8c1a8c93bd930fd0321d12abc5e13fd731ff129c43bf0bfc41 # Modifier un identifiant dans le plan approuvé.$ sed -i 's/legacy_ref/legacy_id/g' plan.json$ ptah schema verify-approval --plan plan.jsonerror: approval does not verify against .ptah/allowed_signers: either the plan changed after it was approved, or it was signed by a key that file does not list # Code de retour 2. Le plan modifié n’a été approuvé par personne.
-
# Le nom des fichiers de données initiales indique leur environnement.$ ls seeds001_demo_accounts.dev.sql002_reference_data.prod.sql$ ptah seed --db-url sqlite://app.db --seeds-dir ./seeds --env dev=== SEED ===Environment: dev Matching seeds: 1Applied seeds: 1Seeds completed successfully. # Relancer l’exécution. Les chargements appliqués sont enregistrés : rien ne change.$ ptah seed --db-url sqlite://app.db --seeds-dir ./seeds --env devMatching seeds: 1Applied seeds: 0Skipped seeds: 1Database seed data is already up to date. # Saisir prod ne suffit pas à autoriser une exécution en production.$ ptah seed --db-url sqlite://app.db --seeds-dir ./seeds --env proderror: refusing to seed protected environment "prod" without --allow-prod
-
# Le schéma est une struct Go. Les annotations constituent la déclaration.$ cat entities/user.gopackage entities //ptah:schema:table name="users"type User struct { //ptah:schema:field name="id" type="SERIAL" primary="true" ID int64 //ptah:schema:field name="email" type="VARCHAR(255)" not_null="true" Email string //ptah:schema:field name="plan" type="ENUM" enum="free,team,enterprise" Plan string} # Produire le SQL pour PostgreSQL.$ ptah schema render --root-dir . --dialect postgres-- Statement 1/2CREATE TYPE "enum_user_plan" AS ENUM ('free', 'team', 'enterprise'); -- Statement 2/2-- POSTGRES TABLE: users --CREATE TABLE "users" ( "id" SERIAL PRIMARY KEY NOT NULL, "email" VARCHAR(255) NOT NULL, "plan" enum_user_plan); # Le même fichier avec une seule option différente.$ ptah schema render --root-dir . --dialect sqlite-- Statement 1/1CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL, "plan" TEXT CHECK (plan IN ('free', 'team', 'enterprise'))); # SQLite n’a pas de type enum : une contrainte CHECK le remplace. Rien n’est perdu.
-
# Une base pour laquelle aucun fichier de schéma n’a été écrit.$ ptah introspect --db-url sqlite://app.db --out ./modelsGenerated 2 Go file(s) in /tmp/app/modelsImported 2 table(s), 5 field(s), 0 enum(s) # Le résultat est le Go annoté que l’on aurait écrit à la main.$ cat models/posts.go// Code generated by ptah introspect; DO NOT EDIT. package models //ptah:schema:table name="posts"type Posts struct { //ptah:schema:field name="id" type="INTEGER" primary="true" Id *int //ptah:schema:field name="user_id" type="INT" not_null="true" foreign="users(id)" foreign_key_name="fk_posts_user_id" on_delete="NO ACTION" on_update="NO ACTION" UserId int //ptah:schema:field name="title" type="TEXT" not_null="true" Title string} # La contrainte conserve son nom et ses actions référentielles.
-
# Consulter les propriétés que Ptah a déterminées pour le serveur connecté.$ ptah db capabilities --db-url sqlite://app.dbDialect: sqliteServer version: 3.53.4Capability preset: SQLite3 (sqlite)Preset source: version-ladderSupport level: certified Behavior: identifier_limit unlimited enum_modeling unsupported foreign_key_reference unique Capabilities: advisory_locks unsupported catalog_partitions supported # enum_modeling explique pourquoi un enum déclaré devient ici une contrainte CHECK.
-
# Relire la base dans un format accepté par un autre outil.$ ptah schema inspect --db-url sqlite://app.db --format dbmlTable "users" { "id" INTEGER [pk] "email" TEXT [not null]} # Ou en JSON pour un traitement automatique.$ ptah schema inspect --db-url sqlite://app.db --format json{"schemas":[{"name":"main","tables":[{"name":"users","columns":[{"name":"id","type":"INTEGER","null":true},{"name":"email","type":"TEXT"}],"primary_key":{"parts":[{"column":"id"}]}}]}]} # Les autres formats sont hcl et sql. --out-dir écrit des fichiers.
-
# Quelles colonnes de vues dépendent de quelles colonnes de base ?$ ptah schema lineage --schema-file schema.sql \ --dialect sqliteSOURCE FEEDS KINDusers.email active_users.email viewusers.id active_users.id view # L’analyse est statique : sans --db-url, aucune connexion n’est établie.
-
# La structure du schéma dans un format déjà lu par la chaîne de métriques.$ ptah schema stats --db-url sqlite://app.db# HELP ptah_schema_tables Tables.# TYPE ptah_schema_tables gaugeptah_schema_tables{dialect="sqlite"} 2# HELP ptah_schema_columns Columns across all tables.# TYPE ptah_schema_columns gaugeptah_schema_columns{dialect="sqlite"} 5# HELP ptah_schema_indexes Indexes.# TYPE ptah_schema_indexes gaugeptah_schema_indexes{dialect="sqlite"} 0 # Compter les objets, jamais les lignes. Décrire la structure, pas les données.
-
# Les tables décrivent déjà l’API qui les lit.$ ptah schema export --to graphql --root-dir ./entities \ --out schema.graphqlExported GraphQL schema to /tmp/app/schema.graphqlFound 2 table(s), 5 field(s), 0 enum(s)$ cat schema.graphql# Code generated by ptah; DO NOT EDIT. type User { id: ID! email: String!} type Post { id: ID! user_id: Int! title: String! user: User!} # Le dernier champ est la clé étrangère représentée comme relation.
-
# Les tables décrivent aussi les structures de données échangées.$ ptah schema export --to openapi-v3 --from sql \ --schema-file schema.sql --out openapi.yamlExported OpenAPI schema to /tmp/app/openapi.yamlFound 2 table(s), 5 field(s), 0 enum(s)$ head -20 openapi.yamlopenapi: 3.0.3info: title: Ptah Exported Schema version: 1.0.0paths: {}components: schemas: users: type: object required: - id - email properties: id: type: integer format: int32 # NOT NULL devient required. Les deux restent cohérents.
-
# Le même schéma comme contrat d’échange.$ ptah schema export --to protobuf --from sql \ --schema-file schema.sql --out schema.proto \ --proto-package acme.app.v1Exported Protobuf schema to /tmp/app/schema.protoExported 2 message(s), 5 field(s), 0 enum(s)bootstrapped new compatibility history1 export warning(s) reported # L’avertissement est essentiel : les numéros de champs sont un engagement.$ head -14 schema.protoedition = "2023"; package acme.app.v1; message Post { int32 id = 1; int32 user_id = 2; string title = 3;} message User { int32 id = 1; string email = 2;} # Un export ultérieur réutilise ces numéros ou refuse d’écrire.
-
# L’outil de diagrammes attend du DBML. Une seule option change.$ ptah schema export --to dbml --root-dir ./entities \ --out schema.dbmlExported DBML schema to /tmp/app/schema.dbmlFound 2 table(s), 5 field(s), 0 enum(s)$ cat schema.dbmlTable "posts" { "id" SERIAL [pk] "user_id" INT [not null] "title" VARCHAR(200) [not null]} Table "users" { "id" SERIAL [pk] "email" VARCHAR(255) [not null]} Ref "posts"."user_id" > "users"."id" # Aucune description en double n’a été nécessaire.
-
# Générer la page de référence qu’il faudrait sinon tenir à jour à la main.$ ptah schema export --to markdown --from sql \ --schema-file schema.sql --out SCHEMA.mdExported schema documentation to /tmp/app/SCHEMA.mdFound 2 table(s), 5 field(s), 0 enum(s)$ head -13 SCHEMA.md# Schema reference - [posts](#posts)- [users](#users) ## posts | Column | Type | Null | Default | Key | Comment || --- | --- | --- | --- | --- | --- || id | INTEGER | yes | — | PK | — || user_id | INTEGER | no | — | FK → users(id) | — || title | TEXT | no | — | — | — | # Pour HTML, la commande reste la même ; seule l’option --to change.
-
# La déclaration devient aussi un diagramme.$ ptah viz --root-dir ./entities --format mermaid \ --include-columnserDiagram users { SERIAL id PK VARCHAR_255 email } posts { SERIAL id PK INT user_id FK VARCHAR_200 title } users ||--o{ posts : "fk_posts_user_id" # La relation est déduite de la clé étrangère, sans second fichier.
-
# Publier le schéma lui-même comme artefact identifié par un digest.$ ptah schema push oci://registry.example/acme/schema:v1 \ --schema-file schema.sql --dialect sqlite --latestPushed schema as oci://registry.example/acme/schema:v1Digest: sha256:66fed834333daafa24448c7c6b5fe548c4f429d036977194f5b417c505fb933fVersion: Tags: [v1 latest] # Seuls les deux tags demandés pointent vers cet artefact.$ ptah oci tags oci://registry.example/acme/schemalatestv1 # Un tag modifiable se résout en digest immuable.$ ptah oci resolve oci://registry.example/acme/schema:latestoci://registry.example/acme/schema@sha256:66fed834333daafa24448c7c6b5fe548c4f429d036977194f5b417c505fb933f
-
# Consulter la déclaration de l’artefact sans le télécharger.$ ptah oci inspect oci://registry.example/acme/schema:v1Reference: oci://registry.example/acme/schema:v1Digest: sha256:66fed834333daafa24448c7c6b5fe548c4f429d036977194f5b417c505fb933fMedia type: application/vnd.oci.image.manifest.v1+jsonSize: 843Artifact type: application/vnd.stokaro.ptah.schema.v1Discovery: none Annotations: io.stokaro.ptah.kind application/vnd.stokaro.ptah.schema.v1 io.stokaro.ptah.schema-format hcl org.opencontainers.image.revision 112a72244a81db149899df0ce43afc704a152bc4 Files: NAME MEDIA TYPE SIZE DIGEST schema.hcl application/vnd.stokaro.ptah.schema.hcl.v1 483 sha256:11719c3690781f43aadc70a236e5494dd5435e9418b6197e4048fc3a5fd23366 # 843 octets de manifeste suffisent. Les données de l’artefact restent sur place.
-
# Une référence de registre est une source de schéma, comme un fichier.$ ptah schema render \ --schema-file oci://registry.example/acme/schema:v1 \ --dialect postgres-- Statement 1/3-- POSTGRES TABLE: users --CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY NOT NULL, "email" TEXT NOT NULL); # Ou télécharger l’artefact dans la forme HCL canonique sous laquelle il a été stocké.$ ptah schema pull oci://registry.example/acme/schema:v1 \ --out pulled.hclPulled oci://registry.example/acme/schema:v1 to /tmp/app/pulled.hclDigest: sha256:66fed834333daafa24448c7c6b5fe548c4f429d036977194f5b417c505fb933f$ head -12 pulled.hcl// Code generated by ptah; DO NOT EDIT. table "posts" { column "id" { type = INTEGER } primary_key { columns = [column.id] } foreign_key "fk_posts_user_id" { columns = [column.user_id] ref_columns = [table.users.column.id] }}
-
# Une pipeline Atlas existante, avec les mêmes options.$ ptah-compat migrate diff add_users --dir file://migrations \ --to file://schema.sql \ --dev-url "sqlite://dev.db?mode=memory"Created migration file: /tmp/app/migrations/20260907193826_add_users.sqlUpdated migration checksum: /tmp/app/migrations/atlas.sum # Un répertoire Atlas a été écrit : le fichier horodaté et atlas.sum.$ cat migrations/atlas.sumh1:9q+8PcnP03kKT2aZzOj4MHlWEilRmbPXaScNvs2FJzI=20260907193826_add_users.sql h1:eXKVFuXvHFnneZKiWkpyJSHeF5q8KlZCCXJqM7JNymE= # apply lit ce même répertoire.$ ptah-compat migrate apply --dir file://migrations \ --url sqlite://app.dbMigrating to version 20260907193826 from 1 pending migrations.Migration complete. Current version: 20260907193826 # Aucun script n’a changé. Les fonctions natives restent disponibles.