Skip to content
PtahDocs
v0.13.0

Graphic preview

100%
Page type: reference

Go annotation reference

Every //ptah directive and attribute accepted by Ptah's Go annotation parser.

Every //ptah comment directive and attribute accepted by Ptah’s Go annotation parser is listed below. The same metadata is exported as a JSON Schema document by ptah schema annotations, and that document is published at https://docs.ptah.run/ptah-annotations.schema.json, which is the address it declares as its own $id. Point an editor’s JSON Schema setting at that URL to get completion and validation for //ptah directives. Each published version also serves its own copy, so /edge/ptah-annotations.schema.json is the one built from the development source. For the workflow — modeling, rendering, and generating migrations from annotated structs — see Go annotations.

A directive is a single Go comment line: the directive name followed by space-separated key="value" attributes.

//ptah:schema:table name="products"
  • Attributes marked “bare form allowed” may be written without a value: primary is equivalent to primary="true".
  • An unknown attribute fails parsing with unknown annotation attribute "<name>" on //<directive>.
  • Directives marked “Platform overrides: yes” also accept platform.<dialect>.<attribute>="..." pairs that override the base attribute for one dialect, for example platform.mysql.type="JSON" platform.mariadb.type="LONGTEXT".
  • The Required column records what the parser rejects. An attribute the renderer needs for valid SQL (such as name on a table) can still be omitted at parse time; the rendered SQL is then invalid. ptah schema render is the cheapest way to catch that early.

Each directive attaches to one of three places in Go source:

  • struct — the comment lines directly above a type ... struct declaration. Any struct works, including empty marker structs declared only to carry directives, and one struct can carry several directives.
  • field — the comment line directly above a struct field. Directives that allow both struct and field placement (index, constraint) can use a blank _ int placeholder field inside the struct.
  • file — a detached comment line anywhere in a Go file, separated from declarations by blank lines (row-level security directives only). The named table must be declared in the same file; a file-scoped RLS comment naming a table from another file is silently ignored.
//ptah:schema:table name="users"
//ptah:schema:constraint name="users_email_check" type="CHECK" check="email <> ''"
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:index name="idx_users_email" fields="email"
_ int
}
//ptah:schema:enum name="user_status" values="active,disabled"
type StatusEnumMarker struct{}
Directive Declares Placement
ptah:schema:table A database table struct
ptah:schema:field A table column field
ptah:embedded Columns or relations from an embedded Go field field
ptah:schema:index An index struct or field
ptah:schema:constraint A table constraint struct or field
ptah:schema:columnfamily A YDB column family of a table struct or field
ptah:schema:changefeed A YDB changefeed of a table struct or field
ptah:schema:changefeed:consumer A consumer of a YDB changefeed’s topic struct or field
ptah:schema:enum A reusable enum type struct
ptah:schema:domain A PostgreSQL domain type struct
ptah:schema:composite A PostgreSQL composite type struct
ptah:schema:range A PostgreSQL range type struct
ptah:schema:schema A database schema/namespace struct
ptah:schema:extension A PostgreSQL extension struct
ptah:schema:sequence A standalone PostgreSQL sequence struct
ptah:schema:function A database function struct
ptah:schema:procedure A stored procedure struct
ptah:schema:trigger A database trigger struct
ptah:schema:view A database view struct
ptah:schema:matview A materialized view struct
ptah:schema:topic A YDB topic struct or field
ptah:schema:topic:consumer A consumer of a YDB topic struct or field
ptah:schema:resourcepool A YDB resource pool struct or field
ptah:schema:resourcepool:classifier A YDB resource pool classifier struct or field
ptah:schema:coordinationnode A YDB coordination node struct
ptah:schema:async_replication A YDB async replication struct or field
ptah:schema:async_replication:item A table an async replication copies struct or field
ptah:schema:transfer A YDB transfer from a topic into a table struct or field
ptah:schema:role A database role struct
ptah:schema:grant Database grants struct
ptah:schema:revoke Privileges a role must not hold struct
ptah:schema:defaultprivilege A PostgreSQL default privilege struct
ptah:schema:rls:enable Row-level security enablement file or struct
ptah:schema:rls:policy A row-level security policy file or struct
ptah:schema:secret A YDB secret, by the variable its value comes from struct or field
ptah:schema:externaldatasource A YDB external data source struct or field
ptah:schema:externaltable A YDB external table over files in object storage struct or field
ptah:schema:data Reference/seed row data for a table struct
ptah:schema:notdescribed What this schema does not describe struct

Placement is semantic, not cosmetic. A struct directive belongs in the doc comment of a Go struct declaration; a field directive belongs in the doc comment of the field it describes. File-scoped RLS directives may use a separate comment group, but they still need enough attributes to resolve to a parsed RLS object. ptah schema export --cleanup-go-annotations refuses to remove any recognized standalone directive that does not meet those conditions. Directive names use an exact token boundary: //ptah:schema:tableau is an ordinary comment, not a table directive.

Every directive that declares a standalone database object accepts dialects, a comma-separated list of the targets the object belongs to:

//ptah:schema:function name="get_current_tenant_id" returns="TEXT" language="plpgsql" dialects="postgres,cockroachdb,yugabytedb" body="BEGIN RETURN current_setting('app.tenant_id', true); END;"
type CurrentTenant struct{}

An object whose dialects excludes the target is absent from that target’s desired state. It is not skipped and not refused: nothing compares it, nothing plans it, and ptah schema render prints a note on stderr naming what was left out and which dialects it was declared for.

Absence is what makes a shared schema converge. Without a scope, a PostgreSQL-only object on a MySQL target is either passed over with a comment — in which case schema apply exits 0 having created nothing and the next comparison asks for the same object again, forever — or refused outright, which makes one schema across postgres, mysql and mariadb impossible.

Rules:

  • Omitting dialects means every dialect. Declarations written before this attribute existed are unaffected, and a scope can only narrow an object, never widen one.
  • Every accepted spelling resolves to the same target. dialects="postgresql" and dialects="postgres" select the same dialect.
  • A dialect family is not implied. dialects="postgres" does not include cockroachdb, yugabytedb or spanner; name each target you mean.
  • A scope that names no supported dialect is a parse error, including an empty dialects="". Reading a typo as “belongs to nothing” would drop the object from every target with every command still exiting 0.
  • ptah schema export --to hcl reports the scope as an export loss. Atlas HCL cannot carry it, so --cleanup-go-annotations refuses to delete an annotation whose scope the exported file would not preserve.

dialects is accepted on extension, sequence, domain, composite, range, function, trigger, view, matview, role, grant, revoke, rls:enable and rls:policy. Directives that describe table structure — table, field, index, constraint, embedded, enum and schema — do not accept it.

Maps a Go struct to a database table.

Attribute Required Description
checks No Comma-separated table-level check expressions.
comment No Table comment.
depends_on No Comma-separated tables this table must be created after.
custom No Raw custom CREATE TABLE SQL.
engine No MySQL/MariaDB table engine shortcut; see the note below the table.
name No Table name.
primary_key No Comma-separated primary key columns.
primary_key_block_size No MySQL-family primary index block-size hint. Zero uses the engine default.
primary_key_comment No MySQL-family primary index comment.
row_deletion_column No Row deletion policy (Spanner and YDB TTL): the column a row’s age is measured from. Needs row_deletion_interval.
row_deletion_interval No Row deletion policy: how long after the column’s time a row is deleted, such as P30D on YDB or 30 days on Spanner.
row_deletion_unit No YDB TTL on an integer column: what the column counts since the Unix epoch, SECONDS, MILLISECONDS, MICROSECONDS or NANOSECONDS.
schema No Database schema name.
ttl_delete_batch_size No CockroachDB row-level TTL: rows deleted per batch; at least 1.
ttl_delete_rate_limit No CockroachDB row-level TTL: rows deleted per second; at least 1.
ttl_disable_changefeed_replication No CockroachDB row-level TTL: omits the job’s deletes from changefeeds. true/false.
ttl_expiration_expression No CockroachDB row-level TTL: SQL expression whose value is when a row expires. Enables the TTL.
ttl_expire_after No CockroachDB row-level TTL: interval after a row is written at which it expires, such as 3 days. Enables the TTL.
ttl_job_cron No CockroachDB row-level TTL: cron schedule for the deletion job.
ttl_label_metrics No CockroachDB row-level TTL: labels the job’s metrics with the table name. true/false.
ttl_pause No CockroachDB row-level TTL: pauses the deletion job without removing the policy. true/false.
ttl_select_batch_size No CockroachDB row-level TTL: rows selected per batch; at least 1.
ttl_select_rate_limit No CockroachDB row-level TTL: rows selected per second; at least 1.

The table engine, character set, collation and AUTO_INCREMENT start are MySQL-family options. A PostgreSQL-family target renders none of them and names each one it was handed on a skipped comment above the statement; declare a key’s start there with identity_start on the column.

The ttl_ attributes declare CockroachDB row-level TTL and are refused on every other dialect before anything is applied. Either ttl_expiration_expression or ttl_expire_after enables the policy, and the rest are refused without one. One real CockroachDB parameter is deliberately absent — ttl_row_stats_poll_interval — because the server rewrites the duration it stores and drops a value below one second entirely, so a declaration could never read back as written; declaring it is refused by name with the reason. See CockroachDB row-level TTL.

The row_deletion_ attributes declare a row deletion policy, which Spanner and YDB have and every other target refuses. A policy needs its column and its interval, and each engine reads the interval in its own spelling. See YDB TTL.

A YDB row table also takes its partitioning, its read replicas and its key bloom filter, in attributes spelled as YDB names the settings, in lower case. A setting the table leaves out keeps what the table holds. Every other dialect refuses a table that declares one, rather than build it with the server’s defaults. See table partitioning.

Attribute Value
auto_partitioning_by_size ENABLED or DISABLED
auto_partitioning_partition_size_mb megabytes, at least 1
auto_partitioning_by_load ENABLED or DISABLED
auto_partitioning_min_partitions_count at least 1
auto_partitioning_max_partitions_count at least 1
read_replicas_settings PER_AZ:<n> or ANY_AZ:<n>
key_bloom_filter ENABLED or DISABLED
uniform_partitions partitions a new table starts with, at least 1
partition_at_keys split points a new table starts with: 10, 20 or (10, 'a'), (20)

Platform overrides: yes.

Maps a Go struct field to a database column.

Attribute Required Description
auto_increment No Marks the column as auto-incrementing. true/false; bare form allowed.
check No Column CHECK expression.
check_name No Explicit CHECK constraint name.
comment No Column comment.
default No Literal column default.
default_expr No SQL default expression.
enum No Comma-separated enum values.
foreign No Foreign key reference in table(column) form.
foreign_key_name No Explicit foreign key constraint name.
generated No Generated column expression.
generated_kind No Generated column kind, such as STORED or VIRTUAL.
identity_generation No SQL identity generation mode.
identity_increment No SQL identity increment value.
identity_options No Raw SQL identity options.
identity_start No SQL identity start value.
name No Column name.
not_null No Marks the column NOT NULL. true/false; bare form allowed.
on_delete No Foreign key ON DELETE action.
on_update No Foreign key ON UPDATE action.
primary No Marks the column as part of the primary key. true/false; bare form allowed.
stored No Shortcut controlling generated column storage. true/false.
type No Database column type.
unique No Adds a single-column unique constraint. true/false; bare form allowed.
unique_expr No Uniqueness over an expression. Not implemented: rendering fails with column "…" declares unique_expr, because emitting the column’s own UNIQUE instead would enforce a different constraint.

Platform overrides: yes.

Controls how an embedded Go field contributes schema objects.

Attribute Required Description
comment No Generated column comment.
field No Generated relation field name.
mode No Embedding mode: inline, json, or relation.
name No Column name for json embedding.
nullable No Marks generated embedded columns nullable. true/false; bare form allowed.
on_delete No Generated foreign key ON DELETE action.
on_update No Generated foreign key ON UPDATE action.
prefix No Column prefix for inline embedded fields.
ref No Relation target in table(column) form.
type No Generated column type for json or relation embedding.

Platform overrides: yes.

For relation embedding, set type to the referenced column’s physical type. When it is omitted, Ptah uses a conservative numeric or string heuristic, which cannot infer every user-defined or dialect-specific key type.

Declares an index for a table.

Attribute Required Description
columns No Synonym for fields.
comment No Index comment.
condition No Partial index condition.
fields No Comma-separated Go field or column names.
granularity No ClickHouse data-skipping index granularity.
key_block_size No MySQL-family index block-size hint. Zero uses the engine default. See MySQL and MariaDB for retention and range limits.
include No Comma-separated INCLUDE columns for PostgreSQL, YugabyteDB, CockroachDB, or the Spanner PostgreSQL dialect, and COVER columns on YDB. Order is preserved, and a changed list rebuilds the index.
invisible No Hides the index from the optimizer: INVISIBLE on MySQL, IGNORED on MariaDB, NOT VISIBLE on CockroachDB. true/false; bare form allowed. A target whose capability set does not carry invisible_indexes refuses it at render time rather than building a visible index.
name No Index name.
nulls_distinct No Controls NULLS DISTINCT behavior. true/false. The clause is PostgreSQL’s; a target whose capability set does not carry unique_nulls_distinct_clause refuses it at render time rather than dropping it, in either spelling.
ops No PostgreSQL operator class.
table No Explicit target table.
type No Index type or method. On YDB, async builds a GLOBAL ASYNC index and vector_kmeans_tree a vector index.
unique No Creates a unique index. true/false; bare form allowed.
where No Atlas-style partial index condition alias.

Omit include when the index has no payload columns. A present value with any empty element fails parsing instead of silently removing that element. This includes empty, whitespace-only, comma-only, sparse, and trailing-comma lists.

The accepted access methods depend on the target: PostgreSQL accepts the default, BTREE, and GIST, plus SPGIST on PostgreSQL 14 and newer; YugabyteDB accepts the default and LSM, with BTREE normalized to its documented default-LSM alias; CockroachDB accepts the default and BTREE, and refuses GIN and GIST, both of which name an inverted index there; the Spanner PostgreSQL dialect accepts only the default; and YDB writes the columns as COVER (...) on a synchronous or an asynchronous global index. Every other dialect rejects include before emitting SQL.

A YDB global index also takes its partitioning, in attributes spelled as YDB names the settings, in lower case. Every other dialect refuses an index that declares one, rather than build it with the server’s defaults. See index partitioning.

Attribute Value
auto_partitioning_by_size ENABLED or DISABLED
auto_partitioning_partition_size_mb megabytes, at least 1
auto_partitioning_by_load ENABLED or DISABLED
auto_partitioning_min_partitions_count at least 1
auto_partitioning_max_partitions_count at least 1
read_replicas_settings PER_AZ:<n> or ANY_AZ:<n>

A YDB vector index, type="vector_kmeans_tree", takes its settings in attributes spelled as YDB names them. It names one of distance and similarity, and every other one. Every other dialect refuses an index that declares them. See vector indexes.

Attribute Value
distance cosine, euclidean or manhattan
similarity inner_product or cosine
vector_type float, uint8, int8 or bit
vector_dimension 1 to 16384
levels 1 to 16
clusters 2 to 2048

CockroachDB’s catalog names its access methods prefix and inverted, and it refuses both as input. ptah db read reports them as btree and gin, the spellings the same server prints in pg_get_indexdef, so a description it produces replays against the database it came from.

Declares a table constraint.

Attribute Required Description
check No CHECK expression.
columns No Comma-separated local columns.
comment No Constraint comment.
condition No Constraint WHERE condition.
elements No EXCLUDE constraint elements.
foreign_column No Single referenced column for FOREIGN KEY constraints.
foreign_columns No Comma-separated referenced columns for composite FOREIGN KEY constraints.
foreign_table No Referenced table for FOREIGN KEY constraints.
include No Comma-separated INCLUDE columns for a covering UNIQUE or PRIMARY KEY constraint. Order is preserved.
key_block_size No Block-size hint for a MySQL-family PRIMARY KEY. Declare a unique index to give a UNIQUE key this option.
name No Constraint name.
nulls_distinct No Controls NULLS DISTINCT behavior. true/false. The clause is PostgreSQL’s; a target whose capability set does not carry unique_nulls_distinct_clause refuses it at render time rather than dropping it, in either spelling.
on_delete No Foreign key ON DELETE action.
on_update No Foreign key ON UPDATE action.
table No Explicit target table.
type No Constraint type: CHECK, UNIQUE, PRIMARY KEY, FOREIGN KEY, or EXCLUDE.
using No Access method: an EXCLUDE constraint’s index method, or a PRIMARY KEY’s on MySQL and MariaDB (BTREE or HASH). Refused on UNIQUE, CHECK and FOREIGN KEY.

include belongs to a UNIQUE or a PRIMARY KEY constraint, and the two accept it on different targets:

type Targets that accept include
UNIQUE PostgreSQL, YugabyteDB, CockroachDB
PRIMARY KEY PostgreSQL, YugabyteDB

Every other target refuses the constraint before emitting SQL, naming the constraint and the targets that accept it. CockroachDB stores a covering UNIQUE constraint as a unique index with a STORING clause, which is its spelling of the same payload; it has no covering primary key, and the Spanner PostgreSQL dialect answers <INCLUDE> clause is not supported in constraints for either kind, so both are refused there.

include on a CHECK, FOREIGN KEY, or EXCLUDE constraint is refused on every target: those constraints carry no payload to render. Omit include when there are none; a present list with an empty element is refused rather than trimmed.

The targets for a constraint are not the targets for an index. The Spanner PostgreSQL dialect takes include on an index and refuses it on a constraint, and CockroachDB takes it on an index and on a UNIQUE constraint but not on a primary key. See //ptah:schema:index for the index list.

A PRIMARY KEY constraint’s name is the name the key is built with on PostgreSQL; without one the server names it <table>_pkey. MySQL and MariaDB call every primary key PRIMARY whatever it is declared as, so there the comparison matches the key without its name.

Declares a YDB column family: columns a row table stores together, with a storage pool, a compression and a cache mode of their own. It belongs to the table of the struct it is on, or to the one table names, which the same file declares. Every other dialect refuses a table that declares one. See column families.

Attribute Required Description
name Yes Family name. default sets the family that holds the key and every unlisted column.
table No Table the family belongs to, when not the struct’s own.
data No Kind of storage pool the family is kept in, such as ssd. Omitted, the table keeps the pool it holds.
compression No off or lz4. Omitted, the table keeps the compression it holds.
cache_mode No regular or in_memory, either of which needs column_family_cache_mode. Omitted, the table keeps the cache mode it holds.
fields No Columns the family holds, never a key column. The default family lists none.

Declares a YDB changefeed: a stream of a table’s changes, which YDB keeps in a topic at <table>/<name>. It belongs to the table of the struct it is on, or to the one table names, which the same file declares. Every other dialect refuses a table that declares one. See changefeeds.

Attribute Required Description
name Yes Changefeed name, unique among the table’s changefeeds and indexes.
mode Yes What a record carries: KEYS_ONLY, UPDATES, NEW_IMAGE, OLD_IMAGE or NEW_AND_OLD_IMAGES.
format Yes How a record is written: JSON or DEBEZIUM_JSON.
table No Table the changefeed belongs to, when not the struct’s own.
virtual_timestamps No Each record carries its change’s virtual timestamp. true/false; bare form allowed.
resolved_timestamps No Interval of the barrier records, an ISO 8601 duration of whole seconds.
retention_period No How long the topic keeps a record; 24 hours when omitted.
initial_scan No The stream opens with a record for every row. true/false; bare form allowed.
user_sids No Each record names its change’s user. Needs changefeed_user_sids.
schema_changes No The stream carries schema change records. Needs changefeed_schema_changes.
topic_min_active_partitions No Partitions the topic starts with, at least 1.
topic_auto_partitioning No The topic gains partitions as writes grow. Needs changefeed_topic_auto_partitioning.

Declares a consumer of a changefeed’s topic: a named reader that keeps its own position in the stream.

Attribute Required Description
name Yes Consumer name, unique within the topic.
changefeed Yes Changefeed whose topic the consumer reads.
table No Table of the changefeed, when not the struct’s own.
important No The topic keeps what this consumer has not read past the retention. true/false; bare form allowed.
read_from No RFC 3339 time a partition this consumer has not read is read from, in whole seconds.
supported_codecs No Codecs the consumer reads: raw, gzip, lzop, zstd, custom.
availability_period No How long the topic keeps what this consumer has not read past the retention. Needs topic_consumer_availability_period.

Declares a reusable enum type.

Attribute Required Description
comment No Enum type comment.
name Yes Enum type name.
values Yes Comma-separated enum values.

Declares a PostgreSQL domain type.

Attribute Required Description
check No CHECK constraint expression (uses VALUE).
comment No Domain comment.
default No Literal DEFAULT value.
default_expr No DEFAULT expression.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
name Yes Domain name.
not_null No Marks the domain NOT NULL. true/false.
schema No Target schema/namespace.
type Yes Underlying base data type.

Declares a PostgreSQL composite type.

Attribute Required Description
comment No Composite type comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
fields Yes Comma-separated name:type field list.
name Yes Composite type name.
schema No Target schema/namespace.

Declares a PostgreSQL range type.

Attribute Required Description
canonical No Canonicalization function.
collation No Collation for the subtype.
comment No Range type comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
name Yes Range type name.
schema No Target schema/namespace.
subtype Yes Element subtype the range is built over.
subtype_diff No Subtype difference function.
subtype_opclass No Operator class for the subtype.

Omitting an attribute and writing it empty are different instructions. Omission says nothing about the attribute, so a range in an existing database that carries a SUBTYPE_DIFF keeps it when the declaration does not mention one — which is what makes pointing Ptah at a database somebody else built safe. Writing the attribute empty says the range has none, and is the spelling that removes one:

//ptah:schema:range name="measurement" subtype="int8" subtype_diff=""
type Measurement struct{}

This applies to canonical, collation, subtype_diff and subtype_opclass. PostgreSQL has no ALTER TYPE … AS RANGE, so removing one is planned as a drop and a create: the drop is non-CASCADE and fails while the type is in use. That is the reason omission cannot mean removal.

Declares a database schema or namespace.

Attribute Required Description
comment No Schema comment.
name Yes Schema name.

Declares a PostgreSQL extension.

Attribute Required Description
comment No Extension comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
if_not_exists No Adds IF NOT EXISTS where supported. true/false.
name No Extension name.
schema No PostgreSQL installation schema. Empty means the target’s default schema.
version No Extension version.

Declares that this description does not describe an object family, or one named object in it, so its absence is not a removal.

Attribute Required Description
kind Yes Object family, such as extension, schema, role or sequence.
name No One object of that family. Omitted, the whole family is declined.
//ptah:schema:notdescribed kind="extension" name="pg_trgm"
//ptah:schema:notdescribed kind="role"
type _ struct{}

An object a schema declines is neither created nor dropped: the comparison treats the silence as a limit of the description rather than as a statement that the database should not have the object. An extension a bootstrap step installs is the usual case — declaring it instead hands Ptah the object, and that includes removing it.

kind comes from a closed list, and one this build does not know is refused rather than ignored: a directive nothing understands reads as no directive at all, and the absence it was protecting becomes a removal.

A serialized description says the same thing in its own grammar, as a directive in the leading comment header:

// ptah:not-described extension "pg_trgm"

The two produce one plan. Which one fits depends on where the description lives, not on what it means.

Declares a standalone PostgreSQL sequence.

Attribute Required Description
as No Underlying integer type, such as bigint.
cache No CACHE size.
comment No Sequence comment.
cycle No Enables CYCLE wrap-around. true/false.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
if_not_exists No Adds IF NOT EXISTS where supported. true/false.
increment No INCREMENT BY value; must be non-zero.
maxvalue No MAXVALUE bound.
minvalue No MINVALUE bound.
name Yes Sequence name.
owned_by No Owning table.column association (OWNED BY).
schema No Target schema/namespace.
start No START WITH value.

Declares a database function.

Attribute Required Description
body No Function body SQL.
comment No Function comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
language No Function language.
leakproof No Marks the function LEAKPROOF. true/false.
name No Function name.
parallel No Parallel level: SAFE, RESTRICTED or UNSAFE.
params No Function parameter list.
returns No Return type.
schema No Target schema/namespace.
security No Security mode, such as DEFINER.
settings No Routine configuration settings, name=value, separated by ;.
strict No Marks the function STRICT. true/false.
volatility No Volatility class.

strict makes the function return NULL without running its body when any argument is NULL. PostgreSQL also spells it RETURNS NULL ON NULL INPUT, and false is CALLED ON NULL INPUT, the server’s default, which is left out of the rendered statement.

leakproof and parallel decide how the query planner may use the routine, and the first decides it across a security boundary: a filter using a leakproof function may be pushed past a row-level-security predicate, so a function marked leakproof by mistake can reveal rows a policy withholds. PostgreSQL reserves the attribute for a superuser for that reason. UNSAFE is the server’s default and is left out of the rendered statement, so a routine that states no level renders as it did; a level that is none of the three is refused at parse time rather than read as a default, because neither direction of that guess is safe.

settings pins the routine’s own configuration, and search_path is the entry that matters: a SECURITY DEFINER routine without one resolves unqualified names through whatever the caller had set.

schema places the routine in a named schema, and is also accepted on view and matview. A function, view or materialized view carries its schema inside its NAME rather than in a field of its own — schema="app" name="fn" becomes app.fn — which is the same spelling the atlas.hcl frontend produces for these three kinds, and what the renderers split back apart when they emit DDL. A declaration naming no schema keeps its name exactly as written.

Declares a stored procedure: a routine that returns nothing and is invoked with CALL.

A procedure is the same catalog object as a function with one property removed, so it takes the same attributes minus returns. Declaring returns is refused rather than ignored: CREATE PROCEDURE ... RETURNS does not parse on either engine that has procedures, and accepting the attribute would mean a declaration that says one thing while the database holds another.

//ptah:schema:procedure name="archive_tenant" params="tenant_id integer" language="sql" body="DELETE FROM tenants WHERE id = tenant_id"
type ArchiveTenant struct{}
Attribute Required Description
body No Procedure body SQL.
comment No Procedure comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
language No Procedure language.
name No Procedure name.
params No Procedure parameter list.
schema No Target schema/namespace.
security No Security mode, such as DEFINER.
volatility No Volatility class.

Declares a database trigger.

Attribute Required Description
body Yes Trigger body SQL.
comment No Trigger comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
event Yes Trigger event, such as INSERT or UPDATE.
for No Trigger granularity; defaults to ROW.
name Yes Trigger name.
new_table No Transition table holding the rows after the change (REFERENCING NEW TABLE). PostgreSQL family.
old_table No Transition table holding the rows before the change (REFERENCING OLD TABLE). PostgreSQL family.
table Yes Target table.
timing Yes Trigger timing, such as BEFORE or AFTER.
when No WHEN condition; the trigger fires only where it is true. PostgreSQL family.

Declares a database view.

Attribute Required Description
body Yes View SELECT body.
depends_on No Comma-separated objects this view must be created after.
comment No View comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
name Yes View name.
schema No Target schema/namespace.
with_check No Controls WITH CHECK OPTION where supported. true/false.

Declares a materialized view.

Attribute Required Description
body Yes Materialized view SELECT body.
depends_on No Comma-separated objects this view must be created after.
comment No Materialized view comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
name Yes Materialized view name.
schema No Target schema/namespace.
refresh No ClickHouse refresh schedule, as ClickHouse spells it. See below.

refresh carries a ClickHouse scheduled materialized view’s schedule, in the engine’s own words: every 1 hour, after 30 minute, every 1 day offset 2 hour randomize for 30 minute append. Omitting it leaves an ordinary materialized view, maintained by inserts into its source.

The server rewrites intervals — every 60 minute is stored as EVERY 1 HOUR — so the declaration is normalized to the stored spelling when it is parsed. Any spelling of the same schedule converges. A schedule the server would refuse is refused here instead: an interval mixing calendar units with clock units, a zero interval, or offset on an after schedule.

It is a ClickHouse property and only that. It is not the retired cross-dialect strategy below, which described an operation rather than state:

refresh_strategy is not an attribute. Ptah does not refresh materialized views: one is populated when it is created, a changed body is reconciled as a drop and a create that populates it again, and it goes stale only when its source data changes, which schema reconciliation cannot observe. Refresh from your own scheduler.

An annotation that still declares it is refused while the source file is parsed, with that reason, on every dialect – including the bare form with no value, and including a view scoped away from the current target by dialects. The name stays recognized so the refusal explains itself instead of reading as a misspelling.

Declares a YDB topic: a persistent message queue at a path of the database. A setting left out stands for the value YDB gives a new topic. Every target but YDB refuses a topic. See Topics for what a plan does with one.

Attribute Required Description
auto_partitioning_down_utilization_percent No Share of a partition’s write speed below which partitions merge, 1 to 100.
auto_partitioning_stabilization_window No How long a load lasts before the partition count follows it, an ISO 8601 duration.
auto_partitioning_strategy No disabled, scale_up, scale_up_and_down or paused.
auto_partitioning_up_utilization_percent No Share of a partition’s write speed above which it splits, 1 to 100.
max_active_partitions No Most partitions auto-partitioning splits the topic into; needs a strategy other than disabled.
min_active_partitions No Partitions writers write to. The count only grows.
name Yes Topic name, the last segment of its path.
partition_write_burst_bytes No Burst a partition takes above its quota, in bytes; the write speed when omitted.
partition_write_speed_bytes_per_second No Write quota of one partition, in bytes per second.
retention_period No How long the topic keeps a message, an ISO 8601 duration; 24 hours when omitted.
schema No Directory that holds the topic, relative to the database root.
supported_codecs No Codecs a writer may use: raw, gzip, lzop, zstd, custom.

Declares a consumer of a topic declared in the same file: a named reader with a read position of its own.

Attribute Required Description
availability_period No How long the topic keeps a message this consumer has not read past the retention period, an ISO 8601 duration. YDB 25.4 and later.
important No The topic keeps a message this consumer has not read past the retention period. true/false.
name Yes Consumer name, unique within the topic.
read_from No RFC 3339 time a partition this consumer has not read is read from.
schema No Directory of the topic, when it has one.
supported_codecs No Codecs the consumer reads: raw, gzip, lzop, zstd, custom.
topic Yes Topic the consumer reads.

Declares a YDB resource pool, which limits the queries that run in it. A pool belongs to the whole database, and a setting left out has no limit. YDB keeps pools behind the EnableResourcePools flag; see resource pools.

Attribute Required Description
name Yes Pool name; default changes the pool YDB creates.
concurrent_query_limit No Most queries that run at once, a whole number.
queue_size No Most queries that wait; needs concurrent_query_limit or database_load_cpu_threshold.
database_load_cpu_threshold No Database CPU load in percent above which new queries wait.
query_memory_limit_percent_per_node No Share of a node’s memory one query may take, in percent.
query_cpu_limit_percent_per_node No Share of a node’s CPU one query may take, in percent.
total_cpu_limit_percent_per_node No Share of a node’s CPU the pool’s queries take together.
resource_weight No The pool’s share of the CPU when pools compete, in percent.

Declares a YDB resource pool classifier, which sends a user’s or a group’s queries to a pool. Of the classifiers that match a query, the lowest rank decides.

Attribute Required Description
name Yes Classifier name.
resource_pool Yes Pool the queries go to: a declared pool or default.
member_name No User or group whose queries it matches; every query when omitted.
rank Yes Order among the classifiers, from 0; unique.

Declares a YDB coordination node, which holds an application’s semaphores and rate limiter resources. A setting left out takes YDB’s default.

Attribute Required Description
attach_consistency_mode No strict or relaxed. YDB’s default is strict.
name Yes Node name.
rate_limiter_counters_mode No aggregated or detailed. YDB’s default is aggregated.
read_consistency_mode No strict or relaxed. YDB’s default is relaxed.
schema No Directory holding the node, relative to the database root.
self_check_period No How often the node checks it is alive, as an ISO 8601 duration from PT0.5S to PT10S. YDB’s default is PT1S.
session_grace_period No How long a session keeps its semaphores while the node changes its leader, from the self-check period plus one second to PT30S. YDB’s default is PT10S.
//ptah:schema:coordinationnode name="locks" schema="app" self_check_period="PT2S"
type Locks struct{}

A coordination node is YDB’s own object, and every other target refuses the declaration. ptah_locks at the database root is Ptah’s lock node and is refused. Coordination nodes says how Ptah applies one.

Declares a YDB async replication: tables of another database copied into read-only replica tables YDB creates and keeps current. The tables it copies are declared with //ptah:schema:async_replication:item in the same file, and the replica tables are not declared as tables. A credential names a secret and never holds a value. Every target but YDB refuses a replication. See Async replications and transfers for what a plan does with one.

Attribute Required Description
commit_interval No How often a global replication commits, an ISO 8601 duration; ten seconds when omitted.
connection_string Yes The other database: grpc://host:port/?database=/path or grpcs://....
consistency_level No Consistency of the replica: row or global; row when omitted.
name Yes Replication name, the last segment of its path.
password_secret_name No Object secret holding the user’s password.
password_secret_path No Path of a secret holding the user’s password, relative to the database root (YDB 25.4 and later).
schema No Directory that holds it, relative to the database root.
token_secret_name No Object secret holding an access token.
token_secret_path No Path of a secret holding an access token, relative to the database root (YDB 25.4 and later).
user No User a password secret signs in as.

Declares one table, or directory of tables, an async replication copies, and where its replica is created. The replication is declared in the same file.

Attribute Required Description
replication Yes Replication the item belongs to.
schema No Directory of the replication, when it has one.
source Yes Path in the source database, relative to its root or absolute.
target Yes Path of the replica in this database, relative to its root.

Declares a YDB transfer: messages of a topic turned into rows of a table through a YQL lambda. The table is declared in the same schema, and so is the topic, as a topic or as a changefeed, unless the transfer reads another database. Every target but YDB refuses a transfer.

Attribute Required Description
batch_size_bytes No Bytes the transfer gathers before a write; 8 MiB when omitted.
connection_string No The other database, for a topic outside this one: grpc://host:port/?database=/path or grpcs://....
consumer No Existing topic consumer the transfer reads through; YDB creates one when omitted.
flush_interval No Longest wait before a write, an ISO 8601 duration of whole seconds; a minute when omitted.
name Yes Transfer name, the last segment of its path.
password_secret_name No Object secret holding the user’s password.
password_secret_path No Path of a secret holding the user’s password, relative to the database root (YDB 25.4 and later).
schema No Directory that holds it, relative to the database root.
source Yes Topic the transfer reads, relative to the database root; for a topic of another database, relative to its root or absolute.
target Yes Table the transfer writes, relative to the database root.
token_secret_name No Object secret holding an access token.
token_secret_path No Path of a secret holding an access token, relative to the database root (YDB 25.4 and later).
user No User a password secret signs in as.
using Yes The YQL lambda, written inline: ($msg) -> { ... }.

Declares a database role.

Attribute Required Description
comment No Role comment.
create_db No Alias for createdb. true/false.
create_role No Alias for createrole. true/false.
createdb No Allows database creation. true/false.
createrole No Allows role creation. true/false.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
group No Declares a group, which never logs in and has members. YDB only. true/false.
inherit No Controls role inheritance; defaults to true. true/false.
login No Creates the role with LOGIN. true/false.
member_of No Comma-separated groups the role is a member of. YDB only.
name No Role name.
password No Role password.
replication No Allows replication. true/false.
superuser No Creates the role as SUPERUSER. true/false.

Most of these attributes are PostgreSQL-family notions. A ClickHouse role carries none of them — system.roles is (name, id, storage) — so declaring password, login, superuser, createdb, createrole, or replication for a ClickHouse target is refused rather than dropped, and comment is emitted as a leading SQL comment because the engine cannot store one. See ClickHouse roles and grants. A MySQL or MariaDB role carries none of them either, and the same attributes are refused there. inherit="false" is refused too: a role on those engines always passes on the privileges of the roles granted to it. See MySQL and MariaDB.

On YDB a role is a user, and group="true" declares a group instead. A user takes login and password; a group takes neither. member_of names the groups a user or a group joins, a group of the cluster’s own such as DATA-READERS included. The other attributes are refused, and so is a name holding anything but lower-case letters and digits. See YDB users, groups and permissions.

Declares database grants.

Attribute Required Description
columns No Comma-separated columns of on_table the privileges are limited to, such as state,decided_at. PostgreSQL only.
comment No Grant comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
grant_option No Alias for with_option. true/false.
on_database No Targets the database itself. YDB only. true/false.
on_function No Target function with its argument types, such as purge(uuid). PostgreSQL only.
on_procedure No Target procedure with its argument types, such as archive(uuid). PostgreSQL only.
on_schema No Target schema.
on_sequence No Target sequence.
on_table No Target table.
privilege No Privilege or comma-separated privileges.
privileges No Alias for privilege.
role No Target role.
with_option No Adds WITH GRANT OPTION where supported. true/false.

A function or procedure is named with its argument types in parentheses, because PostgreSQL tells overloads apart by them. A name without them is refused while the file is parsed.

On ClickHouse a grant names one scope, and on_table must be qualified as database.table because rendering is offline and has no current database to resolve a bare name against. on_sequence, wildcard scopes and column-scoped privileges such as SELECT(id) are refused, as is declaring the same privilege on both db.* and db.t — the server would absorb the narrower grant, so the pair could never converge. role must name a role the same schema declares, because ClickHouse resolves a grantee across users and roles and a user of that name would win. Privilege names the server rewrites on the way in — ALL, CREATE, DROP, SYSTEM and the rest — are refused too, because they never read back as written. See ClickHouse roles and grants.

On YDB a grant is on the database (on_database="true"), a directory (on_schema) or a table (on_table). A privilege is a YDB permission, by its name, such as ydb.granular.select_row, or as GRANT spells it, such as SELECT ROW. with_option is refused: YDB records the grant option as a permission of its own, so grant ydb.access.grant instead.

Declares privileges a role must not hold, including ones it holds without a grant: the EXECUTE every new function gives PUBLIC, or what ALTER DEFAULT PRIVILEGES gives a role on a new table.

Attribute Required Description
columns No Comma-separated columns of on_table the privileges are limited to, such as state,decided_at. PostgreSQL only.
comment No Comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
on_database No Targets the database itself. YDB only. true/false.
on_function No Target function with its argument types, such as purge(uuid). PostgreSQL only.
on_procedure No Target procedure with its argument types, such as archive(uuid). PostgreSQL only.
on_schema No Target schema.
on_sequence No Target sequence.
on_table No Target table.
privilege No Privilege or comma-separated privileges.
privileges No Alias for privilege.
role Yes Role the privileges are taken from; PUBLIC names every role.
//ptah:schema:revoke role="PUBLIC" privilege="EXECUTE" on_function="purge_workspace(uuid)"
//ptah:schema:grant role="app" privilege="EXECUTE" on_function="purge_workspace(uuid)"
type AccessControl struct{}

Ptah plans the REVOKE whenever the database holds the privilege, and in the same plan that creates the object, since the privilege arrives with it. The same privilege declared both granted and revoked to one role on one object is refused: annotations have no order, so neither declaration could win.

Declares a PostgreSQL default privilege: what a grantee receives on objects a named role creates, in a named schema or in every schema. It is ALTER DEFAULT PRIVILEGES, and no other engine has the statement.

Attribute Required Description
comment No Default privilege comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
for_role Yes Role whose newly created objects the privileges apply to.
grantable No The subset of privileges carrying WITH GRANT OPTION.
grantee Yes Role receiving the privileges; PUBLIC names every role.
object_type Yes TABLES, SEQUENCES, FUNCTIONS or TYPES, and without schema also SCHEMAS or LARGE OBJECTS.
privileges No Comma-separated privileges, such as SELECT,INSERT. Required unless revoked is set.
revoked No Comma-separated privileges the grantee must not hold by default, such as INSERT,UPDATE; ALL names every privilege of the object type. A name also in privileges is refused.
schema No Schema the default applies in. Left out, the directive is the global default, which applies in every schema.

The directive needs a holder struct. Written at file level, below the closing brace of the declaration above it, it contributes no object and reports nothing, the same trap index annotations carry. Give it a struct of its own:

//ptah:schema:defaultprivilege for_role="app_owner" schema="app" object_type="TABLES" grantee="app_reader" privileges="SELECT,INSERT" grantable="INSERT"
type AccessControl struct{}

The object’s identity is for_role, schema, object_type and grantee together. Two declarations differing only in for_role are two objects: PostgreSQL enforces the grantor and refuses the statement from a role that is not a member of it.

grantable names a subset of privileges, not a second list. Grantability is recorded per privilege, so one identity granted SELECT plainly and INSERT WITH GRANT OPTION reads back from the catalog as two rows. A name in grantable that is absent from privileges is refused while the file is parsed, rather than kept as a declaration nothing can render.

object_type accepts those keywords and nothing else; any other value is refused at parse time. Without schema the directive declares the global default, ALTER DEFAULT PRIVILEGES without IN SCHEMA, which applies in every schema and starts from the built-in default:

//ptah:schema:defaultprivilege for_role="app_owner" object_type="FUNCTIONS" grantee="PUBLIC" revoked="EXECUTE"
type NoPublicExecute struct{}

SCHEMAS and LARGE OBJECTS exist only in that form, so either one with a schema is refused. See global default privileges for how the comparison treats the built-in default.

Enables row-level security on a table.

Attribute Required Description
comment No RLS enablement comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
force No Apply the table’s policies to its owner too. true/false.
table No Target table.

Enabling row-level security leaves the table’s owner exempt from every policy on it. force="true" adds ALTER TABLE ... FORCE ROW LEVEL SECURITY, which binds the owner as well. The two are separate flags on the relation, so the render emits two statements.

Declares a row-level security policy.

Attribute Required Description
as No PERMISSIVE (the default) or RESTRICTIVE.
comment No Policy comment.
dialects No Comma-separated target dialects this object belongs to; omitted means every dialect. See Scoping an object to dialects.
for No Policy command, such as ALL or SELECT.
name No Policy name.
table No Target table.
to No Comma-separated roles; omitted means PUBLIC on PostgreSQL.
using No USING expression.
with_check No WITH CHECK expression.

A row passes when at least one permissive policy admits it and every restrictive policy admits it, so the two combine differently and neither substitutes for the other. PERMISSIVE is the server’s default and is left out of the rendered statement. Any value other than the two is refused at parse time rather than read as the default: permissive is the weaker of the two, so a misspelled RESTRICTIVE folded into it would grant the access the policy was written to withhold.

Declares a YDB secret: a scheme object whose value the server keeps and never returns, which an external data source reads a password or a key from. The declaration names the environment variable that holds the value, and never the value itself. Every other dialect refuses a secret. See secrets.

Attribute Required Description
name Yes Secret name, the last segment of its path.
schema No Directory that holds the secret, relative to the database root.
value_env Yes Environment variable that holds the value. Its name starts with PTAH_SECRET_.

A value attribute is refused, and the error names the attribute and not what it held.

Declares a YDB external data source: another system YDB reads from, such as an object storage bucket or a PostgreSQL database, and how YDB authenticates to it. A credential is named by the secret that holds it, never written. Every other dialect refuses one. See external data sources.

Attribute Required Description
name Yes Data source name, the last segment of its path.
schema No Directory that holds the data source, relative to the database root.
source_type Yes SOURCE_TYPE, such as ObjectStorage, PostgreSQL or ClickHouse.
location No LOCATION: the bucket’s address or the server’s host and port.
auth_method Yes AUTH_METHOD, such as NONE, BASIC or SERVICE_ACCOUNT.
options No Every other option, NAME=value separated by ;. \; writes a semicolon into a value.

A PostgreSQL source whose password the secret ext/pg_password holds:

//ptah:schema:externaldatasource name="warehouse" schema="ext" source_type="PostgreSQL" location="pg:5432" auth_method="BASIC" options="DATABASE_NAME=app;LOGIN=reader;PASSWORD_SECRET_PATH=ext/pg_password"
type Warehouse struct{}

Declares a YDB external table: columns over files that an external data source of type ObjectStorage holds. YDB stores no row of it. Every other dialect refuses one.

Attribute Required Description
name Yes External table name, the last segment of its path.
schema No Directory that holds the external table, relative to the database root.
data_source Yes Path of the data source the table reads, relative to the database root.
location Yes LOCATION: the files’ path under the data source.
columns Yes Columns, name Type [NOT NULL] separated by commas, such as id Int64 NOT NULL, amount Decimal(22,9). A default, a key or a column family is refused.
options No Every other option, such as FORMAT and COMPRESSION, NAME=value separated by ;.

Declares external reference/seed row data for a table.

Attribute Required Description
file Yes Path to the YAML row-data file, relative to the Go source file.
key Yes Comma-separated key column(s) forming each row’s identity.
schema No Database schema the table belongs to.
table Yes Target table the rows belong to.

The repository ships ptah-ls, a language server for //ptah annotations in Go source. It speaks the Language Server Protocol over stdio and provides hover documentation, attribute completion, and diagnostics backed by the same directive metadata as this page. Build it from the repository root:

Terminal window
go build -o bin/ptah-ls ./cmd/ptah-ls

A Visual Studio Code extension that starts ptah-ls for Go files lives in editors/vscode; its ptah.languageServer.path setting points at the built binary. Any other LSP-capable editor can run ptah-ls directly as a stdio language server.

Serve mode takes no arguments. ptah-ls version and ptah-ls --version print build metadata and exit; anything else on the command line is a usage error and exits 2. Earlier releases silently discarded arguments and started serving anyway, so an editor configuration that passes a document or workspace path now fails loudly instead of appearing to work.