---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';
const basicPull = `# Generate schema from existing database
prax db pull
# Outputs to ./schema.prax (current directory) by default
# Introspects tables, columns, indexes, relations, views
# Specify output file
prax db pull --output ./my-schema.prax
# Overwrite existing without prompting
prax db pull --force`;
const filteringTables = `# Filter by table pattern
prax db pull --tables "user*" # Tables starting with "user"
prax db pull --tables "auth_*" # Auth-related tables
# Exclude tables
prax db pull --exclude "_prisma*" # Skip Prisma internals
prax db pull --exclude "temp_*,log_*" # Skip temp and log tables
# Specific schema/namespace (PostgreSQL)
prax db pull --schema public`;
const includeViews = `# Include views in introspection
prax db pull --include-views
# Include materialized views
prax db pull --include-materialized-views
# Both
prax db pull --include-views --include-materialized-views`;
const outputFormats = `# Output as Prax schema (default)
prax db pull --format prax
# Output as JSON (for tooling integration)
prax db pull --format json --output schema.json
# Output as SQL (CREATE TABLE statements)
prax db pull --format sql --output schema.sql
# Print to stdout instead of file
prax db pull --print
prax db pull --format json --print | jq '.tables[].name'`;
const generatedSchema = `# Example output from PostgreSQL database
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prax-client-rust"
output = "./src/generated"
}
model User {
id Int @id @default(autoincrement())
email String @unique @db.VarChar(255)
name String? @db.VarChar(100)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
posts Post[]
profile Profile?
@@map("users")
@@index([email])
}
model Post {
id Int @id @default(autoincrement())
title String @db.VarChar(255)
content String? @db.Text
published Boolean @default(false)
authorId Int @map("author_id")
createdAt DateTime @default(now()) @map("created_at")
author User @relation(fields: [authorId], references: [id])
@@map("posts")
@@index([authorId])
}
// Introspected view
view UserStats {
userId Int @map("user_id")
postCount Int @map("post_count")
totalViews Int @map("total_views")
@@sql("""
SELECT user_id, COUNT(*) as post_count, SUM(views) as total_views
FROM posts GROUP BY user_id
""")
@@map("user_stats")
}`;
const programmaticApi = `// The introspector lives in the CLI crate (requires the "postgres" feature):
use prax_cli::commands::introspect::{
Introspector, IntrospectionOptions,
postgres::PostgresIntrospector,
};
use prax_query::introspection::generate_prax_schema;
// Introspect a PostgreSQL database programmatically
let options = IntrospectionOptions {
schema: Some("public".to_string()),
include_views: true,
include_materialized_views: true,
table_filter: None, // e.g. Some("user*".to_string())
exclude_pattern: Some("_prisma*".to_string()),
include_comments: true,
sample_size: 100, // reserved for MongoDB (not yet supported)
};
let introspector = PostgresIntrospector::new(connection_string);
let schema: DatabaseSchema = introspector.introspect(&options).await?;
// Access schema information
for table in &schema.tables {
println!("Table: {}", table.name);
for col in &table.columns {
println!(" {} {} {}",
col.name,
col.data_type,
if col.is_nullable { "NULL" } else { "NOT NULL" }
);
}
}
// Generate a Prax schema string (takes only the DatabaseSchema)
let prax_schema = generate_prax_schema(&schema);
std::fs::write("schema.prax", prax_schema)?;
// Export as JSON
let json = serde_json::to_string_pretty(&schema)?;
std::fs::write("schema.json", json)?;`;
const mongoInference = `// NOTE: not wired into \`prax db pull\` in v0.11 (PostgreSQL only).
// The inferrer itself is available in prax-query for programmatic use:
use prax_query::introspection::mongodb::SchemaInferrer;
// SchemaInferrer::new() takes no arguments — feed document samples in:
let mut inferrer = SchemaInferrer::new();
for doc in sample_documents {
inferrer.add_document(&doc); // doc: serde_json::Value
}
// Extract the inferred collection schema as a TableInfo:
let table = inferrer.to_table_info("users");
for col in &table.columns {
println!("{}: {}", col.name, col.data_type);
}
// Inferred types based on document analysis:
// - String, Int, Float, Boolean, Date, ObjectId
// - Array<T> with element type inference
// - Embedded documents as nested types
// - Union types for fields with multiple types
// Example inferred schema:
// model User {
// id String @id @map("_id")
// email String
// name String?
// age Int?
// tags String[]
// createdAt DateTime
// }`;
const typeMapping = `// Type mapping from database to Prax types
| PostgreSQL | MySQL | SQLite | MSSQL | Prax Type |
|-----------------|-----------------|-----------|-----------------|-----------|
| INTEGER | INT | INTEGER | INT | Int |
| BIGINT | BIGINT | INTEGER | BIGINT | BigInt |
| SMALLINT | SMALLINT | INTEGER | SMALLINT | Int |
| SERIAL | AUTO_INCREMENT | - | IDENTITY | Int @auto |
| VARCHAR(n) | VARCHAR(n) | TEXT | NVARCHAR(n) | String |
| TEXT | TEXT | TEXT | NVARCHAR(MAX) | String |
| BOOLEAN | TINYINT(1) | INTEGER | BIT | Boolean |
| TIMESTAMP | DATETIME | TEXT | DATETIME2 | DateTime |
| DATE | DATE | TEXT | DATE | DateTime |
| DECIMAL(p,s) | DECIMAL(p,s) | REAL | DECIMAL(p,s) | Decimal |
| FLOAT/REAL | FLOAT/DOUBLE | REAL | FLOAT/REAL | Float |
| JSONB/JSON | JSON | TEXT | NVARCHAR(MAX) | Json |
| BYTEA | BLOB | BLOB | VARBINARY(MAX) | Bytes |
| UUID | CHAR(36) | TEXT | UNIQUEIDENTIFIER| String |
| ARRAY | - | - | - | Type[] |`;
---
<DocsLayout title="Schema Introspection - Prax ORM">
<article class="max-w-4xl mx-auto px-6 py-12">
<header class="mb-12">
<h1 class="text-4xl font-bold mb-4">Schema Introspection</h1>
<p class="text-xl text-muted">
Generate Prax schemas from existing databases with the <code>prax db pull</code> command.
</p>
</header>
<div class="space-y-12">
<!-- Introduction -->
<section>
<h2 class="text-2xl font-semibold mb-4">Overview</h2>
<p class="text-muted mb-4">
Schema introspection analyzes your existing database and generates a Prax schema file.
This is useful for migrating existing projects or keeping your schema in sync with database changes.
</p>
<div class="p-4 mb-6 rounded-xl bg-warning-500/10 border border-warning-500/30">
<p class="text-warning-400 text-sm">
<strong>PostgreSQL only in v0.11:</strong> <code>prax db pull</code> currently supports
PostgreSQL <strong>only</strong> and requires the CLI to be built with the
<code>postgres</code> feature. Other providers are rejected before connecting.
</p>
</div>
<div class="grid md:grid-cols-3 gap-4">
<div class="p-4 rounded-xl bg-surface border border-border">
<div class="text-2xl mb-2">📊</div>
<h4 class="font-semibold mb-1">Tables & Columns</h4>
<p class="text-muted text-sm">Types, constraints, defaults</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<div class="text-2xl mb-2">🔗</div>
<h4 class="font-semibold mb-1">Relations</h4>
<p class="text-muted text-sm">Foreign keys, references</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<div class="text-2xl mb-2">📇</div>
<h4 class="font-semibold mb-1">Indexes & Views</h4>
<p class="text-muted text-sm">Performance optimizations</p>
</div>
</div>
</section>
<!-- Basic Usage -->
<section>
<h2 class="text-2xl font-semibold mb-4">Basic Usage</h2>
<p class="text-muted mb-4">
The simplest way to generate a schema from your database.
</p>
<CodeBlock code={basicPull} lang="bash" />
</section>
<!-- Filtering -->
<section>
<h2 class="text-2xl font-semibold mb-4">Filtering Tables</h2>
<p class="text-muted mb-4">
Include or exclude specific tables using glob patterns.
</p>
<CodeBlock code={filteringTables} lang="bash" />
</section>
<!-- Views -->
<section>
<h2 class="text-2xl font-semibold mb-4">Including Views</h2>
<p class="text-muted mb-4">
Optionally include views and materialized views in the generated schema.
</p>
<CodeBlock code={includeViews} lang="bash" />
</section>
<!-- Output Formats -->
<section>
<h2 class="text-2xl font-semibold mb-4">Output Formats</h2>
<p class="text-muted mb-4">
Export the schema in different formats for various use cases.
</p>
<CodeBlock code={outputFormats} lang="bash" />
</section>
<!-- MongoDB -->
<section>
<h2 class="text-2xl font-semibold mb-4">MongoDB Schema Inference</h2>
<div class="p-4 rounded-xl bg-info-500/10 border border-info-500/30">
<p class="text-info-400 text-sm">
<strong>Deferred:</strong> MongoDB schema inference is not available through
<code>prax db pull</code> in v0.11 — the pull path is PostgreSQL-only. The
<code>SchemaInferrer</code> API in <code>prax-query</code> can be used programmatically
(see <a href="#mongodb-type-inference" class="underline">MongoDB Type Inference</a> below).
</p>
</div>
</section>
<!-- Generated Schema -->
<section>
<h2 class="text-2xl font-semibold mb-4">Generated Schema Example</h2>
<p class="text-muted mb-4">
Here's what an introspected schema looks like:
</p>
<CodeBlock code={generatedSchema} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Programmatic API -->
<section>
<h2 class="text-2xl font-semibold mb-4">Programmatic API</h2>
<p class="text-muted mb-4">
Use the introspection API directly in your Rust code.
</p>
<CodeBlock code={programmaticApi} lang="rust" filename="src/introspect.rs" />
</section>
<!-- MongoDB Inference -->
<section id="mongodb-type-inference">
<h2 class="text-2xl font-semibold mb-4">MongoDB Type Inference</h2>
<p class="text-muted mb-4">
Prax analyzes document samples to infer types, including arrays and embedded documents
(programmatic API only in v0.11).
</p>
<CodeBlock code={mongoInference} lang="rust" filename="src/introspect.rs" />
</section>
<!-- Type Mapping -->
<section>
<h2 class="text-2xl font-semibold mb-4">Type Mapping</h2>
<p class="text-muted mb-4">
How database types are mapped to Prax types:
</p>
<CodeBlock code={typeMapping} lang="text" />
</section>
<!-- CLI Options -->
<section>
<h2 class="text-2xl font-semibold mb-4">CLI Options Reference</h2>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-3 px-4 font-semibold">Option</th>
<th class="text-left py-3 px-4 font-semibold">Description</th>
<th class="text-left py-3 px-4 font-semibold">Default</th>
</tr>
</thead>
<tbody class="text-muted">
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--output, -o</code></td>
<td class="py-3 px-4">Output file path</td>
<td class="py-3 px-4">./schema.<ext></td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--force, -f</code></td>
<td class="py-3 px-4">Overwrite without prompting</td>
<td class="py-3 px-4">false</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--schema, -s</code></td>
<td class="py-3 px-4">Database schema to introspect</td>
<td class="py-3 px-4">public</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--tables</code></td>
<td class="py-3 px-4">Glob pattern for tables</td>
<td class="py-3 px-4">* (all)</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--exclude</code></td>
<td class="py-3 px-4">Glob pattern to exclude</td>
<td class="py-3 px-4">none</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--include-views</code></td>
<td class="py-3 px-4">Include views</td>
<td class="py-3 px-4">false</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--include-materialized-views</code></td>
<td class="py-3 px-4">Include materialized views</td>
<td class="py-3 px-4">false</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--comments</code></td>
<td class="py-3 px-4">Include column comments</td>
<td class="py-3 px-4">false</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--format</code></td>
<td class="py-3 px-4">Output format (prax, json, sql)</td>
<td class="py-3 px-4">prax</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--print</code></td>
<td class="py-3 px-4">Print to stdout</td>
<td class="py-3 px-4">false</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>--sample-size</code></td>
<td class="py-3 px-4">MongoDB sample size (deferred — pull is PostgreSQL-only in v0.11)</td>
<td class="py-3 px-4">100</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- Best Practices -->
<section>
<h2 class="text-2xl font-semibold mb-4">Best Practices</h2>
<div class="grid gap-4">
<div class="p-4 rounded-xl bg-surface border border-border">
<h4 class="font-semibold mb-2 text-success-400">Review Generated Schema</h4>
<p class="text-muted text-sm">
Always review the generated schema. Introspection infers relations from foreign keys,
but you may want to add or adjust relation names for clarity.
</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<h4 class="font-semibold mb-2 text-success-400">Use Version Control</h4>
<p class="text-muted text-sm">
Commit your schema file to version control. Compare diffs after re-introspecting
to catch unexpected database changes.
</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<h4 class="font-semibold mb-2 text-warning-400">MongoDB Support Deferred</h4>
<p class="text-muted text-sm">
MongoDB introspection via <code>prax db pull</code> is deferred — the pull path is
PostgreSQL-only in v0.11. Use the programmatic <code>SchemaInferrer</code> API instead.
</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<h4 class="font-semibold mb-2 text-info-400">Exclude Internal Tables</h4>
<p class="text-muted text-sm">
Use <code>--exclude</code> to skip migration tables, Prisma internals (_prisma*),
and other non-application tables.
</p>
</div>
</div>
</section>
</div>
</article>
</DocsLayout>