Ptah in der Praxis
24 im Terminal aufgezeichnete Ptah-Abläufe. Die Ausgaben stammen aus echten Ausführungen. Öffnen Sie ein Beispiel zum Lesen oder wählen Sie Abspielen für die Wiedergabe.
-
# Das Schema der Datenbank in eine Datei schreiben.$ ptah db read --db-url sqlite://app.db | tee schema.sqlCREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL); # Die Driftkontrolle vergleicht die Datei mit ihrer Quelldatenbank.$ ptah schema drift --schema-file schema.sql \ --db-url sqlite://app.dbNo schema drift detected. # Eine Spalte zum gewünschten Schema hinzufügen.$ cat > schema.sql <<'SQL'CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL, "created_at" TEXT);SQL # Dieselbe Prüfung hat jetzt eine Abweichung zu melden.$ 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) # Die nötigen Änderungen ermitteln. Ein Dry Run führt nichts aus.$ ptah schema apply --schema-file schema.sql \ --db-url sqlite://app.db --dry-runPlanned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TEXT; # Den geprüften Plan ausführen. --auto-approve eignet sich für eine entbehrliche Datei.$ 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. # Dieselbe Prüfung zum dritten Mal ausführen.$ ptah schema drift --schema-file schema.sql \ --db-url sqlite://app.dbNo schema drift detected.
-
# Aus der Deklaration eine versionierte Migration erzeugen.$ 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 # Die Down-Datei entsteht gleichzeitig mit der Up-Datei.$ 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 # Die Revisionstabelle bestätigt das Ergebnis.$ ptah migrations status --db-url sqlite://app.db \ --migrations-dir ./migrations=== MIGRATION STATUS ===Database: ***Current Version: 1788809709Applied Migrations: 1Pending Migrations: 0
-
# Jede erzeugte Migration enthält eine Down-Datei.$ ls migrations1788810699_create_users.down.sql1788810699_create_users.up.sql # Die Wirkung eines Rollbacks prüfen. Nichts wird ausgeführt.$ 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] # Erst der Plan, dann der Rollback. Dieselbe Regel wie beim Vorwärtsmigrieren.
-
# Welches SQL überführt die eine Datei in die andere?$ 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"); # Zwei beliebige Zustände, ohne laufende Datenbank.
-
# Ein neues Embedding-Modell. Zuerst die nötigen Schritte planen.$ ptah inference plansource.estimated_rows = 3 (measured)target.capability.vector_type = true (measured)[backfill] embed 3 in-scope source rowsConsistency mode: outbox # Die neue Generation neben der aktiven aufbauen.$ 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 # Zwischenzeitlich geänderte Zeilen nachziehen und anschließend indizieren.$ ptah inference catchup --batch-rows 10caught up to transaction 761: 0 changed rows, 0 tombstoned$ ptah inference indexgeneration b7cc9eb5c7d2 has a valid index # Prüfen, bevor die neue Generation gelesen wird.$ ptah inference verify3 source rows, 3 target rowsevery deterministic layer passed # Abfragen lesen noch die bisherigen Vektoren. Die Umschaltung ist ein eigener Schritt.$ ptah inference cutoverplan 5bbecb32c113cutover refused: - this policy requires an approval and none was givenerror: cutover refused # Genau diesen Plan anhand seines Digests freigeben.$ 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 # Der Zeiger für die Abfragen verweist nun auf die neue Generation.$ psql -c 'SELECT target_table, active_generation FROM ptah_embedding_pointer;' target_table | active_generation--------------+------------------------------------------------------------------ docs | b7cc9eb5c7d2ee13ad46ba3d5da8a479ec57b128d771e4151fab5bf6d1da0866(1 row)
-
# Ein Pull Request fügt diese Migration hinzu.$ cat migrations/1700000100_drop_email.up.sqlALTER TABLE "users" DROP COLUMN "email"; # Prüfen, bevor die Migration eine Datenbank erreicht.$ 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 # Der Exit-Code lässt den Build fehlschlagen.$ echo $?1
-
# Den Plan speichern, damit genau das Geprüfte ausgeführt wird.$ 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 # Signieren. Den Schlüssel liest ssh-keygen, nicht Ptah.$ 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 # Einen Bezeichner im freigegebenen Plan ändern.$ 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 # Exit-Code 2. Für den veränderten Plan liegt keine Freigabe vor.
-
# Seed-Dateien tragen die Umgebung im Namen.$ 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. # Erneut ausführen. Angewendete Seeds sind protokolliert, daher ändert sich nichts.$ 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. # Die Angabe prod allein berechtigt nicht zur Ausführung in der Produktion.$ ptah seed --db-url sqlite://app.db --seeds-dir ./seeds --env proderror: refusing to seed protected environment "prod" without --allow-prod
-
# Das Schema ist ein Go-Struct. Die Annotationen bilden die Deklaration.$ 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} # Für PostgreSQL ausgeben.$ 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); # Dieselbe Datei mit einem anderen Flag.$ 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 hat keinen Enum-Typ, daher entsteht eine CHECK-Bedingung. Nichts geht verloren.
-
# Eine Datenbank, für die keine Schemadatei existiert.$ 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) # Das Ergebnis ist annotierter Go-Code, wie er auch von Hand geschrieben würde.$ 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} # Die Bedingung behält ihren Namen und ihre referenziellen Aktionen.
-
# Die von Ptah ermittelten Eigenschaften des verbundenen Servers abfragen.$ 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 erklärt, warum ein deklariertes Enum hier eine CHECK-Bedingung wird.
-
# Die Datenbank in einem Format auslesen, das ein anderes Werkzeug versteht.$ ptah schema inspect --db-url sqlite://app.db --format dbmlTable "users" { "id" INTEGER [pk] "email" TEXT [not null]} # Oder als JSON für die maschinelle Verarbeitung.$ 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"}]}}]}]} # Weitere Formate sind hcl und sql. --out-dir schreibt Dateien.
-
# Welche View-Spalten hängen von welchen Basisspalten ab?$ ptah schema lineage --schema-file schema.sql \ --dialect sqliteSOURCE FEEDS KINDusers.email active_users.email viewusers.id active_users.id view # Die Analyse ist statisch: Ohne --db-url entsteht keine Verbindung.
-
# Die Schemastruktur in einem Format ausgeben, das die Metrik-Pipeline bereits liest.$ 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 # Objekte zählen, keine Datenzeilen. Die Struktur statt der Inhalte erfassen.
-
# Die Tabellen beschreiben bereits die API, die sie liest.$ 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!} # Das letzte Feld ist der Fremdschlüssel als Beziehung.
-
# Die Tabellen beschreiben auch die übertragenen Datenstrukturen.$ 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 wird zu required. Beide Angaben stimmen überein.
-
# Dasselbe Schema als Vertrag für den Datenaustausch.$ 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 # Die Warnung ist entscheidend: Feldnummern sind verbindlich.$ 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;} # Ein späterer Export verwendet diese Nummern erneut oder verweigert das Schreiben.
-
# Das Diagrammwerkzeug benötigt DBML. Ein anderes Flag genügt.$ 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" # Keine zweite Beschreibung ist dafür nötig.
-
# Die Referenzseite erzeugen, die sonst von Hand gepflegt werden müsste.$ 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 | — | — | — | # Für HTML bleibt der Befehl gleich; nur --to ändert sich.
-
# Die Deklaration als Diagramm darstellen.$ 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" # Die Beziehung wird aus dem Fremdschlüssel gelesen, nicht aus einer zweiten Datei.
-
# Das Schema selbst als Artefakt mit Digest veröffentlichen.$ 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] # Genau die beiden angeforderten Tags verweisen darauf.$ ptah oci tags oci://registry.example/acme/schemalatestv1 # Ein veränderlicher Tag wird zum unveränderlichen Digest aufgelöst.$ ptah oci resolve oci://registry.example/acme/schema:latestoci://registry.example/acme/schema@sha256:66fed834333daafa24448c7c6b5fe548c4f429d036977194f5b417c505fb933f
-
# Die Deklaration des Artefakts abfragen, ohne es herunterzuladen.$ 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 Bytes Manifest liefern die Antwort. Die Nutzdaten bleiben in der Registry.
-
# Eine Registry-Referenz ist eine Schemaquelle, genau wie eine Datei.$ 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); # Oder das Artefakt in der gespeicherten kanonischen HCL-Form herunterladen.$ 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] }}
-
# Eine vorhandene Atlas-Pipeline mit denselben Flags.$ 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 # Ein Atlas-Verzeichnis wurde erzeugt: eine Datei mit Zeitstempel und atlas.sum.$ cat migrations/atlas.sumh1:9q+8PcnP03kKT2aZzOj4MHlWEilRmbPXaScNvs2FJzI=20260907193826_add_users.sql h1:eXKVFuXvHFnneZKiWkpyJSHeF5q8KlZCCXJqM7JNymE= # apply liest dasselbe Verzeichnis.$ ptah-compat migrate apply --dir file://migrations \ --url sqlite://app.dbMigrating to version 20260907193826 from 1 pending migrations.Migration complete. Current version: 20260907193826 # Kein Skript wurde geändert. Die nativen Funktionen stehen weiterhin zur Verfügung.