On this page
Generate Synthetic Data from SQL DDL
misata.from_ddl() parses SQL CREATE TABLE statements and converts them into a SchemaConfig ready for data generation. Paste your real schema DDL and get synthetic data that matches your database structure, column types, foreign key relationships, and NOT NULL constraints all preserved.
Quick start#
import misata
schema = misata.from_ddl("""
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
amount DECIMAL(10, 2),
status VARCHAR(50),
placed_at TIMESTAMP
);
""")
tables = misata.generate_from_schema(schema)
print(tables["users"].head())
print(tables["orders"].head())
# FK integrity guaranteed
assert tables["orders"]["user_id"].isin(tables["users"]["id"]).all()Supported SQL dialects#
- PostgreSQL:
SERIAL,BIGSERIAL,TIMESTAMP WITH TIME ZONE, schema-qualified names,"quoted"identifiers - MySQL:
INT AUTO_INCREMENT,DATETIME,TINYINT - SQLite:
INTEGER PRIMARY KEY,TEXT,REAL - Generic ANSI SQL: standard
CREATE TABLEwithREFERENCESclauses
SQL type mapping#
| SQL types | Misata type |
|---|---|
INT, INTEGER, BIGINT, SMALLINT, SERIAL | int |
FLOAT, REAL, DOUBLE, DECIMAL, NUMERIC, MONEY | float |
VARCHAR, TEXT, CHAR, STRING, UUID, JSON, JSONB | text |
DATE | date |
TIMESTAMP, DATETIME (any variant) | date |
BOOLEAN, BOOL | boolean |
Foreign key detection#
Three sources of FK relationships are supported:
1. Inline REFERENCES clause:
user_id INT REFERENCES users(id)2. Standalone FOREIGN KEY constraint:
FOREIGN KEY (user_id) REFERENCES users(id)3. Naming convention inference (infer_fks=True, default):
Columns ending in _id that don't have an explicit REFERENCES clause are automatically inferred as foreign keys to the table named by the prefix:
-- order_id → orders(id) inferred automatically
order_id INTDisable with infer_fks=False if you don't want this behavior.
Configuration#
schema = misata.from_ddl(
ddl,
infer_fks=True, # infer FKs from _id naming convention (default True)
default_rows=1000, # row count per table (default 1000)
)Adjust per-table row counts after parsing:
schema = misata.from_ddl(ddl)
# Customize row counts
for table in schema.tables:
if table.name == "users":
table.row_count = 5000
elif table.name == "orders":
table.row_count = 20000
tables = misata.generate_from_schema(schema)Full example: PostgreSQL schema#
import misata
schema = misata.from_ddl("""
CREATE TABLE public.customers (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
country VARCHAR(2),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE public.products (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10, 2) NOT NULL,
category VARCHAR(100),
created_at TIMESTAMP WITH TIME ZONE
);
CREATE TABLE public.orders (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES public.customers(id),
status VARCHAR(50) DEFAULT 'pending',
total NUMERIC(12, 2),
placed_at TIMESTAMP WITH TIME ZONE
);
CREATE TABLE public.order_items (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES public.orders(id),
product_id BIGINT NOT NULL REFERENCES public.products(id),
quantity INT NOT NULL DEFAULT 1,
unit_price NUMERIC(10, 2) NOT NULL
);
""", default_rows=500)
# Scale appropriately
for t in schema.tables:
if t.name == "order_items":
t.row_count = 2000
tables = misata.generate_from_schema(schema, min_quality_score=80)
for name, df in tables.items():
print(f"{name}: {len(df)} rows")Export to your database#
Generate and seed directly:
import misata
schema = misata.from_ddl(open("schema.sql").read())
# Seed PostgreSQL: generates and inserts in one transaction
misata.seed_database(schema, "postgresql://user:pass@localhost/mydb")Constraints the importer carries into generation#
Generated rows are meant to insert into the schema they were imported from, so the constraints a database would reject them for are part of the import:
| DDL | Becomes |
|---|---|
PRIMARY KEY (single column), inline UNIQUE, UNIQUE (col) | unique: true. A text primary key becomes a code (CUS-########, or a UUID when it is 36+ characters wide), never a sentence |
VARCHAR(n), CHAR(n) | max_length: n: longer values are cut at a word boundary, and a uniqueness suffix never pushes a value past its width |
CHAR(2) named like country, state, lang; CHAR(3) named like currency | ISO 3166 / US state / ISO 639 / ISO 4217 codes instead of full names |
NUMERIC(p, s) / DECIMAL(p, s) | decimals: s |
CHECK (col IN ('a', 'b')) | categorical choices |
CHECK (col BETWEEN x AND y), CHECK (col >= x AND col < y) | min / max (strict bounds step inward) |
CHECK (length(col) <= n) | max_length |
A CHECK the importer cannot translate (an OR, two columns, a function
call) is counted in a warning: the database still enforces it, so seeding can
fail on it, and because seed_database runs in one transaction a failure
leaves the database exactly as it was.
Limitations#
- Computed / generated columns:
GENERATED ALWAYS ASexpressions are not evaluated; the column is treated as its base type - Untranslatable
CHECKconstraints: see above; declare the rule inmisata.yamlinstead - Composite
UNIQUE (a, b): not enforced during generation - Enum types:
CREATE TYPE ... AS ENUMis not parsed; usemisata.yamlto define categorical choices - Complex DEFAULT expressions:
DEFAULT NOW(),DEFAULT uuid_generate_v4()are ignored; Misata generates values from type-appropriate distributions
Related#
- YAML Schema, full control over distributions and constraints
- Database Seeding, seed your generated data into a real database
- Validate, verify generated data quality
- Seed a dev database from its DDL, the end-to-end recipe with an attestation of 22 invariants
- Validity benchmark, violations per schema against a Faker script and SDV
Prefer generating data visually?
Misata Studio lets you turn natural language prompts into relational schemas with exact outcome invariants, live preview tables, and one-click database seeding without writing code.

