README
¶
Ptah YDB provider
This repository owns the executable YDB provider for Ptah #4204. It is under development. Inspection covers row tables and their indexes and changefeeds, column tables, topics, views, coordination nodes, external data sources, external tables, and scheme secrets. Native SQL execution and session-bound data transactions are implemented, together with provider-owned migration history, event journals, tags, and coordination locks. Offline YAML and Go annotation decoding is available for coordination-node, topic, external data source external table and secret declarations, and YAML and Go annotation decoding for the partitioning, TTL, column families and changefeeds of row tables, including the columns of each family. Offline comparison, planning, and rendering produce native coordination, topic, changefeed, secret and external object operations (see Secrets, Topics, Changefeeds and External data sources and external tables). Native row-table creation and alteration of column families, columns, the TTL and the automatic partitioning settings are available through captured SDK planning requests. Execution verifies captured preconditions and reads back the result. Offline migration lint splits YQL migrations and checks YD rules; see Migration lint.
The executable opens native YDB connections and reads row-table schemas through the public Ptah SDK. It supports anonymous, URL user/password, and URL token authentication. Session receipts contain the selected database and measured server version. The implementation does not import the Ptah application module.
Install it with ptah providers install ydb. The documentation says what YDB
does in Ptah, from installing and selecting the provider to the objects it
declares and how it runs a migration. It is published on
docs.ptah.run, and its pages
live in docs/.
The provider ID is github.com/stokaro/ptah-ydb-plugin. Model kinds use the github.com/stokaro/ptah-ydb-plugin/ namespace. This changes
the experimental model identities; pre-v1, no compatibility with the earlier
unpublished names is owed. Older envelopes and plans are refused.
Releases
A tag that starts with v runs .github/workflows/release.yml, which
publishes a GitHub release through GoReleaser (.goreleaser.yaml). Each
release carries one archive per platform, two JSON documents and a
checksums.txt file:
ptah-plugin-ydb_<version>_<os>_<arch>.tar.gz (.zip on Windows)
ptah-provider.json
release-lines.json
checksums.txt
<version> is the tag without the leading v. The platforms are linux,
darwin and windows on amd64 and arm64. Every archive holds the
ptah-plugin-ydb binary (ptah-plugin-ydb.exe on Windows), README.md and
LICENSE. The binaries are built with CGO_ENABLED=0 and -trimpath.
ptah-provider.json names the provider, the release version, the oldest
provider contract a host must implement to start it (min_contract, a major and
a minor version), its engines and the archive and executable for each platform.
A host starts the release when its contract has the same major version and a
minor version at least min_contract.minor. The document states the minimum the
provider declares in its Describe reply, so the two cannot disagree. This
provider requires contract 25.7. It negotiates database.context and
values.context to resolve dependency paths, and planning.lifecycle to order
native replacements around common table operations. It offers
planning.statement-style (contract 25.8), which ptah-compat's migrate diff
needs, because it plans in the exact statement style: no comment that only
describes a statement, no guard the plan does not need and no CASCADE. The
operations the provider plans carry none of these in either style, so an exact
plan is the plan the annotated style gets. ptah providers install
reads the document to choose the newest release the host can start, without
downloading an archive. release-lines.json lists the release lines the provider declares.
checksums.txt lists the SHA-256 digest of every archive and of both documents
in sha256sum format (<hex digest> <file name>). A host checks the archive
it downloads against the digest listed there.
Pull requests that touch the release inputs run goreleaser check and a
snapshot build, then verify the snapshot's checksums.txt and that
ptah-provider.json names exactly the archives GoReleaser wrote. To try it
locally:
GOWORK=off goreleaser release --snapshot --clean
(cd dist && shasum -a 256 -c checksums.txt)
Build and test
The module pins provider-sdk v0.35.0, which speaks provider contract 25.37.
The provider still requires 25.7 of its host and offers each later feature to a
host that lists it. Builds require neither the Ptah application source nor a local SDK
checkout.
Database context is resolved without connecting or reading credentials. A dev realm carries both its selected root and the authored database root. Context is captured separately from object identity and survives normalization, conversion, and saved operations. Absolute secret and external data source references require this context before selection or planning. Unresolved references retain an explicit pending state; they cannot report an empty or partial dependency list.
Run the unit suite and build the executable:
GOWORK=off go test -race ./...
GOWORK=off CGO_ENABLED=0 go build -o bin/ptah-plugin-ydb ./cmd/ptah-plugin-ydb
The provider is held to Ptah's lint contract: .golangci.yml carries the
host's linters and settings, and the Makefile runs the host's other lint tools.
Lint as CI does, with golangci-lint v2.15.0 on the PATH; qtlint, nolintguard
and teststyle are pinned as tools in go.mod:
make lint # qtlint, nolintguard, golangci-lint and the test style baseline
make lint-fix # the automatic fixes of qtlint and golangci-lint, then make lint
Every linter runs over both build contours, the default one and the
integration tag. scripts/check-test-style.sh holds the tests to the
declarative rule: no if, switch or goto in a test function. The functions
that broke it when the rule was adopted are listed in
.teststyle-baseline.json; the list may shrink and must not grow.
CI compares .golangci.yml with the host's: every linter and formatter the
host enables is enabled, every setting of one is the host's, and the
exclusions that name no path are the host's. The gci prefix, the rowserrcheck
packages, the depguard messages and the path exclusions name this module and
are not compared. To run the comparison against a checkout of Ptah:
GOWORK=off go run ./internal/cmd/lintcontract -host ../ptah/.golangci.yml
The executable requires the SDK's authenticated local subprocess bootstrap.
Run it through a Ptah SDK client rather than invoking it as a command-line tool.
The committed module has no replace directive or workspace file.
scripts/check-module-boundaries.sh rejects local replacements and host-module
dependencies.
Live checks require an isolated test database. Schema tests create uniquely named directories, tables, and topics and remove them afterward. Session tests only read server metadata.
PTAH_YDB_TEST_URL='ydb://localhost:2136/local?go_balancer=disable' \
GOWORK=off go test -race -tags integration ./integration -count=1 -v
A test whose dev database must be on a server other than the target's reads a
second isolated server from PTAH_YDB_OTHER_TEST_URL and skips without it. CI
starts the second server beside the first.
The test servers enable enable_vector_index and
enable_external_data_sources; the 26.2 fixture also enables
enable_add_unique_index. A full-text index is refused on 25.1, and the
full-text tests that need one skip there; the 25.1 run checks the refusal
instead. One full-text test turns enable_fulltext_index off for the whole
26.2 cluster through its dynamic configuration and drops the configuration
when it ends.
Ptah's provider conformance suite, from the SDK's package conformance, runs
in integration/conformance_e2e_test.go. Its offline part runs in the unit job
and needs no database: the handshake, the description and its negotiation
under every host profile, every RPC, offline calls, and the model identities
of the last release in integration/testdata. Its live part runs with the
other live tests: sessions, reads, locks across two processes, transactions, a
table created and dropped, a refused statement, and a login whose password is
the suite's canary. A release records its model identities there with the
suite's command:
GOWORK=off go run github.com/stokaro/ptah/provider-sdk/cmd/ptah-provider-conformance \
models -executable bin/ptah-plugin-ydb -provider-id github.com/stokaro/ptah-ydb-plugin
CI runs this process test against YDB 25.1.4.7 and 26.2.1.14. It also rejects host-module imports and local module replacements. These checks cover sessions, SQL execution, commit/rollback, range conflicts, durable migration history, atomic revision comparisons, shared data/history/tag transactions, durable event journals, immutable paged metadata snapshots, process lock exclusion, confirmed release, and write refusal after lease loss, native row-table observations, offline normalization, index implementation tables, changefeed consumers and retention, standalone native objects, exact-directory completeness, and refusals for incomplete reads. Coordination tests cover source decoding through planning, rendering, native execution, and inspection. They do not establish complete migration support or host CLI integration.
Native CLI development acceptance
Development checks with the experimental host adapter verified reviewed SQL
migration execution and rollback against YDB 25.1.4.7 and 26.2.1.14. The checks
also exercised status, journals, tags, and default locking through the executable.
Schema changes used --tx-mode none for migrations up and a
-- +ptah no_transaction directive in the down file. YDB does not provide
transactional schema changes.
The host and provider are built against the published SDK pin with
GOWORK=off. The live tests run twice per change and nightly. The blocking
run builds the newest published Ptah release whose provider contract this
provider can run on (scripts/resolve-host.sh); it is skipped with a notice
while no release speaks that contract. A second, non-blocking run builds Ptah's
master branch, so a host change that breaks this provider shows up early. A
third run, on each image, builds a host a run names: a branch, tag or commit of
stokaro/ptah, given as the host_ref input of a manual run or as the first line
of .github/host-ref in a pull request whose tests need a host change master
does not have yet (scripts/live-lines.sh). It blocks, because the run asked
for that host, and a ref that names no commit fails it. Each run also builds
the host's ptah-compat from the same checkout and names it in
PTAH_HOST_COMPAT_TEST_BIN. The TestHostCompat tests run it with
PTAH_PROVIDER_CONFIG naming this provider's manifest, which is how
ptah-compat takes one. They skip, with the binary's answer, where ptah-compat
still builds in a YDB engine of its own, which claims ydb and refuses this
provider's registration; a host without the built-in engine
(stokaro/ptah#5298) runs them. This
repository owns the YDB end-to-end, live and parity tests; Ptah's own CI checks
the provider contract only on its in-tree fixture provider. These are source-build checks; packaged installation and
default bundling remain separate requirements.
Host schema inspect --format json exports the native desired model, including
provider-owned values and coverage. Common collections use names such as
Tables, Fields, and Indexes. This JSON is diagnostic output, not a schema
file input. Full native CLI planning remains pending.
TestHostAppliesAndAltersARowTable_HappyPath drives ptah schema apply of the
current host through this provider: it creates a row table from a Go source,
finds nothing to change on the next run, sets a TTL in place, and finds nothing
to change again. The run passes --tx-mode none, because the host refuses a
provider step that forbids a transaction under the default mode.
Connection addresses
Use ydb://host:2136/database or ydbs://host:2135/database for TLS. The
database comes from the path or from the database parameter; naming it twice
is refused when the two differ.
The accepted parameters follow the host's embedded engine:
database,tokenanduse_env_credentialschoose the database and the credential. A token, URL user/password anduse_env_credentialscannot be combined.use_env_credentialsreads theYDB_*variables of the provider process, service-account keys and instance metadata included, and fails when none is set, so an environment that would connect anonymously is refused. An address with no credential source connects anonymously.go_balancerand the olderbalancerchoose the balancer; naming both is refused, and so is a value the SDK does not know.go_default_idempotenttakes a boolean andprefetch_query_result_partsa non-negative integer. The SDK reads these three.go_query_modeandquery_modeacceptqueryonly.go_fake_txandgo_query_bindare refused by name, because a faked transaction would read outside the snapshot read and the SDK's binders rewrite a?inside a YQL literal.monitoringnames the cluster's monitoring endpoint, anhttp://orhttps://address with no user, path, query or fragment. A session reads the database's feature flags there when it opens (see "Feature flags"), and inspection reads the description of each column table there. Both reads present the connection's own credential in YDB's bareAuthorizationheader, follow no redirect and read at most 4 MiB. A credential that travels over TLS (ydbs://) is never sent to a plainhttp://endpoint: the session is refused and asked forhttps://.
Unknown or repeated parameters, conflicting database paths, and malformed
values are refused, and a refusal names the parameter and never its value. Two
refusals are stricter than the host's: a fragment, and a query the host reads
as empty because it cannot unescape it. The testdata/oracle corpus beside
internal/connection holds the host's answer for each address it covers.
Credentials travel in the session-open request and are omitted from receipts,
SDK addresses, and error diagnostics. Native errors currently report only the
failed operation, which limits diagnosis until structured redaction is added.
Feature flags
A release line runs with default feature flags, and an operator can turn a
flag on or off, so one cluster can do more or less than its line's defaults
say. When the address names a monitoring endpoint, opening a session reads
/viewer/json/feature_flags?database=<db> and folds the flags that gate a
capability the provider owns into the session's capabilities, false
included. A failed read fails the session rather than falling back to the
defaults, as in the host's embedded engine. The refusal is a reason the host
prints, and it names the right a user lacks: on 26.x the page is served only
to a user with DESCRIBE SCHEMA on the database. What the endpoint answered
stays out of the reason. Without the endpoint the session reports the
defaults of every supported line.
| Capability | Flag | Default |
|---|---|---|
tiered_ttl |
EnableTieringInColumnShard |
false on every line from 25.1 to 26.2 |
changefeed_topic_auto_partitioning |
EnableTopicAutopartitioningForCDC |
false on 25.1, true from 25.2 |
external_data_sources |
EnableExternalDataSources |
false on every line from 25.1 to 26.2 |
external_object_replace |
EnableReplaceIfExistsForExternalEntities |
false on every line from 25.1 to 26.2 |
resource_pools |
EnableResourcePools |
false on every line from 25.1 to 26.2 |
secrets |
EnableSchemaSecrets |
false through 25.3, true from 25.4 |
vector_indexes |
EnableVectorIndex |
false on 25.1, true from 25.2 |
full_text_indexes |
EnableFulltextIndex |
false through 26.1, true from 26.2 |
local_bloom_indexes |
EnableLocalBloomFilterIndex |
false through 26.1, true from 26.2 |
local_ngram_indexes |
EnableLocalBloomNgramFilterIndex |
false through 26.1, true from 26.2 |
unique_index_on_existing_table |
EnableAddUniqueIndex |
false on every line; 25.1 and 25.2 do not list the flag |
index_rename |
EnableMoveIndex |
true on every line from 25.1 to 26.2, for a host that lists planning.index-renames |
local_min_max_indexes |
EnableLocalMinMaxIndex |
false on every line from 25.1 to 26.2 |
column_family_cache_mode |
EnableTableCacheModes |
false through 25.3, true from 25.4; 25.1 and 25.2 do not list the flag |
The release line alone decides the other vector capabilities, as the host's
line presets name them: vector_index_maintained_on_write from 25.3 (an index
built on 25.1 or 25.2 does not find the rows written after its build), and
vector_bit_type from 26.1 (25.1 to 25.4 do not build an index over bit
vectors over rows: bit vector type is not supported, Unsupported vector_type: VECTOR_TYPE_BIT).
The host maps more flags than these. The provider reports only the
capabilities it plans with, because the host plans with the others itself.
index_rename is false for a host that does not list
planning.index-renames: such a host has no step to send a renamed index
with, and would refuse the plan. internal/featureflags/testdata
holds the pages the mapping is checked against, and a live test turns the
flag on and off through the server's dynamic configuration and reads the
capability each new session reports.
A plan refused for want of a capability a flag decides names the flag, after
the host's words for the capability: ...; YDB keeps it behind feature flag <Flag>: turn the flag on, and name the cluster's monitoring endpoint in the URL (monitoring=http://host:8765) so Ptah reads the flags before it plans. A
session without the endpoint takes its release line's default, so the flag
alone does not change the answer.
Session close releases both the SQL pool and its parent native driver, even when the RPC context is canceled. Cleanup results are retained for repeated closes. A process accepts at most 1,024 lifetime session handles; start a new provider process when that bound is reached.
Dev realms
SQL cannot create a YDB database, so a dev or scratch database on a server the
run does not own is a directory. dev_realm=<name> on an address makes the
provider treat <database>/ptah_dev/<name> as the whole database of the
session. A name is 1 to 64 lowercase letters, digits and underscores, and the
address must name the database that holds the realm. Opening such a session
creates the directory when it does not exist.
The engine declares database.dev-realms. A host that lists the feature sends
a dev database request with the operator's address instead of a spec, and the
provider creates a realm of the run's own in the database the address names, as
the host's embedded engine does: a name of 16 random hexadecimal digits, and an
address that keeps the operator's scheme, host and credentials and names the
realm in place of any it named; an address whose dev_realm is not a realm
name is refused, as any address that is not a YDB address is. Releasing the lease removes the realm with
everything in it, and then ptah_dev when no realm is left in it; an object
there is no statement to drop for stops the removal before anything is dropped.
The provider also removes the realms it holds when its process ends. A realm is
a directory, which YDB creates only for a user who holds
ydb.granular.create_directory; a user refused the directory is refused the
realm with that right named.
The session receipt reports the realm's path as its database, and inspection,
locks, migration history, migration metadata and every native write resolve
against it, so the realm reads as an empty database. Every SQL unit starts with
PRAGMA TablePathPrefix naming the realm, which resolves a relative path inside
it. The pragma does not confine an absolute path, a path that climbs out with
.., a name written through a $ expression, or an object the whole database
shares, so the provider reads each unit before it dispatches any of them and
refuses the batch with ydb.realm-escape when a statement writes outside the
realm, moves the prefix, runs code it computes, or creates a user, group,
permission, resource pool or backup. An external data source or table, an async
replication, a transfer and a streaming query are refused too. A statement the
guard does not recognize is refused. The rules are the host's replay guard for a
realm; internal/yql/testdata/realm holds the host's answers for 94 statements,
and --!syntax_v1 is the one difference.
The provider also serves the replay guard itself (database.dev-replay-guard):
the host asks, without a session, about each statement it is about to replay
into a dev database, and the provider answers as the host's YDB guard does
(internal/devclean/ydbreplay). A run that owns the whole server, because the
operator declared it disposable or the run provisioned it, may change anything
on it but what reaches past it: an external data source or table, an async
replication, a transfer and a streaming query, and a translation setting, which
changes how the rest of the text is read. Every other scope confines a statement
to the dev database, with the rules above, and refuses --!syntax_v1 as the
host does. testdata/realm/scopes.json holds the host's verdict on each
statement under every scope.
Dev database cleanup
The provider lists and empties the dev database a session selected
(database.dev-cleanup), as the host's embedded engine lists and resets one.
The list and the cleanup read one walk of the session's root, each directory's
entries in name order and a directory after its contents, so a database the list
finds empty is one the cleanup leaves as it is. The walk leaves alone a name
that starts with a dot, which belongs to the server, the lock node the session
keeps its locks on, and at the root of a database the directory of the dev
realms. A request that names schemas walks only those directories, and keeps
them.
The cleanup drops every transfer and streaming query, then every async
replication, then external tables and column tables, then external data sources,
then the other objects, then secrets, and removes the directories, deepest
first, with the host's statements. A coordination node is dropped through the
coordination service. An object there is no statement for, such as a resource
pool, refuses the cleanup before anything is dropped, and the reply names it. A
cleanup that stops after its first step is UNKNOWN, and the host treats the
database as not clean.
A session on a dev realm takes its locks on a ptah_locks node at the realm's
root, and the cleanup of the realm keeps that node, where the host's embedded
engine dropped a node there. That engine took a realm session's locks on the
node at the database root instead (path.Join(driver.Name(), "ptah_locks") in
its internal/dblockext/ydb; measured on 25.1.4.7, a realm run creates the
root's node), so the realm's node was no lock of its, and it created the root's
node from inside a realm, which the realm's guard refuses for every other
write. The provider keeps a realm session inside its realm: its lock node is
there, and the read and the cleanup leave it alone as they leave the database
root's node alone.
internal/devclean/testdata holds scheme trees and what the host's embedded
engine listed, dropped and removed for each, recorded by an oracle program; the
tests replay each tree through the provider's walk and cleanup.
A session on the database itself leaves the ptah_dev directory out of
everything it reads, and a table owner may not be named ptah_dev.
Dev databases
The provider advertises database.dev. A host request with the spec
docker://ydb[/<tag>[/local]] starts ydbplatform/local-ydb:<tag> (latest
without a tag) with in-memory disks, publishes its port on loopback only, waits
until the server takes DDL, and returns a lease and the address
ydb://127.0.0.1:<port>/local?go_balancer=disable. The host removes the
container by releasing the lease; the provider also removes every lease it still
holds when it receives SIGTERM or SIGHUP or when the host closes it. A provider
killed outright leaves its containers, which carry the label ptah-dev for
manual removal.
The spec follows the host's docker://ydb rules: the engine is matched as
written, one path segment is a tag, and local-ydb serves the database local
alone. The host also sends the image form the operator types,
docker+ydb://[<host>/]<image>[:<tag>][/local], which starts the image it
names and is read as the host reads the image form of its other engines: the
last path segment is the database when there are two or more and it holds no
colon, the segments before it are the image, a host other than _ leads the
image, and an image with no tag is latest. The image has to serve the
database local without a credential, as local-ydb does. Parameters are merged over the default go_balancer=disable. Those that
would point the connection elsewhere or hand the container a credential
(host, hostaddr, port, user, password, dbname, passfile, service,
servicefile, database, dev_realm, monitoring, token,
use_env_credentials) are refused, and so is any parameter a YDB address does not accept, before a container
starts. The host starts other engines itself.
The provider drives the docker command line, which it finds on PATH. The
host starts the provider with an explicit environment, so pass PATH, and
DOCKER_HOST or DOCKER_CONTEXT when the daemon is not the default. local-ydb
takes every connection without a credential, so a daemon on another machine is
refused (UNSUPPORTED), and a missing command or an unreachable daemon is
UNAVAILABLE.
Execution and transactions
Execution sends native SQL units through YDB's query service. A batch is checked before its first unit runs. Unsupported native envelopes refuse the whole batch. SQL units use a pinned connection; native coordination, table creation and table alteration units use their native services. Dispatched writes are not retried, except a changefeed statement YDB refused because another schema operation held the table's path (see Changefeeds). Request IDs correlate calls and do not provide exactly-once execution.
Results distinguish a confirmed prefix from an uncertain remainder. A
dispatched unit YDB refused is NOT_APPLIED, or PARTIAL after a confirmed
prefix, when nothing of it can have been applied. That takes a status that
establishes the refusal (BAD_REQUEST, SCHEME_ERROR, PRECONDITION_FAILED,
ALREADY_EXISTS, NOT_FOUND, UNSUPPORTED, ABORTED, or GENERIC_ERROR for
a plain data statement, for one ANALYZE statement, which changes no schema
object and no row, or for a text YDB refused while compiling it, which its
issues show by naming a position in the text and no execution stage) and a
unit that ran in a caller transaction, which YDB
applies only by its commit, or that is one query YDB applies whole: a data
query or one scheme statement. A native unit whose first statement YDB refused
is judged by that statement: the service reads and checks before it writes, and
writes only through its statements, so nothing of the unit was applied. That
holds for every native unit but a column inventory, which alters a table through
the table service first, a rebuild's copy, a table creation or alteration and a
coordination node operation, which run no statement. Its message is YDB's own
issue text on one line, which no host prints; its reason is the provider's (see
Error reasons).
Every other failed dispatched unit reports UNKNOWN, even when earlier units
succeeded: a block or action call that runs scheme statements, a BATCH
statement, a unit of several scheme statements, a native unit past its first
statement, a unit that holds a coordination node statement, a unit that expands
secret inputs, and a
failure that carries no server status. A unit that holds a comment statement
runs as its statements one after another, and reads by the first one when that
one fails, since nothing of the unit ran before it: a query by the rules above,
and a comment statement, one AlterTable YDB applies whole, by a status that
establishes the refusal, such as BAD_REQUEST for attributes past YDB's
budget. A later one failing leaves the unit UNKNOWN. Cancellation between
units reports the confirmed prefix. Diagnostics omit raw driver errors.
A unit YDB refused in a caller transaction ends the transaction: measured on
26.2.1.14, YDB has dropped it, and a rollback answers Transaction not found.
The provider rolls it back and retains ROLLED_BACK for the caller's finish.
A commit YDB answered ABORTED applied nothing and is ROLLED_BACK.
A unit refused before dispatch reports NOT_STARTED, or PARTIAL after an
applied prefix, with the diagnostic ydb.precondition-refused. Where the
reason is the provider's own text about the object, such as a plan the object
changed after, a path another object holds, or a read the server answered with
a status other than success, the message is that reason, naming the object by
its path and the server's status by its name. Any other reason, such as a lost
lock or a failed system-view query, gets a fixed sentence, so no server text,
credential or declared value reaches the host.
Every native creation reads where its object goes before the write: the listing of its directory, or the system view for a streaming query and the resource pools. YDB creates the directories an object needs with the object, so a directory that does not exist holds no occupant, by the rule a filtered read uses (see Schema observations), and the first object of a new directory is created there. On 26.2.1.14, YDB created a topic, a coordination node, a secret, an external data source and an external table, a row table (through YQL and through native CreateTable), a column table, a view and a streaming query two directory levels below an existing directory; 25.1.4.7 did the same for every kind but secrets and streaming queries, which it does not keep. Resource pools and classifiers have no directory.
The provider offers units.statement-timeout. A host that lists it sends a
migration's statement timeout with each Execute call, and the provider bounds
each unit with it and answers for what YDB does when it fires, as the host's
embedded engine does (migration/migrator, ydb_timeouts.go); the reply says
the timeout stopped the unit. A unit in the caller's transaction is canceled
and the transaction never commits, so nothing of the unit is applied
(NOT_APPLIED, or PARTIAL after a prefix), and the provider rolls the
transaction back. An index added to a table that exists, and a column added
with a default, run as a build that keeps running when the client stops
waiting: the provider cancels the build through the operation service and
waits for it to end, so a stopped build is not applied, and one that completed
first counts as applied. Any other unit, and a build that cannot be found or
does not end, may still be applied by the server, and its outcome is UNKNOWN.
A host that does not list the feature bounds a statement with the deadline of
the Execute call, and a unit that deadline stops is UNKNOWN and is not sent
again. The session receipt reports migration_statement_timeout=true either way.
Transaction handles belong to one provider session. DEFAULT and SERIALIZABLE both use YDB's native serializable read/write mode; receipts retain the requested isolation. Begin cancellation remains connected while the database transaction opens, then detaches when the handle is returned. Ending the begin RPC therefore does not roll back later work.
A session permits one active transaction. Execution must name its handle while it is active. Commit and rollback retain their terminal result, including UNKNOWN and an aborted commit, without issuing a second database operation. Rollback of an unused handle fences a late begin. The SDK client sends rollback after a failed begin, including a duplicate begin; callers must not reuse handles. Each session retains at most 1,024 transaction handles.
Closing a session rolls back unfinished work even if the close caller was
canceled. Connection acquisition for begin and native transaction calls have a
30-second bound. Unconfirmed cleanup remains an error. YDB schema changes are not transactional; session
receipts explicitly report transactional_ddl=false. Live tests verify that
DDL and SQL transaction-control statements cannot escape an explicit data
transaction. See YDB transaction semantics.
Coordination, external object, row-table creation, row-table alteration, changefeed and topic operations use native envelopes, or YQL statements in the TEXT form; see Text units. Other operation models remain to be extracted. A SQL unit that runs one of the host's coordination node statements is run through the coordination service; the host's extended comment statements are not implemented here.
Migration splits
The provider and the ydb engine list units.migration-split (contract 25.12,
ADR 0054 in Ptah) beside the sessions.transactions and sessions.history
services it requires. SplitMigration reads one migration body offline and
returns the queries YDB runs it as, cut by internal/yqlquery as the host's
embedded YDB engine cuts them: each scheme statement and each BATCH
statement a query of its own, each run of data statements one query, and
every PRAGMA, DECLARE, named expression and action carried into each later
query. A unit's sql is that query's text, and its source is the query's own
statements as written, which the host hashes and records. The corpus under
internal/yqlquery/testdata/corpus holds the host's text and source for each
case, recorded by internal/rowdata/testdata/oracle.
A data query is WITH_CHECKPOINT: the host runs it in a serializable
transaction together with the history write that records it. A scheme query,
a BATCH query, and a block or action call that runs a scheme statement are
OUTSIDE, and the host runs each with no transaction. A block or an
EVALUATE FOR that runs only data statements is a data query: measured on
26.2.1.14, YDB runs and commits DO BEGIN UPSERT ...; END DO, DO $a(1) over a
data action and EVALUATE FOR ... DO BEGIN UPSERT ...; END DO in an
interactive serializable transaction, and refuses one that runs a scheme
statement there.
The split refuses the whole body, with the byte range of the refused text and
a reason that quotes nothing from the body, where the host's embedded engine
refuses it before a run: a translation setting other than --!syntax_v1 (such
as --!ansi_lexer), a client delimiter directive (DELIMITER or
-- atlas:delimiter), a statement that controls a transaction (such as
COMMIT), and a block or action call that runs a scheme statement and a
statement that reads or writes a table, which YDB refuses whole. Each check
assertion gets the verdict RunStatement gives the same text under
READ_ONLY before it opens anything, so a dry run refuses what the run
refuses. The run still proves the reads local against the catalog.
For a host that lists the feature, a unit YDB aborted for a conflict in a
caller transaction (ABORTED, Transaction locks invalidated) is
NOT_APPLIED with Execution.transaction_aborted, and a commit YDB aborted is
ROLLED_BACK with TransactionResult.aborted. Neither applied anything, and
the host may run the transaction again. A host that does not list the feature
gets the same outcomes without the flags. An ABORTED answer YDB gives for the
transaction itself, not for a conflict, sets neither flag, since every new
attempt answers the same: measured on 25.1.4.7, a transaction whose writes do
not fit one datashard program, such as the copy of a table rebuild over about
48 MiB, commits with issue 200509 (Datashard program size limit exceeded),
and the commit is ROLLED_BACK with the reason that says so.
Text units
The provider lists render.text-units (contract 25.3, ADR 0034 in Ptah). A
host that lists it asks for the TEXT form where it writes text: migration
files, schema render, SQL diffs and SQL inspection. Every unit is then a YQL
statement, the one the host's embedded engine writes for the same change, and
this provider's Execute does with it what the native call does:
| Operation | Statement |
|---|---|
| Row-table creation | CREATE TABLE with its columns, key, families, TTL, automatic partitioning, read replicas, key bloom filter and starting layout, then ALTER SEQUENCE ... RESTART WITH for a Serial whose sequence the declaration gives a start or an increment |
| Serial sequence change | ALTER SEQUENCE on the sequence's absolute path, with START WITH and INCREMENT BY and no restart |
| Row-table alteration | One ALTER TABLE: families and placements, added or dropped columns, SET (TTL = ...) or RESET (TTL), the whole automatic partitioning set, or ADD/DROP INDEX |
| Column table | The CREATE TABLE ... STORE = COLUMN and TTL statements the provider runs |
| Coordination node | CREATE COORDINATION NODE ... WITH (...), ALTER ... SET (...) naming the settings that change, DROP COORDINATION NODE |
| External data source or external table | CREATE, CREATE OR REPLACE or DROP EXTERNAL DATA SOURCE or EXTERNAL TABLE, with the paths and options as the declaration writes them |
| Topic | CREATE TOPIC with its consumers and the settings the declaration states, DROP TOPIC, or the ALTER TOPIC statements of an alteration, one to a line: the statements Execute runs natively for the change |
| Changefeed change | One statement to a line: ALTER TABLE ... DROP CHANGEFEED for each removal, then ALTER TABLE ... ADD CHANGEFEED for each addition with one ALTER TOPIC ... ADD CONSUMER per consumer, then the ALTER TOPIC statements that change a topic in place |
YQL has no coordination node statement, so Execute reads one as the host's
YDB connection does and runs it through the coordination service: alone in its
query after nothing but definitions, with the query's PRAGMA TablePathPrefix,
and inside a dev realm relative to the realm. A CREATE checks first that no node
is at the path, because the service answers a second creation with success and
keeps the node as it is; an ALTER merges its settings with the node's and
refuses a result YDB would move into its range. Such a statement in a
transaction, or one that cannot be read, refuses the batch before any unit runs.
A change only the native call makes refuses the batch with NO_TEXT_FORM and
no unit: user attributes other than Ptah's comments, storage settings, tiering, the partition columns, a
family kept in memory, a default of a type without a literal, a sequence on a
column that is no Int16, Int32 or Int64, a start or an increment on a 16-bit or
32-bit Serial's sequence, a sequence of a creation or a change that names no
database (ALTER SEQUENCE takes only the absolute path), a TTL run interval other than YDB's own, a coordination node that
declares its access, an external data source or external table dropped and
created again around the plan's other statements, which the native call gives
the owner and the access entries of the old one, and every topic
operation. A host that does not list the feature gets native units.
Reverse migrations
The provider lists models.reverse (contract 25.19, ADR 0062 in Ptah) and
declares one reversal service, ydb-objects, for the changes of its
standalone objects: coordination nodes, secrets, topics, streaming queries,
external data sources and external tables, resource pools and classifiers,
async replications and transfers. ptah migrations generate writes the down
file of such a change from this service's reverse of it, as the host's
embedded engine writes it from its own:
- The reverse asks for the declaration of what the forward change found, the conversion of the read, and finds what the forward change leaves, the conversion of its declaration. A creation reverses to a drop, a drop to a creation and a change to the settings the object held. The prediction of what the forward change leaves is the forward state the host plans the reverse against; it is never read from the server.
- The down file holds the reverse's statements, in the exact statement style
the host plans in, and a
-- Recovery limit:line for what the rollback cannot undo, in the embedded engine's words: a dropped topic loses its messages and its consumers' positions, a dropped coordination node its semaphores, a restored workload setting cannot undo the queries run under the forward one. - A rotated secret has nothing to run in reverse: the value it replaced was never read. A secret created again takes the value of the variable for its path when the rollback runs.
- A changed streaming query text keeps
allow_state_resetin the reverse, which resets the query's checkpoint again. - The reverse asks for the nearest state YDB reaches in place, and the down file says what it keeps. A topic keeps the partitions the forward change added, and keeps auto-partitioning, paused, once the change turned it on. An async replication or a transfer keeps a credential the change added. A replication failed over before the forward change dropped it is not created again, since its tables stayed as ordinary tables. A reverse that leaves nothing YDB can run has no statement, only its recovery limit.
- What the forward change leaves of a replication or a transfer runs in the state the change found it in, so the reverse of a paused replication's connection change is an ALTER that YDB takes.
- A change on a target without the capability its forward change needed,
such as
secretsorexternal_data_sources, is not reversed: the reversal refuses it, as the host's embedded engine did.
The service also undoes a change of a table's own settings: a row table's
properties, such as its TTL and its partitioning, and a column table's
storage. What the forward change leaves is the description the table held
with the declared settings in place of its own; a setting the declaration
leaves unset stays as the table held it, except the TTL, which a table the
declaration gives none loses. A column's move to another family reverses to
a move back: what the forward change leaves is the column in the declared
family, with a default or storage setting the declaration does not state
kept as the column held it. A host that does not project a reverse's state
onto a column cannot plan that reverse and refuses it. A change of a global
index's partitioning and read replicas reverses to every setting the index
held, as the host's embedded engine restores them (restore every setting the index held in place), read replicas the forward change removed included: the
declaration keeps every setting it leaves out, so a reverse that stated only
some would keep the rest as the forward change left them. What the forward
change leaves is the index's own facet with the declared settings in place of
the ones it held. YDB removes no maximum partition count, so a maximum the
forward change gives an index that held none stays, and the down file says so
in the embedded engine's words. The host plans such a reverse with the
declaration the forward change started from; the planner gives the index what
the reverse change asks for, against the facet the reverse predicts, rather
than what that declaration states. A host that does not list the feature gets
no reversal service and refuses every such change, naming the change kind
that has no reversal.
A topic reverses like any other object, and a migration file holds its statements (see Text units).
Common objects
The provider lists schema.common-objects (contract 25.3, ADR 0033 in Ptah),
and the ydb engine declares views, comments, roles and grants. A host that lists
the feature compares and orders these objects in its shared code, as it
does for every engine; the provider reads them, spells a declaration the way
YDB does, and writes each change as the host's embedded engine writes it. A
host that does not list it gets none of this: views stay native objects, and a
declared comment is refused as a table setting no creation states.
The comments of a table the host creates, of its columns and of its indexes, and of a column or an index it adds to a table, reach the provider as comment steps after the step that creates the object, as for any comment change. The provider takes them off the declaration it creates the table from, and runs the steps after its own steps on that table, with the exceptions below.
Views and comments use these shared stages:
| Step | Views | Comments |
|---|---|---|
| Inspect | Each view, its query as the server stores it; on a dev realm without the realm's PRAGMA TablePathPrefix, which YDB stores at the head of a view the session created |
The comment of each row table, of its columns and indexes, and of each view, from the user attributes YDB keeps them in |
| PrepareCommonObjects | The query in the server's form: its tokens joined by single spaces, comments and terminators left out | As declared |
| ValidateSchema | Refuses a view without a query, WITH CHECK OPTION, view attributes and materialized views |
Refuses what an attribute cannot hold: a key over 100 bytes, a value over 4096 bytes, text that is not UTF-8, more than 10240 bytes on one table |
| Render | CREATE VIEW ... WITH (security_invoker = TRUE) AS, the query and ; on lines of their own, then the view's comment; DROP VIEW |
Ptah's COMMENT ON TABLE, COLUMN, INDEX ... ON and VIEW, IS NULL to remove one |
| PlanFeatures | Dropped before every table step and created after each one that does not only remove | After every step on the table that keeps it, except one that only removes; a comment on the table or a column also skips a step that changes only an index |
Under schema.view-query-equivalence (contract 25.33, ADR 0108 in Ptah) the
host asks the provider whether the query a declared view states and the query
the held view keeps are one query, and compares no YDB view query by its own
reading, which folds case. The provider reads both into the server's form and
compares them as they stand, as the host's embedded engine did: YDB names keep
case, so a query that changes only the case of a name, of a keyword or inside a
string is another query, and the plan replaces the view; a comment, a line
break or a terminator the server drops changes nothing.
The server's form of a query was measured on 25.1.4.7 and 26.2.1.14 alike: a
two- or three-character operator is one token except >>, which YDB writes
> >; $x is $ x; a number keeps its exponent and its suffix; a quoted
string or name, a multi-line string among them, is kept whole. A read of a view
applied once therefore equals the preparation of its declaration.
A step that only removes, such as a column's removal, is ordered after the views and comments of the plan, not before them: the host places a removal after its views and comments, and what a removal drops is no longer declared, so no view the plan creates reads it and no comment the plan sets names it. The one comment step that names a dropped column removes its comment: YDB keeps the attribute once the column is gone and would give it to a column added later under that name. The host sends that step for each dropped column that held a comment (stokaro/ptah#5280), in its comment window, so it runs before the removal.
A comment on a table or on one of its columns is not ordered after a step that changes only an index of the table, such as an index's addition. The host places the comment of a column it adds right after the column, before the index additions, and the index step changes nothing such a comment names. A comment on an index still follows the steps on its table.
YQL has no comment statement. Execute reads Ptah's COMMENT ON as the host's
YDB connection does and sets the one attribute it names through the table
service, after checking that the object is in the session's database or realm,
is a row table or a view of the kind the statement names, and for a non-empty
comment has the column or the index. A statement whose path holds nothing, or
one the session may not see, is refused before any write with a message that
names the path. A unit holding a comment statement beside
other statements, such as a commented view, runs as the queries a migration
file is split into, one statement after another. A comment statement in a
transaction, or one that cannot be read, refuses the batch before any unit
runs. A comment is never a table property: the table facet leaves Ptah's
comment keys out, a declared attribute under one of them is refused, and the
new table of a rebuild takes the comments the read reported, which the host
carries on the observed table, its columns and its indexes of a planning
request.
Users and groups share YDB's principal namespace. Creation accepts lower-case ASCII letters and digits. Groups have no login or password settings. Membership can name a cluster-managed group, but cannot declare self-membership or make a principal a member of a declared user. Inspection preserves every observed membership, including edges whose principals are outside the managed role list.
A read leaves out of the description the users and groups the cluster made for
itself, the database's owner, and every user and group of a dev realm, and
names them as roles the server holds outside its scope. Under
schema.role-scope-reasons (contract 25.26, ADR 0076) the read also says why,
and the host's note gives the reason the host's embedded engine gave: because the cluster made them or they own the database, or the read was of a dev realm, which shares its database's users and groups.
A user's password is set when the user is created, from a YAML, Go or YQL
declaration, and is never read back: a declaration that names another password
plans nothing, as the host's embedded engine plans it. A YQL document's
password reaches the host under source.sql-role-passwords (see SQL schema
reading).
The password never enters a plan. Under schema.password-inputs (contract
25.22, ADR 0071) the host sends a declared password, or a password hash, as an
execution input, and the statement names the input where the password stands:
CREATE USER `app` PASSWORD $PTAH_SECRET_PASSWORD_APP;. YDB refuses that
statement as written. DescribeExecutionInputs reports the input, and the
provider writes the bound value in as a string literal only when it runs the
statement, with HASH for a password hash, and keeps it out of any error the
server answers with. The host binds the declared password when it plans from
the schema and applies in one run, and the variable's value for a saved plan
or a migration file. A host that does not negotiate the feature sends the
password itself, and the provider refuses the user rather than write the
password into the plan.
schema.common-access (contract 25.5) carries independently covered owners and
memberships. An owner's login state is optional: unknown remains unknown,
including when the server refuses its principal views. Principal queries read
names and login settings without reading passwords or hashes. The host refuses
security analysis with incomplete access knowledge. A host that does not
negotiate this feature receives uninspected role and grant coverage.
Permissions use canonical YDB names after preparation. Column grants,
WITH GRANT OPTION, PUBLIC, and permissions YDB does not recognize are
refused. Database grants name the opened database. On older release lines a
root-level grant also needs the captured absolute database path; rendering
refuses an unbound path. Native grant targets retain the provider's naming,
including literal dots in resource-pool names.
Inspection marks as implicit the entries YDB gives an object when it creates
it, which no GRANT made, as the host's embedded engine marks them: the entries
of the default resource pool that name ydb.granular.select_row and
ydb.granular.describe_schema together, and the ydb.granular.describe_schema
entry a new secret gets for every subject whose effective permissions on the
secret's directory carry that permission.
Mixed plans check captured native state before changing its access. A native
alteration precedes grants, revocations, and principal changes. A grant or a
revocation on a directory changes the directory's access list and occupies no
path, so the native planners leave it out of their path checks; a native
creation in that directory, or below it, comes first, because creating the
object creates a missing directory and YDB grants nothing on a path that holds
nothing. A final native
drop accounts for matching revocations by removing the object's access; a
temporary external-table drop keeps revocations after restoration. These plans
require the negotiated planning.lifecycle feature. Workload operations retain
their routing order after principal creation and before principal removal.
Standalone sequences are unsupported. A sequence declaration is refused before rendering.
Migration history
The history service owns its physical YDB storage. Native revision keys are signed 64-bit integers; Atlas revision keys retain exact strings, including an empty identity. Each row contains the key and a deterministic binary SDK revision record. The record retains nanosecond timestamps and durations, direction, progress, diagnostics, checksums, Atlas type bits, and ordered partial hashes.
Creation installs the table layout and a ptah.history ownership attribute in
one native request. The marker is stokaro/ptah-ydb-plugin/history-v1/native
or stokaro/ptah-ydb-plugin/history-v1/atlas. This is a storage format version,
not a stable SDK release. Reads and writes verify the marker, column types,
primary key, and absence of automatic behavior such as TTL or changefeeds.
Unknown fields, malformed records, altered layouts, and unmarked tables are
refused, with one exception: the revision tables Ptah's built-in YDB engine
wrote.
That engine kept a database's migrations in tables of its own layout, with no
attribute: the native schema_migrations (version, description, applied_at,
state, applied, total, error, error_stmt, execution_time_ms, checksum), and
the Atlas-compatible atlas_schema_revisions that ptah-compat migrate apply
writes. A revision table with no attribute and exactly one of those layouts is
read as history, and every revision written to it keeps that layout, so a
dirty migration the engine left stays dirty with its progress, a resume
continues it, and the engine still reads the table: a down run is suffixed
:down in state as the engine wrote it, an empty error is NULL in the native
table and an empty string in the Atlas one, and no partial hashes are the JSON
null. Those layouts keep the time to the microsecond, the native run time to
the millisecond, and no operator version or partial hashes in the native
table, so a revision written there holds no finer part, and a native record
with one is refused. Under the engine's default names such a table is also
left out of a schema read, as the engine left it out, so a plan for its
directory does not drop it. A table that is neither the provider's nor one of
those layouts is still refused. The provider's own Atlas format preserves SDK
revision semantics; it does not reproduce the Atlas SQL column layout or
promise interchange with external Atlas tools.
Mutations compare the complete expected record before inserting, replacing, or deleting it. The comparison and write share one native serializable transaction. A caller transaction binds history and application writes to the same commit or rollback. Otherwise the provider opens and finishes a private transaction. A result inside a caller transaction remains provisional until commit succeeds. Conflicts cannot overwrite a committed winner; uncertain outcomes never authorize an automatic retry. Request IDs are correlation identifiers.
Reads distinguish a missing store from an empty store. Namespace traversal is refused. A failed native listing is an error rather than evidence of absence. Snapshots are bounded by the caller's revision limit and a 3 MiB size limit; exceeding either returns no partial history. An unreadable history snapshot or failed dispatched history mutation makes its caller transaction rollback-only. History storage alone does not establish complete migration support. Planning and complete host integration remain pending.
Migration journals and tags
The metadata service stores exact SDK records. A journal key combines the run ID and signed sequence number; duplicate identities are refused even when the payload is identical. A tag key preserves its exact name. Versions remain signed 64-bit values, including zero, and timestamps retain nanoseconds. The host owns event meaning, attempt pairing, tag normalization, and migration recovery.
Creation installs the native layout and the ptah.metadata ownership attribute
in one request. Its value is stokaro/ptah-ydb-plugin/metadata-v1/log for journals
or stokaro/ptah-ydb-plugin/metadata-v1/tags for tags. Preparation and operations
refuse unmarked tables, unknown markers, altered columns or keys, indexes,
changefeeds, and automatic deletion settings. Reads also refuse unknown,
noncanonical, corrupt, or mismatched record identities. The log and tag tables
Ptah's built-in YDB engine wrote, schema_migrations_log (run_id, seq,
operation, version, state, actor, actor_source, checksum, logged_at, error)
and ptah_migration_tags (tag, version, recorded_at), are read and written in
that layout, with no attribute, as its revision tables are; the time is kept to
the microsecond, and an empty actor, checksum or error is NULL. Under those
default names they are left out of a schema read too.
Log writes use a separate serializable transaction and remain durable when an execution transaction rolls back. Log scopes cannot name that execution transaction. Tag mutations use the selected execution handle, or a private serializable transaction when no handle is active. Preparation inside a transaction only validates an existing store; it never creates schema. Tag replacement and deletion validate any existing record before changing it. Deletion reports existence from an atomic read, not from affected-row counts. A missing tag is distinct from a tag at version zero.
Each dispatched mutation runs once. A lost write or commit acknowledgment is UNKNOWN, without automatic replay. A storage read or mutation error on the selected SQL transaction makes it rollback-only. Lock loss refuses further journal and tag writes, including durable log writes outside an execution transaction.
Opening a reader captures a complete immutable snapshot in one serializable transaction. A count and ordered stream must agree before any page is exposed. The snapshot uses a private temporary file, so memory is bounded by a record and an RPC page rather than journal length. Unix unlinks the file immediately; Windows uses native delete-on-close. Closing the handle or terminating the provider releases the file on both platforms.
Pages contain at most 256 records and 3 MiB including their envelope. Oversized records and incomplete reads are refused, never truncated. Snapshot handles are single-use and bind the exact session, transaction, kind, and store. Page indices advance once; a failed or uncertain page must be closed. Closing an unused ID fences a late open. Each session retains at most 1,024 reader IDs and allows at most 16 unfinished snapshots. Completion and close release snapshot files; transaction finish and session close release their remaining readers.
The live tests cover journal durability, tag commit and rollback, same-transaction snapshot immutability, journals larger than one RPC, storage reservations, corruption, and reader fencing. Metadata unit tests also run on Windows to exercise native file cleanup.
Coordination locks
The lock service uses exclusive ephemeral semaphores on the database-root
ptah_locks coordination node. Each acquisition has its own native session.
The provider creates a missing node with strict read and attach consistency.
Ptah's built-in YDB engine creates the node with neither setting, which YDB
reads as relaxed reads, so every database that engine locked holds such a
node: the provider alters a node that sets neither mode to strict reads and
attachment before it takes a lock there, as a run of that engine took its
locks there, and a session of that engine that still holds a lock keeps it. A
user who may use the node but not alter it is refused with the right to grant.
Any other node must have both settings explicitly strict; the provider refuses
one someone set otherwise without changing it. The refusal is a
NOT_ACQUIRED receipt that holds nothing, and its reason names the node and its
two settings.
YDB's CLI (2.25.0 and 2.29.0, bundled with 25.1.4.7 and 26.2.1.14) and YQL have no command for a coordination node, so making such a node strict goes through the coordination service. With ydb-go-sdk, alter the node in place:
err := driver.Coordination().AlterNode(ctx, path.Join(driver.Name(), "ptah_locks"), coordination.NodeConfig{
ReadConsistencyMode: coordination.ConsistencyModeStrict,
AttachConsistencyMode: coordination.ConsistencyModeStrict,
})
DropNode on the same path removes the node instead, and the next lock creates
it strict. Remove it only while no run holds a lock there: dropping the node ends
every lock session on it. YDB's default read mode can
return stale state during leader changes; see the
YDB coordination documentation.
The provider finds the node by describing its path rather than by listing the
database root, so a user granted the node takes the lock without the right to
list the root. A server that refuses the user the lock is answered with a
NOT_ACQUIRED receipt, and its reason names the step and the right: the node is
missing and the user may not create it, the user may not use the node, or the
server refused the session on the node. The first two are the host's embedded
engine's words. For the third that engine reports a timeout, because its SDK
retries a refused session without saying why, while the server answers at once
with UNAUTHORIZED. The receipt confirms that nothing was acquired, so the host
neither fences the session nor sends a cleanup release, and the session takes
the lock once the user is granted the node. A host that lists errors.reasons
prints the reason after the outcome; see Error reasons.
A TryLock that takes no lock says why in its receipt: another session holds
the lock, the session has an active transaction, it lost a lock guarantee, it
already holds 16 locks, the server refused this user the lock, or the node is
not strict.
Acquisition is nonblocking. Only a confirmed BUSY permits the host to try a new handle under its wait policy. Handles are single-use and bound to the exact session and name. Release of an unused handle fences a late acquisition. A session retains at most 1,024 handles and holds at most 16 active locks. Acquisition during an execution transaction is refused. Read-only lock previews are not represented by this SDK revision.
A native bidirectional stream sends each acquisition once and checks native operation status and request identity. It does not reconnect or reacquire after a transport failure. Keepalives run every two seconds; the native session timeout is ten seconds. Ownership checks run every second and verify the strict node configuration, exact session owner, and exclusive ephemeral semaphore count. A confirmation expires after five seconds. Both monotonic and wall-clock elapsed time are checked, including before refreshing a confirmation after a pause.
Every SQL unit, native coordination operation, history or metadata mutation, and transaction commit verifies guarding locks. Storage creation checks the lease immediately before native dispatch too. Loss or uncertainty cancels guarded calls and permanently fences further writes in that provider session. Rollback, release, and close remain available. A new owner does not repair the old session. Failure after dispatch may have effects; an acknowledgment racing ownership loss is reported as uncertain.
Release requires successful semaphore-release and session-stop acknowledgments. Local stream shutdown is insufficient. Cleanup continues with its own timeout after caller cancellation and retains uncertainty on repetition. Provider death leaves the server to expire the native lease. The coordination node remains for later runs; its ephemeral semaphores disappear after their owners leave.
These are advisory leases, not transactional fencing tokens for SQL or DDL. Fresh checks and cancellation cannot undo a statement already executed or make nontransactional DDL atomic with ownership. Live tests cover competing provider processes, independent names, immediate takeover after confirmed close, process exit, node removal, and refusal of execution/history/commit after loss. Controlled clock tests cover stale confirmations without relying on a scheduled watcher.
Error reasons
The provider lists errors.reasons, so a host that lists it too prints why a
call failed after its gRPC code:
provider compare models failed (InvalidArgument): streaming query capability was not established
A reason is a sentence the provider wrote. It names the subject, a capability
or a parameter, and never quotes statement text, a value, an address or a
credential. Package internal/authored keeps such sentences apart from the
errors the provider receives: authored.New marks one, and authored.Status
sends it as the reason, with the error's whole text as the status message,
which no host prints. The reasons the provider gives are its refusals of a
connection address, of a monitoring page it could not read, of a capability
the target lacks, a declared external object among them, of a lock the server
refuses this user, of a streaming query body changed without
allow_state_reset, the refusals whose text is a fixed sentence, such as a
closed session or a reached handle limit, and the refusal of a table's
declaration by table preparation or the creation
projection. That last reason names the table and gives the sentence schema
validation reports for the same declaration; the compilation behind it reads no
server. A YDB driver's error is never a
reason, because YDB can quote a statement and its values, or a credential
source: the host prints its code alone.
A declaration a source decoder refuses gives a reason in the words of Ptah's
built-in YDB engine: a topic, a changefeed, an external data source or table, a
resource pool or classifier, a streaming query, an async replication or a
transfer, by its YAML key or its name, the attribute, and what the attribute
takes. authored.Declared writes it from the decoder's words around the
refusal and the refusal itself without the value it quotes. A refusal another
package wrote, such as the YQL splitter's, gives none. A Go directive that
leaves out a required attribute is refused as the host's annotation parser
refused it, and the host names the directive's place after the reason:
provider decode source failed (InvalidArgument): topic "events": invalid retention_period: takes an ISO 8601 duration such as PT12H or P1D
decode Go directives of engine "ydb": provider decode source failed (InvalidArgument): missing required annotation attribute "rank" on //ptah:schema:resourcepool:classifier at Refused
A failed execution and a commit that did not apply give a reason too, in
Execution.diagnostic and TransactionResult.diagnostic (contract 25.18), and
a host of that contract prints it after the outcome:
provider execution: NOT_APPLIED (0 units confirmed applied): YDB refused the statement while compiling it (GENERIC_ERROR, line 1, column 27)
provider execution: NOT_APPLIED (0 units confirmed applied): YDB refused the statement: it violates a constraint of the table, such as a key the table holds (PRECONDITION_FAILED, issue 2012)
The reason of a unit YDB answered says what YDB did with it (refused it, refused it while compiling it, aborted its transaction, or answered without showing whether it applied), followed by YDB's status, the most specific issue code and the position YDB named, counted in the unit's own text: on a dev realm, the realm's pragma YDB received first is not counted. For the issue codes 2003, 2012, 2031 and 2032 it also says what the statement did, in the provider's words. A reason never repeats YDB's issue text as a whole: measured on 25.1.4.7 and 26.2.1.14, that text quotes the token YDB refused, a string literal of the statement and the table's path, and a statement can carry a password or a bound secret. The issue text stays in the result's message. A reason carries the parts of it that quote none of those, as the host's embedded engine printed them: the paths of the objects a refused DROP leaves in place, YDB's fixed sentences, and the check a rebuild's copy fails with, which the provider wrote into the statement and which names the table and the column:
provider execution: NOT_APPLIED (0 units confirmed applied): YDB refused the statement: rebuilding table app.items: column n holds a value that does not convert to its new type (PRECONDITION_FAILED)
YDB's fixed sentences follow saying. A name YDB does not take in a path, such
as a secret whose name holds a space, is refused on 26.2.1.14 with an issue that
quotes the path and the name; the reason carries YDB's sentence without them:
provider execution: NOT_APPLIED (0 units confirmed applied): YDB refused the statement, saying "symbol ' ' is not allowed in the path part" (SCHEME_ERROR, issue 2019)
INTERNAL_ERROR shows nothing about a statement, with one measured exception:
26.2.1.14 fails an UPSERT on a table with a unique index that names no column
a synchronous index is keyed on while it plans the query
(verification=!hasUniqIndex || !usedIndexes.empty() in
kqp_opt_phy_upsert_index.cpp), and writes nothing; 25.1.4.7 runs the same
statement. Such a data statement is refused rather than unknown: a unit is
NOT_APPLIED with the reason that YDB cannot plan the UPSERT and that naming
every column of the table, or UPDATE or INSERT, avoids it, and a statement
the host runs for an operator, such as a check or a test assertion, FAILED
with YDB's issues. A unit the provider refused before YDB answered, a statement
timeout, a lost lock and a commit YDB did not confirm each give a fixed
sentence.
Where YDB's issues say a feature flag is off, the reason names the capability
and the flag, as the host's embedded engine words such a refusal, around the
provider's own reason where the host's engine quotes YDB's text, then what to
do. The provider's reason carries YDB's sentence for the flag, without what
YDB wrote around it. A session whose address names no monitoring endpoint took
the capability from its release line's default, and is told to name the
endpoint as well as to turn the flag on; one whose address names it read the
flags already, and is told only to turn the flag on. The texts and the flags they name are the
host's measured ones (internal/ydbflags in Ptah). Measured on 25.1.4.7 and
26.2.1.14, where EnableResourcePools is off by default, without the endpoint:
provider execution: NOT_APPLIED (0 units confirmed applied): capability resource_pools is off on this YDB cluster, which runs with feature flag EnableResourcePools off: YDB refused the statement, saying "Resource pools are disabled" (UNSUPPORTED, issue 2030). Turn the flag on, or name the cluster's monitoring endpoint in the URL (monitoring=http://host:8765) so Ptah reads the flags before it plans
and with it, the same sentence up to Turn the flag on.
YDB refuses a DROP of an object other objects depend on, such as an external
data source an external table reads, with SCHEME_ERROR before it changes
anything, and names the dependents inside issue 2019. A reason repeats their
paths, as Ptah's built-in YDB engine printed them, because the operator has to
drop them first. Measured on 25.1.4.7 and 26.2.1.14:
provider execution: NOT_APPLIED (0 units confirmed applied): YDB refused the statement: other scheme objects depend on what it drops, /local/readers/tbl; drop them first (SCHEME_ERROR, issue 2019)
25.1.4.7 has no unique secondary index, and refuses one with Unknown index type: syncGlobalUnique inside its execution stage, before it proposes a
schema operation. The unit is NOT_APPLIED (PARTIAL after an applied prefix),
with the reason YDB refused the statement, saying "Unknown index type: syncGlobalUnique": the YDB release the cluster runs has no unique secondary index (GENERIC_ERROR, issue 1060, line 1, column N).
Operational storage reservations
Inspection recognizes history and metadata storage from exact ownership attributes and complete native layout, before extracting indexes or changefeeds. A marked table with an altered layout refuses the entire inspection. An unknown marker version is also refused. An unmarked table remains managed even when its name or layout matches a history table.
Confirmed history, journal, and tag stores are returned as SDK reservations instead of managed tables. A reservation occupies the name for every supported native scheme-object kind and for common tables. Exact UNREPRESENTABLE subject coverage overrides directory completeness at these occupied names, so omission does not assert absence. A comparison leaves a reserved name out. When a plan changes an object a streaming query could read, it counts no query at a reserved name, even where the desired source claims nothing there, as an HCL document without a coverage header does. Filtered reads return reservations only from the exact directories they inspect. Names or layout alone never establish ownership.
The coordination node ptah_locks at the root of the session's database is
reserved with purpose LOCK by its name alone. The lock service takes whatever
node it finds at that path, after checking its consistency settings, so the
name is the reservation; YDB returns SUCCESS for a repeated CreateNode, so no
creation receipt could prove more. A read reports the name as taken and
describes no node there, so db read and an export leave it out, as the host's
embedded engine does. A reservation never changes or drops the node. Every
other coordination node, one named ptah_locks in a directory included, is a
managed observation.
Operational storage reservations support host collision and reference guards. They do not establish native directory-cascade safety; containment qualification remains pending. Planning orders an object created below a path after the drop of the object there, and refuses any other overlap of directory lifecycles. Markers are storage-format identifiers, not an access-control mechanism against a database administrator.
Schema observations
Inspection reads the native scheme, table, topic, view, and coordination APIs. Common columns preserve
YDB type spelling, Decimal precision/scale, nullability, and key membership,
and with schema.observed-primary-keys each table's key in key order, so a Go
export writes primary_key="b,a" for a table keyed in an order other than its
columns'.
Provider-owned table properties retain key order, TTL, column families,
partitioning, storage settings, attributes, and access metadata. Column
properties retain family assignment and typed literal or sequence defaults.
The observations carry their complete native field definitions and digests.
Normalization works offline after the database session closes.
Schema selectors name exact directories relative to the selected database.
Use / for its root. An unfiltered read walks subdirectories. A directory
whose name starts with a dot is not read, as the host's embedded engine read
none: .sys, .metadata and the other directories YDB keeps for itself, and
a user's directory such as .hidden. The read claims such a directory, and a
selected path below one, as empty, so a declared object there is created, and
nothing there is dropped. Measured on 26.2.1.14, that engine also plans the
creation again on the next run, which YDB refuses (path exist); through the
provider the next run is refused before the unit is sent.
A table whose own name begins with a dot is read like any other. Traversal and
snapshot size are bounded.
A database nested in the selected one, such as /local/db2 in /local, is
another database, and the read leaves it out as the host's embedded engine
did: YDB lists it as an entry of type DATABASE, and an unfiltered read neither
descends into it nor reads what it holds. A selector that names a nested
database, or a path below one, reads as an empty directory, as that engine
read it. The read claims the nested database's path as holding nothing of the
selected database and says why, and a plan that creates a table, a view or a
native object there is refused with a reason that names the nested database.
Here the provider is stricter than that engine on purpose: that engine planned
the CREATE TABLE, YDB ran it through the outer session into the nested
database, and every later run planned it again and failed (path exist). A
native write whose directory is at or below a nested database is also refused
before anything is sent.
Coverage names inspected subjects and records native-object completeness for
each directory whose listing and child reads finished. These regions bind the
exact database, empty catalog, and exact schema directory; an unfiltered read
sends subtree regions instead where the host negotiates them (see below). An
empty directory therefore establishes native-object absence there. Unread
descendants, excluded system directories, and other namespaces remain unknown.
The table and column facet kinds (a row table's settings, indexes and
changefeeds, a column table's storage, a column's properties) get the same
complete region for each such directory, since the read describes every table
there with each facet its kind of table has. Without it those kinds stay
narrowed in an export the read otherwise knows completely, and a Go or HCL
source has no not-described entry for a facet kind, so ptah introspect of
such a read, of the whole database or of a directory beside the database's
pools, is refused before it writes (provider encode source failed (Internal)).
A selected directory YDB does not show (it answers SCHEME_ERROR) reads as
empty when its parent's listing, proved the same way up to the database root,
does not name it. That is the evidence a walk from the root takes for any
entry a listing leaves out, and it lets schema apply --schemas create the
first object of a new directory. A directory its parent names but the session
cannot list is still refused, and so is a path below an object that is not a
directory. Execution reads the directory of each object it creates through
the same rule; see Execution and transactions.
A selected path that an object holds reads as an empty directory, with
nothing below it: YDB needs every path above an object to be a directory. The
object itself is read with the directory that lists it. So schema apply --schemas app,app/holder reads the table app/holder in app, and a secret
declared in app/holder is planned after the table's drop.
A read with no selector claims more than the directories it lists. With a
host that lists schema.subtree-coverage (contract 25.22, ADR 0064 in Ptah),
the provider declares / as its schema separator, and the read sends one
complete region over the root and every directory below it. The directories
the walk does not list, a dot-prefixed one and at the database root the dev
realm directory, are covered by it as holding nothing, as the host's embedded
engine read them, so an export of the whole database claims every native kind
and writes no ptah:schema:notdescribed entry for them. A native object in a
directory that does not exist yet is then absent, and schema apply or
migrations generate with no --schemas creates it, as the host's embedded
engine does. A filtered read keeps one exact region per directory, since a
selector names one directory. With an older host, the read claims each
directory it lists alone, and a native object in a new directory is left
unplanned with a warning, as for a topic in fresh/nested:
Warning: github.com/stokaro/ptah-ydb-plugin/topic fresh/nested.events could not be compared between the database and the desired schema: The current topic or its absence was not established.
No changes planned, but 1 comparison limit remains unresolved.
Name the directory with --schemas to create the object.
Unknown native fields and enum values are refused before JSON conversion,
except where the host's embedded engine recorded the object and read the rest:
a coordination node whose description holds a mode or a field the provider
does not model is reported unrepresentable, and so is a resource pool or a
classifier holding a setting outside what YDB keeps. A column store and a
sequence, which the provider does not model, are left out and named in
unread_tables where the host lists schema.unread-tables. A queue of the
older persistent queue kind (PERS_QUEUE_GROUP) is reported uninspected, so a
plan neither keeps nor drops it, and a system view outside a dot-directory is
the server's and is left out. Any other kind of scheme entry refuses
inspection, naming the object and its kind. Shared topic consumers refuse
inspection.
A table an async replication writes is left out of the tables, and the
replication's items name it (see Async replications and
transfers). Right after CREATE ASYNC REPLICATION, YDB can list the replica while DescribeTable still answers
SCHEME_ERROR. So a row table a listing names and the table service answers
SCHEME_ERROR for is described again for up to 10 seconds, and the directory is
listed again before each wait. The topic of a changefeed is waited for the
same way, with its table described again before each wait: the changefeed the
replication adds to its source table can be named before DescribeTopic
describes its topic. The read fails if the new listing or description stops
naming the object, if it fails, or if the object is still not described when
the time runs out; it never reads such an object as absent. A failed traversal
returns no snapshot.
These limitations preserve data for the remaining extraction work.
Observed property models use YDB's typed protocol definitions inside this provider. The neutral SDK carries their envelopes. Common fields and runtime statistics are excluded from property payloads; they cannot become a second writable representation. Index observations preserve sync, async, unique, vector, prefixed vector, and fulltext variants. The provider reads every kind-specific implementation table, including its columns, keys, and partition settings; parent table metadata alone does not expose those settings. Missing implementation tables or non-ready indexes refuse inspection. Internal sequence configuration is retained; its mutable position is excluded after unknown-field validation. An empty index facet records the absence of indexes on that inspected table.
Changefeed observations combine the table API description with a topic API
read for each child stream. They retain modes, formats, state, retention,
consumers, access metadata, and attributes, including replication bindings.
Initial scan presence is retained after completion; progress counters are
excluded. A changefeed in STATE_INITIAL_SCAN asked for a scan even before YDB
reports its first progress, as for the changefeed CREATE ASYNC REPLICATION
adds to a source table. An empty changefeed facet records absence on its
inspected table.
Standalone topics use the same reader and normalization rules, with their own
object identities.
Topic observations preserve partition settings and quotas, retention, codecs, consumer options, and attributes. Live partition topology and topic/consumer statistics are excluded. A missing child description refuses the entire read.
Indexes, implementation tables, changefeeds, and the consumers of a changefeed's topic are read in name order; a topic's own consumers keep their order (see the topic section). Column and key arrays retain their observed order. Desired coordination-node declarations are available as described below. Every observed model has a separate desired contract and an offline conversion. Comparison and planning remain limited to coordination nodes, the TTL, partitioning settings and column families of row tables, and the storage of column tables; source adapters for the other families remain pending, as described under row-table declarations below.
Views retain the query returned by the view service and the attributes read through the table service on the same path. This includes comments and custom attributes. Coordination nodes retain native consistency modes, timing, and counter configuration; the envelope owns the node path. External data sources retain source type, location, authentication references, and native properties. External tables retain source references, location, ordered typed columns, and content options without rewriting their native keys or JSON values.
Object counts
For a host that lists models.report-counts, the provider reports count
metrics for its topic, changefeed, coordination node, streaming query, external
data source, external table, async replication, transfer, secret, resource pool
and resource pool classifier models, and for its row-table properties, column
storage, index set and index settings models, desired and observed, under the
metric names and help texts of the host's built-in YDB engine. It counts each
value offline: a topic is one topic and its consumers, a table's changefeed set
its feeds and the consumers across them, an external table one table and its
columns, and every other object one. ptah schema stats then writes these
lines through the provider as through the built-in engine.
The table setting families count as the built-in engine counted them:
| Metric | Counts |
|---|---|
ydb_ttl_tables |
Row tables with a TTL, and column tables whose TTL is one tier that deletes rows. |
ydb_column_families |
The column families of each row table, without a default family that has no storage pool of its own, no compression, the regular cache and keep_in_memory off. |
ydb_table_partitioning |
Row tables whose partitioning, read replicas or key bloom filter differ from what YDB gives a new table, and declarations that state a starting layout. |
ydb_index_partitioning |
Global sync, async and unique indexes whose partitioning or read replicas differ from what YDB gives a new index. |
ydb_column_tables |
Column tables. |
ydb_tiered_ttl_tables |
Column tables whose TTL has any other tiers, such as one that moves rows to an external data source. |
ydb_vector_indexes |
Vector indexes. |
The host counts the global indexes it reads as common indexes in indexes. The
index set and the column storage models add the indexes they hold under the
same metric and help, Indexes: vector and full-text indexes, and a column
table's local indexes, which the built-in engine counted among the indexes too.
A host adds them to its count with stokaro/ptah#5378; a host without it refuses
schema stats when a provider reports the metric.
What YDB gives a new table or global index is automatic partitioning by size at 2048 MB, none by load, at least one partition, no maximum, no read replicas and no key bloom filter; an unspecified setting is that value. A read holds every setting the server describes and is counted against these values, as is the declaration the host converts it to before it counts.
A host that reads global indexes as common indexes (schema.observed-indexes)
leaves them out of a table's index set, so ydb_index_partitioning counts each
such index from its own settings, the index settings model. A host reads those
when it negotiates schema.observed-index-facets; through a host that does
not, the count leaves the common indexes out.
Offline coordination declarations
The source service accepts the captured YAML document key coordination_nodes.
The host parses YAML and supplies typed values and original byte spans; the
provider interprets this grammar without reading files or opening a session.
coordination_nodes:
workers:
schema: app
self_check_period: PT2S
session_grace_period: PT5S
read_consistency_mode: strict
attach_consistency_mode: relaxed
rate_limiter_counters_mode: detailed
Each entry is a mapping. Its key supplies the node name unless name overrides
it. schema is an exact directory relative to the database; struct_name
retains source provenance. These fields and every setting require text.
Unknown keys, explicit nulls, numeric coercion, repeated fields, duplicate
object identities, and collisions with declared tables in the same captured
namespace refuse the whole source batch. Root ptah_locks is reserved for
runtime use; the same leaf name in a user directory is allowed. Inspection
still returns root coordination nodes as ordinary observations.
Periods use positive ISO8601 durations and must preserve whole milliseconds. The self-check interval must be 500–10,000 milliseconds. The grace interval must be at least the effective self-check interval plus 1,000 milliseconds and at most 30,000 milliseconds. The provider refuses configurations the server would clamp. Mode strings are case-insensitive in source and stored lowercase.
Omitted settings remain absent in the desired model. They request server defaults when the node is created or compared: a 1,000-millisecond self-check, 10,000-millisecond grace interval, relaxed reads, strict attachment, and aggregated counters. Explicit settings equal to defaults remain explicit. Offline normalization does not assert desired/observed equivalence.
The decoder returns native objects with source traces and coverage limited to the models enrolled by each document. An empty mapping declares no nodes within that scope; an empty model scope stays empty. No successful prefix survives an invalid entry or cancellation. Desired and observed models have distinct encodings and definition digests; observation coverage contains only observed models.
The same service decodes the host's Go directive ptah:schema:coordinationnode,
written on a struct of its own, one node per directive:
//ptah:schema:coordinationnode name="limits" schema="app" self_check_period="PT2S" read_consistency_mode="strict"
type Limits struct{}
name is required, and the other attributes are the YAML settings above, with
the same rules; struct_name has no attribute, since the host names the struct.
A YAML document that writes coordination_nodes, and every Go source, describes
the nodes completely, as they do for the host's embedded engine: a node the
source does not declare is one the plan removes from the directories it reads.
A host that lists source.hcl (contract 25.3, ADR 0036 in Ptah) reads the
top-level coordination_node block the engine claims, as the host's bundled
YDB source reads it: labeled with the node's name, or its directory and its
name, its settings string attributes spelled as above, and named
coordination_node by a permission's for.
coordination_node "app" "limits" {
self_check_period = "PT2S"
read_consistency_mode = "strict"
}
It also claims, at the index-attributes site, a global index's partitioning
and read replicas inside the frontend's index block, spelled as the index
directive spells them (see Index settings from a source):
a switch and the read replicas as strings, a count as a number.
index "by_body" {
auto_partitioning_by_load = "ENABLED"
auto_partitioning_min_partitions_count = 2
columns = [column.body]
}
A ptah:not-described coordination_node header entry, with a node's path or
none, is the one header kind the engine declares, and the provider refuses a
name no node can take with the host's words. With source.limits negotiated,
the document's claim leaves the node it names, or every node, unmanaged. A host
that does not list source.hcl reads no such block.
An HCL document is an Atlas file, which names none of the nodes it leaves out,
so it claims the nodes only through its header, as the host's embedded engine
reads it: a coordination_node entry, or the captured ptah:feature-coverage
header an export writes, which the host reads itself. A document with neither
claims nothing of them, and a plan from it creates and alters the nodes it
declares and drops none. The provider cannot keep this rule alone: the SDK lets
a decoding claim less than complete only through header limits, so its reply
for a document without them claims every node. A host that takes an HCL
document's claim only from its header asks the provider about no other
document; one that asks about every document plans to drop each node the
document does not declare, and writes its HCL exports without the captured
header. TestHostKeepsTheNodesAnHCLDocumentDoesNotName measures the first
kind of host and skips on the second.
An HCL file the host's embedded engine exported names that engine's
coordination-node model in its captured header: owner ptah.run/ydb, kind
ptah.run/ydb/coordination-node, schema version 1. The provider names that
identity as a predecessor of its own coordination-node model
(models.predecessors, contract 25.25), so a host that lists the feature reads
such a file with the claim it was written with. Applied back, it plans
nothing, and a node whose block is removed is dropped, as the embedded engine
dropped it. A file the host cuts the document into carries the provider's
identity. A host without the feature refuses the file with unknown feature codec: "ptah.run/ydb/coordination-node"/desired.
TestHostReadsTheBuiltinEnginesHCLExports_HappyPath applies exports recorded
from the embedded engine (internal/backend/testdata/builtin-hcl).
A captured header the host writes for this provider's models names each
model by the identity the provider describes: the provider as the owner, the
model's kind and representation, its schema version and its definition
digest. The release version, the features a host negotiated and the engine's
other declarations do not enter it, so an HCL export one release of this
provider wrote reads with the next one while no definition text changed.
When a text changes without changing what a claim of the model means, the
identity it replaces is named a predecessor of the model itself
(models.own-predecessors), and an earlier export still reads; a header
that names a definition the provider neither describes nor names is refused:
incompatible feature codec: coverage model "github.com/stokaro/ptah-ydb-plugin/coordination-node"/desired.
TestModelIdentitiesAreDescribedOrDeclaredPredecessors pins the identity of
every desired model, so a definition text cannot change unseen, and
TestHostReadsAnEarlierProviderHCLExport_HappyPath applies an export an
earlier build recorded (internal/backend/testdata/provider-hcl). A Go export,
and an HCL export from a host that writes no captured header for a provider,
name no model identity.
A Go source carries the same entries as ptah:schema:notdescribed
declarations, under source.limits, of the kinds the engine lists in
go_header_kinds: coordination_node, streaming_query, resource_pool,
resource_pool_classifier, secret, topic, external_data_source,
external_table, replication and transfer. An entry without a name leaves
the kind unmanaged; one with a name, written as a YQL header names it, leaves
that object unmanaged. A source that declares an object it also leaves
unmanaged is refused.
//ptah:schema:notdescribed kind="topic" reason="not-inspected" provenance="observed"
type PtahSchemaObjects struct{}
EncodeSource (source.encode, ADR 0037, and source.encode-groups, ADR
0040) writes desired values back as the keys, directives and blocks this
provider's decoders read, which decode to the same declarations:
| Value | YAML | Go | HCL |
|---|---|---|---|
| Coordination node | entry of coordination_nodes |
ptah:schema:coordinationnode |
coordination_node block after the tables |
| Topic and its consumers | entry of topics |
ptah:schema:topic, ptah:schema:topic:consumer |
loss |
| Secret, by the variable its value comes from | entry of secrets |
ptah:schema:secret |
loss |
| External data source and external table | entries of their keys | their directives | loss |
| Resource pool and classifier | entries of their keys | their directives | loss |
| Streaming query | entry of streaming_queries |
ptah:schema:streamingquery |
loss |
| Row-table TTL | platform.ydb |
attributes of ptah:schema:table |
the table's platform "ydb" block |
| Row-table partitioning, read replicas, key Bloom filter, starting layout | table keys | attributes of ptah:schema:table |
loss |
| Column families and the columns they hold | column_families |
ptah:schema:columnfamily |
loss |
| Column-table storage, hash key, shards, TTL | column_store |
attributes of ptah:schema:table |
a TTL that only deletes in the table's platform "ydb" block; the rest is a loss |
| Changefeeds and their consumers | changefeeds |
ptah:schema:changefeed, ptah:schema:changefeed:consumer |
loss |
| A global index's partitioning and read replicas | keys of its index entry | attributes of ptah:schema:index |
attributes of its index block |
A value is written as the declaration the provider compares it as: a topic or a
changefeed as its declared settings, without the attributes YDB keeps on it; a
table's settings without the primary key the common columns carry; a column
default and a Serial's sequence not at all, since the common column carries
them: the read reports a sequence's start and increment as the column's
identity_start and identity_increment beside auto_increment, and refuses a
sequence with any other setting. A facet a source states
by writing nothing, such as an empty changefeed set, an empty native index set
or a column of the default family, is written by the table's own part. The
default family every row table holds is written only when it has a setting of
its own, such as a compression or a storage pool, and a declaration that leaves
it out keeps what the table holds. A Go export writes the table directive's
attributes in the host's embedded engine's order: a column table's storage,
with store="column", then the TTL, then the partitioning and the starting
layout. It also gives each column the Go type of its YDB type, as that engine
does, and refuses a nullable key column, which no declaration can ask for.
A Go export writes the directives of the standalone objects in the order the
host's embedded engine wrote them, each with its attributes in the order its
claim lists them: topics and their consumers, secrets, external data sources,
external tables and streaming queries after the views, then resource pools,
their classifiers and coordination nodes. Under source.go-placements
(contract 25.26, ADR 0077) those last three stand after the default
privileges, below the grants, where the embedded engine wrote them; a host
that does not list the feature writes them after the views.
What no declaration states is a loss for its value, in one line that names it:
an access requirement, table attributes, storage settings, tiering, a temporary
table, a family kept in memory, a TTL run interval, a disabled changefeed or the
stream of an async replication, a native index, a column's compression or
encoding, a native view (the host writes views from the common
wire), and an object of another database than the one it was read in. A
secret's value is never written. HCL declares coordination nodes, a global
index's settings, and a table's TTL in its platform "ydb" block, as the host's
embedded engine does; its other YDB settings are losses, which the host reports
at the table in the embedded engine's words, such as YDB table settings (AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 3) are not represented in HCL. A
facet whose TTL the block writes and that holds such a setting too is written in
part, and the loss names the setting. The settings a read reports for every
table, YDB's documented defaults, are no loss, and neither are the empty
changefeed and native index sets a read reports for a table without either,
which the table's platform "ydb" part writes by writing nothing; a
changefeed or a native index the table holds is a loss by name (changefeed updates is not represented in HCL). The parts are
decoded again before they are returned, and a value they do not give back is a
loss. A value that needs several declarations is a loss for a host that does not
negotiate source.encode-groups. struct_name is written in YAML alone.
A source claims every model its format lists, completely. An export of a read
is complete for the selection it is applied to, as the host's embedded YDB
engine writes it: a host that writes a read narrowed by --schemas sends each
kind the read knew in every directory it listed as complete. Applied back over
that directory, the export plans nothing, and an object whose declaration is
removed from it is dropped; applied over more directories, it drops their
objects too. What the request does not know is written as not-described
entries, so that the source read back claims no more than that: a kind no
directory of the read knew, or one the read knew less in some directory, is
left unmanaged as a whole, since a header cannot name a directory, with
reason="not-inspected" provenance="observed". An object whose own knowledge
is not complete gets an entry that names it. A facet is known where its table
is, so a table the export declares carries its facets as the read holds them,
and a declared table whose facet knowledge is not complete refuses the export.
Knowledge left unmanaged by a source's own entry is passed on with
provenance="declared". YAML writes no header, and neither does a format for a
host that does not negotiate source.limits, so there such knowledge refuses
the export, as does a kind no entry names. A host that writes an HCL export's
account itself, as its captured ptah:feature-coverage header, sends the
provider no coverage for HCL, and the provider writes no entry there.
The subprocess tests exercise decoding, encoding, normalization, and representation conversion without a database address; a YQL schema declares coordination nodes through SQL schema reading.
Offline row-table declarations
The same source service decodes the table-scope settings that the host's bundled YDB source reads from YAML and from Go annotations, so a table written the way users write tables reaches the provider. The host lexes the source and sends the values of the keys, directives and attributes the provider claimed; the provider owns their meaning. The host refuses a YAML key the provider does not claim. It reads a Go comment that no provider claims as no declaration, and refuses an attribute of its own directives that no provider claims, so the provider claims the Go directives and attributes it cannot carry and refuses them by name. Nothing is dropped in silence.
A YAML table entry takes these keys, spelled as the YDB settings are:
tables:
documents:
auto_partitioning_by_size: ENABLED # ENABLED or DISABLED, in either case
auto_partitioning_partition_size_mb: 2048 # a whole number of at least 1
auto_partitioning_by_load: ENABLED
auto_partitioning_min_partitions_count: 2
auto_partitioning_max_partitions_count: 16
read_replicas_settings: ANY_AZ:2 # PER_AZ:<n> or ANY_AZ:<n>; a count of 0 removes replicas
key_bloom_filter: ENABLED
column_families:
default: {compression: lz4} # off or lz4
cold: {data: hdd, compression: lz4, fields: [body]} # data is a storage pool kind, kept as written
hot: {cache_mode: in_memory} # regular or in_memory, in either case
platform:
ydb: # the TTL, spelled as the Go platform properties without the prefix
row_deletion_column: created_at
row_deletion_interval: P30D
In YAML, a family's fields lists its column names as a sequence or a
comma-separated scalar. Each name must match a column in that table. A column
can belong to only one family, and the default family lists no columns.
The provider claims the ydb group of the table's platform key as
platform.ydb, and the host keeps no copy of the group. The provider therefore
refuses every key of the group other than row_deletion_column,
row_deletion_interval and row_deletion_unit. The host refuses a group that
holds a list or a mapping before it reaches the provider; scalar values arrive
as written.
In Go, the same settings are attributes of the host's own ptah:schema:table
directive, spelled as the YAML keys are, and a TTL is written as the platform
properties of the ydb engine. A family is a directive of its own,
//ptah:schema:columnfamily, on the table's struct, with the attributes name,
data, compression, cache_mode and fields. The table attribute names
another table of the file (name or schema.name) and wins over the struct's
own table.
fields lists the columns the family holds, as a comma-separated list of the
column names (fields="body, notes"). The provider returns one column facet for
each name, owned by the column under the table, and the host attaches it to the
field of the struct the directive is written on. A family that has fields and
a table attribute for another table is refused, because the host places only
the columns of the directive's own struct. The default family lists no column,
a column sits in one family, and a name the struct has no field for is refused by
the host.
//ptah:schema:table name="documents" auto_partitioning_by_load="ENABLED" read_replicas_settings="ANY_AZ:2" platform.ydb.row_deletion_column="created_at" platform.ydb.row_deletion_interval="P30D"
//ptah:schema:columnfamily name="default" compression="lz4"
//ptah:schema:columnfamily name="cold" data="hdd" compression="lz4" fields="body"
//ptah:schema:columnfamily name="hot" cache_mode="in_memory"
type Document struct { /* ... */ }
A family's cache mode, regular or in_memory, needs a target that takes
one: YDB 25.4 and later, or a cluster that turned EnableTableCacheModes on
(see Feature flags). A plan that declares one for any other
target is refused with the flag's name, regular included, as the host's
embedded engine refuses it.
In HCL, the TTL is the platform "ydb" block of the table, with the same
three properties as override blocks. ptah and ptah-compat read it and
schema inspect writes it:
table "events" {
schema = schema.app
platform "ydb" {
override "row_deletion_column" {
value = "created_at"
}
override "row_deletion_interval" {
value = "P30D"
}
}
column "id" {
type = Uint64
}
column "created_at" {
type = Timestamp
null = true
}
primary_key {
columns = [column.id]
}
}
The provider claims the block (source.hcl-platform, ADR 0072), so it needs a
host that reads it; a host that does not refuses the document. The host refuses
an override other than the three, naming them. The block's label may be the
alias ydbs. A table that writes no block states nothing about its TTL, and
keeps the TTL it holds, where a YAML or Go source that states no TTL asks for
none. HCL cannot say a table is a column table, so the block states the TTL of
a column table the database holds, which keeps its storage and takes the TTL as
one tier that deletes.
A TTL needs row_deletion_column and row_deletion_interval. The interval is an
ISO 8601 duration of weeks, days, hours, minutes and seconds (P30D,
P2W3D, PT1H30M), and YDB keeps whole seconds, so a fraction of a second,
months and years are refused. A column that counts since the Unix epoch also
needs row_deletion_unit, one of SECONDS, MILLISECONDS, MICROSECONDS and
NANOSECONDS in any case. Which column types fit the unit is checked when the
plan is made, where the table's columns are known. A refused interval or unit
says why in the host's output, in the words of the host's embedded engine
without the value the source wrote, which a provider's reason never quotes:
table "items": platform.ydb.row_deletion_interval: the interval has a fraction of a second, and YDB keeps whole seconds only (PT1.5S reads back as PT1S). A
tier of a column table's TTL is refused the same way.
All the keys, attributes and directives of one table become one desired
row-table facet, because a document may hold only one facet of a kind for a
table. The facet holds only what the source states. It has no primary key, which
comes from the table's common columns; a stated key must still be distinct and
ordered. A partitioning setting a source leaves out keeps what the table holds.
A table that declares nothing, such as an empty column_families mapping or
store="ROW" alone, gets no facet.
The values are read as the text the author wrote, so 3 and "3" are the
same count, and a boolean where ENABLED is due is refused. The shared
frontend normalizes the spelling of a YAML number before the provider sees it
(1_000 arrives as 1000, 0x10 as 16); the bundled decoder reads the
written text and refuses both.
A starting layout is the partitions a new table is created with, and YDB takes
it only when it creates the table. uniform_partitions takes a count of at
least 1, and partition_at_keys takes split points in the host's grammar: a
comma separated list whose items are a value, for a split on the first key
column, or a parenthesized list of values for the leading key columns, such as
10, 20 or (10, 'a'), (20). A value is a bare run of characters or a string in
single or double quotes with backslash escapes. Both keys are claimed in YAML
and in the Go table directive, and a table that names both is refused where it
is decoded; the host accepts the pair when it decodes it and refuses the table
when it plans it. The key column types are checked when the table is created
(see "Table creation"). The corpus holds the inputs and the host's answers.
Not read yet, and listed so that nobody mistakes the silence for support:
| Setting | Why |
|---|---|
fields with a table attribute for another table in Go |
The host places the columns of the directive's own struct only. Claimed and refused by name. |
| Local, full-text and vector index options on an index of another type | A host that lists source.index-kinds sends an index of a vector, full-text or local type whole (see Index declarations of the provider's types). On an index of any other type, and on every index for a host without the feature, these attributes of ptah:schema:index and the same keys of a YAML index entry are claimed and refused by name. A global index's partitioning and read replicas are read (see Index settings from a source). |
A table is managed for its TTL when its source states table properties for it,
or when the host reports complete knowledge of the table properties of the
source (the knowledge a Go or YAML source carries when its format describes
them). A comparison that finds a TTL on such a table and none declared reports a
change, and the planner removes the TTL, as the bundled source does: measured
with the host at the commit named in the corpus, schema apply over a Go file
with no TTL plans ALTER TABLE ... RESET (TTL) for every table of the file. A
source that could not describe the properties, or a table whose current
properties were not read, leaves the TTL alone.
Column tables
A column table is declared with store="COLUMN" in the Go table directive (in
either case; an export writes store="column", as the host's embedded engine
did), or
with the column_store mapping in YAML. The Go attributes partition_by_hash
(a comma separated list of key columns), column_shards (a positive 32-bit
count) and column_ttl, and the YAML keys hash_columns, partitions and
ttl, state the hash key, the shard count and a tiered TTL. A tiered TTL names
a column, an optional epoch unit and ordered tiers; only the last tier may
delete rows, and every other one moves them to an absolute external data source
path. A TTL that only deletes is written as the row TTL
(platform.ydb.row_deletion_* in Go, the row_deletion_* keys of the YAML ydb
group) and becomes the column table's one deleting tier. The declaration
becomes one column-storage facet, and what it leaves out stays the server's
choice. store="ROW" declares nothing.
The corpus holds each spelling and each refusal with the host's answer
(*-store-column-* rows). The host's bundled decoders also accept a column
table beside row-table settings (partitioning, read replicas, a key bloom
filter, a starting layout, column families) and beside both a tiered TTL and a
row TTL, and its renderer refuses the table when it plans it. The provider
refuses such a declaration where it is decoded, with the renderer's words:
row-table partitioning, column families and changefeeds cannot be applied to a column table, and declare either tiered TTL or a TTL that deletes rows, not both. It is stricter in one place: the host reads a fractional YAML
partitions as its whole part, and the provider refuses it.
Inspection reads a column table, a COLUMN_TABLE directory entry, from the
table service and from the monitoring endpoint's scheme description
(/viewer/json/describe). The table service's description carries the
columns, the key, the hash key (partitioning_settings.partition_by) and the
TTL, and leaves out the shard count and the local indexes (measured on 25.1.4.7
and 26.2.1.14 for a table created with AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 3); the monitoring page carries both. The two are read together and a
disagreement refuses the inspection. The table service writes a tiered TTL in
a field the pinned ydb-go-genproto lacks, so the provider reads a column
table's description with definitions of its own (ColumnTableDescribeResult,
adapted from upstream with its wire numbers). The table reads back as a common
table with one column-storage facet (hash key, shard count, data channel
count, local indexes and the TTL with its tiers, a TTL that only deletes
included) and a column facet for each column; it has no row-table, index or
changefeed facet. Its access metadata is not observed. A column table with a
column family, a column default, attributes or row-table settings is refused.
The hash function has to be the one YQL's PARTITION BY HASH writes,
HASH_FUNCTION_CONSISTENCY_64: the native CreateTable call writes
HASH_FUNCTION_MODULO_N instead, and the host's embedded engine reads no other.
Without the monitoring parameter in the address, an inspection that meets a
column table is refused with column table <path> requires monitoring=http(s)://host:port in the YDB address, and the column table is
never read as a row table. The SDK client passes the host the status code of a
refused call and not its message, so the host reports provider inspect failed (FailedPrecondition).
Creation. The planner creates a declared column table with one YQL CREATE TABLE ... PARTITION BY HASH (...) WITH (STORE = COLUMN, ...) through the query
service, once, outside a transaction, with the shard count as
AUTO_PARTITIONING_MIN_PARTITIONS_COUNT and the TTL as YQL writes it. YQL is
used rather than the native CreateTable call because the native call writes
HASH_FUNCTION_MODULO_N, which the host's embedded engine does not read. Every
key column is written NOT NULL, as the host writes it: YDB refuses a nullable
key column in a column table, and a Go field marked primary is nullable unless
it says not_null. The table is read back from the table service and the
monitoring endpoint and must hold the declared columns and key, the declared
hash key or, without one, the whole key, the declared shard count, the declared
TTL and the declared local indexes. A creation in a session whose address names
no monitoring endpoint is refused before anything is written. A tier that moves
rows needs the tiered_ttl capability, which a session reads from
EnableTieringInColumnShard; without it the plan is refused with the reason.
Column defaults, Serial columns, column families, global indexes and a data
channel count other than the server's 64 are refused. A TTL column has to be
first in the key: the host's embedded engine also takes one later in the key
that a min_max local index reads, which 26.2.1.14 refuses with the index in
place (Haven't MAX-index for TTL column and TTL column is not first column in primary key), so the provider refuses it before a statement is sent.
Local indexes. The column-storage facet states a column table's local indexes
as its read describes them: a bloom (bloom_filter), an ngram
(bloom_ngram_filter) or a minMax (min_max) index, each over one column,
with its settings. A setting the declaration leaves out is the server's
default: a probability of 0.1, an n-gram size of 3 and case-sensitive
matching. The storage YDB places an index in is not part of a declaration, and
the comparison leaves it out, since YDB describes an index created with its
table in other storage than one added later. An index reads one column
(26.2.1.14 answers Local bloom index on column tables requires exactly one index column, where the host's embedded engine takes several for a bloom
index), an n-gram index a Utf8 one, an n-gram size is 3 to 8, and a
probability gives at most 8 n-gram hashes, as that engine requires. Each kind
needs its capability (see Feature flags); 25.1 has no local
index grammar at all (local: alternative is not implemented yet). A new
table's local indexes are written inside its CREATE TABLE, as INDEX `bf` LOCAL USING bloom_filter ON (`body`) WITH (false_positive_probability = 0.01), every setting named. A Go or YAML source declares a local index as an
index directive or entry of its type, which a host that lists
source.index-kinds sends whole (see Index declarations of the provider's
types); an HCL document declares
none, since HCL cannot state the column table it belongs to.
Export. A Go or YAML export of a column table writes its hash key, its shard
count and its TTL, and on a host that lists source.index-kinds each local
index as a declaration of its type with its settings. A source writes no data
channel count, and the 64 YDB gives every column table the provider creates is
what a declaration without one creates, so a read's count of 64 is no loss; any
other count is. The storage and the encodings YDB gives a local index are not
part of a declaration either.
Comparison and planning. The comparison ydb-row-table-properties compares the
column-storage facet of a table that exists on both sides, and reports a
github.com/stokaro/ptah-ydb-plugin/column-storage-change when the hash key,
the shard count, the TTL or the local indexes differ, or when the table is a
column table on one side and a row table on the other. A declaration that
leaves out the hash key or the shard count takes what the table holds. Under
complete coverage, a table the source states no column storage for asks for a
row table. YDB fixes a table's storage kind, hash key and shard count when it
creates the table and copies no column table into a new one, so the planner
refuses each of these changes with the host's words (changing between row and column storage requires an explicit data migration, changing a column table's hash key or shard count requires an explicit data migration, with both
values; column-table rebuilds require an explicit data migration). A column step of
a column table is refused as not planned yet.
The local indexes are managed when the declaration states that it lists every
one, as a read does and as a Go or YAML source of a host that lists
source.index-kinds does (localIndexesStated), or when it states at least
one: an index the table holds and the declaration leaves out is dropped. Any
other declaration that states none leaves the table's own alone, since its
source cannot declare one. Each change is one step in the lifecycle phase,
column-table-add-local-index/N, column-table-drop-local-index/N or
column-table-rename-local-index/N, of the operation kind
column-table-local-index-operation, running ALTER TABLE ... ADD INDEX ... LOCAL USING ..., ALTER TABLE ... DROP INDEX ... or ALTER TABLE ... RENAME INDEX ... TO ... through the query service. An index whose kind, column or
settings differ is dropped and added again under its name, the removal first.
An index whose name alone changes is renamed in place on a target with
index_rename, and dropped under its old name and added under its new one on
a target without it, as the host's embedded engine plans both. The host
compares no local index, so the provider pairs them as the host's comparison
pairs a renamed index: each new name, in name order, takes the first old name
in name order whose index has the same column, kind and settings. The session
reports index_rename from EnableMoveIndex, and 26.2.1.14 renames a local
index with the flag off too, so a plan without it rebuilds an index the server
could rename. 26.2.1.14 also changes a bloom index's probability in place
(ALTER INDEX ... SET (false_positive_probability = ...)), which neither
engine plans. Only an index a plan adds needs its capability. Execution reads
the table from the table service and the monitoring endpoint and refuses
before it writes when the table holds an index of the name to add or to rename
to, lacks its column or holds it with another type than an n-gram index reads,
or holds the index to drop or rename otherwise than the plan captured it;
after the call it requires the added or renamed index to read back as
requested under its name, the dropped or renamed one to be gone under its old
name, and every other local index, the layout and the TTL to be unchanged.
The TTL changes in place, as the host's embedded engine changes it. ALTER TABLE ... SET (TTL = ...) replaces the TTL a column table holds, whatever
either one moves rows to (measured on 25.1.4.7 and 26.2.1.14), so a held TTL
that moves no row is replaced by the SET alone (column-table-ttl/<n>/set). A
held TTL that moves rows to an external data source is removed first with
ALTER TABLE ... RESET (TTL) (column-table-ttl/<n>/reset, placed early and
ordered before the SET): YDB refuses to drop or replace a data source a TTL
names (Other entities depend on this data source), so the held TTL lets go of
its sources before their owner drops or replaces them, and the declared one is
set once they exist. Each step reads the data sources its TTL names, which
orders it against their owner. A table without a TTL takes the SET alone, and a
declaration without one the RESET alone. A lone RESET drops the table's TTL
setting and a lone SET creates it; any other step alters it, because the host
refuses a provider step that creates what another provider step drops (it runs
every creation of a phase before every drop), and the dependency orders a
pair. The declaration is held to the
rules of a creation, and a tier that moves rows needs tiered_ttl. Each step
reads the table from the table service and the monitoring endpoint, refuses
before it writes when the table holds another TTL than the plan saw or when the
TTL to set reads a column that is absent, not first in the key or of a type its
mode does not read, runs its statement once through the query service and reads
the TTL back.
internal/tablesource/testdata/corpus holds the inputs and the answers of the
host's bundled decoder for them, and the differential test in
internal/tablesource requires the provider to mean the same as the host for
every input the host accepts, to refuse every input the host refuses, and to
name every difference in the table above. testdata/oracle explains how the
answers are regenerated against a host checkout.
Coordination comparison
The offline models.compare service compares desired coordination declarations
with captured native observations. Its context must establish the
coordination_nodes capability, which session receipts advertise for the native
implementation. Omitted settings compare using server defaults;
source holder names do not change native configuration. Removing an explicit
nondefault setting produces a change back to the default. Raw stored values that
the server clamps refuse comparison.
The github.com/stokaro/ptah-ydb-plugin/coordination-node-change model carries complete before and after
operands. The native before retains access metadata. A null operand means
established absence. Normalization reconstructs impact, removal, and access
facts from these operands. Creation has unknown inherited access policy;
alteration preserves access; dropping a node removes its runtime state and access.
Planning lowers these changes to the github.com/stokaro/ptah-ydb-plugin/coordination-node-operation model,
which rendering preserves as a native execution envelope.
An unread object does not establish absence. Independent coverage sources must all establish completeness; overlapping unknown regions override complete regions, and explicit subject claims override namespace claims. Namespace components, target scopes, and exact empty paths retain their boundaries. An incomplete desired source preserves known current configuration. Every decision is made for one node, so a namespace the read left out produces no diagnostic of its own.
The host names a declared node without a database, because it lives in the session's, while inspection names that database in every owner and region it reads. The comparison reads both in the host's terms, so the declared node and the node read at its path are one node. A change names the node as the host sent it: by the declared identity, or by the read one for a removal. A comparison whose read names two databases is refused.
Coordination exports retain owner, direct grants, and effective-permission
requirements in an optional access field. Its JSON field names use snake case.
Omission leaves access unmanaged. The YAML source grammar above does not accept
access declarations. Comparison reports unsatisfied access requirements as
undecided; change/operation normalization refuses them. Existing nodes can
change configuration while satisfying unchanged access requirements. Creation
with explicit access requirements is refused because the coordination service
cannot promise inherited permissions or apply grants.
The comparison refuses contradictory identities, forged normalization receipts,
and collisions with declared relations. Required nodes are not removed.
Exclusions for this model match exact relative paths such as app/workers,
without case folding or trimming. Inspection reserves the database-root
ptah_locks node (see the reservations above), and the comparison leaves that
path out too: it plans no removal and reports nothing about it. A declaration
at that path is the one case it reports, as undecided; YAML and Go declarations
and change normalization reject it.
This changes the desired model definition; pre-v1, no compatibility with the
earlier experimental definition is owed.
Offline subprocess tests cover decode, compare, and change normalization without
opening a session. Live tests compare native observations after closing their
session on both qualified YDB lines. TestHostAppliesACoordinationNode_HappyPath
drives ptah schema apply of the current host through this provider from a
YAML, a Go and a YQL source: it creates a node, finds nothing to change on the
next run, changes its settings in place, and removes it once the source no
longer declares it, reading the node back after each run.
Comparison counts
The provider offers models.compare-counts, so ptah schema drift --format json
lists its native kinds in the report's inventory. Each object comparison counts,
under its change kind, the objects it decides: an object both sides hold counts
on both, whether or not it differs and also when the plan drops and creates it
again, an object only the source declares counts as desired, and an object only
the server holds counts as observed. An object the comparison excludes, reserves,
keeps because the source does not describe it, or leaves undecided counts on
neither side.
The counted kinds are secrets, streaming queries, coordination nodes, topics, external data sources, external tables, resource pools, resource pool classifiers, async replications and transfers. A service counts every kind it owns, with zeros for a kind a batch holds nothing of, because the host reports a kind only when every service that ran counted it.
The row-table facet comparison counts nothing, so its two change kinds have no inventory entry. A table's TTL and partitioning are settings that stand for no object. A table's changefeeds change as one set, with one change for the table, and the contract refuses more additions and removals than a kind has changes, so a set that gains two changefeeds cannot count them.
A host that does not list the feature receives no counts and reports none of these kinds, never a zero.
Offline column declarations
The values.convert service converts column properties between distinct typed
observed and desired models. It preserves family assignment, typed literal
defaults, sequence configuration, compression settings, and encoding order.
Identity, storage type, and nullability remain in the common column contract.
An omitted default requests the native no-default setting and stays omitted
during normalization; dropDefault: true expresses removal explicitly.
Unknown fields, unknown enums, duplicate keys, null settings, and overlapping
defaults are refused. Explicit sequence configuration retains optional values,
including cycle: false.
Sequence positions are mutable data. An observation containing setVal refuses
conversion rather than exporting a request to reset the sequence. Conversion
validates the source payload and its captured normalization, retains exact owner
identity and ordered records, and returns no prefix on failure or cancellation.
It opens no connection and establishes no inspection coverage.
The column-property models complement the common column service and preserve native settings through conversion and JSON schema export.
Offline row-table declarations
The row-table model preserves ordered primary keys, TTL, storage pools, column families, attributes, partitioning policy, bloom-filter settings, read replicas, tiering, and temporary lifetime. Unknown settings, duplicate keys or families, invalid TTL modes, and null settings are refused. Attribute values remain exact, including empty strings. The row-table reader interprets an unspecified native storage kind as row storage; column storage retains its separate model.
Access declarations keep the owner and direct grants separate from requirements on effective permissions. Effective permissions include inherited access under YDB's permission model. A present access declaration specifies the complete direct-grant set and the required effective-permission set. These requirements do not add direct grants. Conversion neither infers an inheritance switch nor changes permissions. The provider does not yet advertise table or access comparison/planning.
Physical shard boundaries are runtime topology. A captured value containing them
refuses conversion rather than turning the current split points into creation
settings. A starting layout (uniformPartitions, or partitionAtKeys as lists
of source text values) is a creation setting of the desired model only: a read
finds none, and converting a desired table to the observed representation leaves
it out. Partitioning policy remains declarative. Statistics and transient
object identity cannot enter the property model. Conversion preserves a complete
mixed table/column batch, works after the session closes, and establishes no
inspection coverage.
JSON schema export includes table and column facets together with native objects and their captured coverage. Unsupported input refuses the whole conversion.
Offline index and stream declarations
The values.convert service also converts index, changefeed, and standalone
topic properties between separate typed desired and observed models. It works
without an open session, retains exact owner identity, and returns no prefix
when any value in a mixed batch fails. Conversion establishes no inspection
coverage. The index model advertises comparison and planning, for adding and
dropping global secondary indexes of an existing row table (see
Index alteration), and so do the topic model (see
Topics) and the changefeed model (see Changefeeds).
Index declarations retain the native index kind, ordered index/data columns, and kind-specific settings. Each declaration includes complete requirements on the server-owned implementation tables: ordered columns and keys, column defaults, storage, partitioning policy, and access constraints. These are requirements on the index implementation, not independent tables to create. Missing implementation tables or columns refuse conversion. Physical shard boundaries also refuse conversion; current splits are not creation settings. The observed index model canonicalizes unspecified implementation storage as row storage and excludes internal sequence positions. This changes its definition digest; this provider is pre-v1 and carries no reader for the previous shape.
Topic declarations preserve partitioning policy, retention, codecs, write/read quotas, attributes, metrics settings, and consumer configuration. Consumer settings retain read timestamps, availability periods, codec preferences, attributes, and streaming-consumer presence. Access uses the same direct-grant and effective-permission requirements as row tables. Runtime partition topology and statistics cannot enter a declaration.
Changefeed declarations include the child topic's settings as well as mode,
format, virtual/resolved timestamps, attributes, AWS region, schema-change
reporting, and user SIDs. disabled and initialScan are independent settings.
An observation in STATE_INITIAL_SCAN converts to enabled intent with
initialScan: true. Converting that declaration back predicts
STATE_ENABLED; it does not prove that a server finished scanning. Scan
progress counters remain excluded.
Unknown fields/enums, runtime state in declarations, null settings, duplicate names, and incomplete child descriptions are refused. Empty index/changefeed sets keep their own desired model definitions. Indexes, implementation tables and changefeeds normalize by name; consumers keep the order the declaration lists them in, and column and key order is retained.
Offline native-object declarations
View declarations retain query text and table-service attributes, including comments. The view and table APIs must agree about access metadata. Conflicting access or additional table settings refuse conversion rather than creating competing declarations for one view. The table API returns disabled partitioning switches for views on both tested YDB lines. This exact placeholder is excluded; other partition settings are refused.
External-source declarations preserve source type, location, and exact native property keys and values, including authentication references. External-table declarations preserve the data-source path, location, content options, ordered column names and types, nullability, and typed column settings. Conversion does not contact an external service or resolve a secret.
Coordination conversion retains optional timing and mode settings together with
access requirements. Native unspecified settings remain omitted. A desired
struct_name cannot convert to an observation because it has no server
counterpart. A runtime-reserved coordination identity is not exportable as a
user declaration.
Column-storage declarations retain hash-column order, partition and data-channel counts, local-index settings, and TTL tiers. Optional false values, disabled TTL, and omitted TTL stay distinct. The same validation applies to both representations; native IDs and shard addresses have no declaration field.
The native models use distinct desired definitions. This changes experimental definition digests and the coordination change/operation contracts. This provider is pre-v1 and has no reader for the previous shapes.
The live CI jobs build a pinned Ptah host in a separate checkout and run
schema inspect --format json with an explicitly trusted provider manifest.
They compare every exported model with SDK conversion and check that coverage
keeps every inspected model family. These tests exercise row tables, columns,
views, external objects, and coordination nodes. Local runs enable the same
checks with PTAH_HOST_TEST_BIN pointing to that host binary.
Offline column-storage observations
The column-storage decoder accepts captured monitoring JSON together with an independent table-service description. It checks table identity, ordered columns and primary keys, primitive types, nullability, and reported hash keys. It preserves hash-column order, partition and data-channel counts, local bloom/ngram/min-max indexes, index storage settings, and TTL tiers. Omitted index options remain absent. Explicit false values remain explicit.
Unknown schema fields, repeated fields or aliases, inconsistent descriptions, unsupported type extensions, and configured column families are refused. Native IDs, schema versions, and shard addresses are excluded from the model. Local indexes are sorted by name; hash columns and TTL tiers retain order.
Fixture replay covers YDB 25.1 and 26.2 captures: monitoring pages copied from
the host, and pairs of a monitoring page and a table-service description read
from one live table each (internal/columnstore/testdata/README.md). A
subprocess test checks SDK normalization and conversion without opening a
database session, and live tests inspect column tables on both lines.
Native protocol generation
The upstream Go bindings lack some native index, sequence, changefeed, consumer
and column family fields, such as a family's cache mode. The provider's typed
definitions preserve their native wire numbers; attribution and the pinned
upstream source are in third_party/ydb/NOTICE.md. Unsupported fields remain
errors, a family's compression level among them. A table service request is
built with the pinned bindings and carries the fields they lack as unknown
fields, the only ones the provider accepts: a family's cache mode as field 6
of the family, and a new table's vector or full-text index as field 7, 8 or 9
of its index, the upstream arm. Runtime statistics are omitted from schema
observations.
With protoc 35.1 on PATH, regenerate with:
sh scripts/generate-native-schema.sh
The Go generator is pinned in go.mod. CI regenerates these definitions and
requires unchanged generated files.
Native coordination planning and execution
The offline planner preserves complete before and after operands. Each step
has an object effect, a shared ydb.scheme-path effect, and a forbidden SQL
transaction requirement. The path effect names no database, as the host names
a path of the session's database. Table-to-node and node-to-table replacements
require an explicit common path handoff. A view the host creates or drops
occupies its path as a table does: a node beside it needs no order, and a node
that takes the path of a dropped view comes after the drop. A grant or a revoke
on a table or a view, and a comment, change a resource of the object's own and
occupy no path; the step's read of the object carries the path. YDB needs every
path above an object to be a directory, so a node created below a path follows
the step that drops the object there, and a node dropped at a path precedes a
step that creates an object below it. That holds whichever planning service
plans the other step: a drop records a ydb.scheme-directory creation at its
path, a creation reads each directory above its own, and the host runs a
creation before another service's read. A drop below a path where the plan
creates an object is refused before anything runs, since YDB keeps a directory
after its last object goes and YQL has no statement that removes one. The
refusal is the host's embedded engine's, this plan drops app/holder/events and creates an object at app/holder, which is a directory in the database, also
where two planning services plan the two objects, and where both are tables. A
service sees only its own changes, so the pair of two services is refused in
the streaming query service's call, which the host asks on every plan with the
desired and current objects of every kind and the subject of every change. Any
other overlap of parent and descendant paths, and an unsupported common
footprint, is refused. The host's window markers (such as common/feature-creations) and the
column of a column step occupy no path, so they order nothing; the host places
the step between its markers. A common step that names another database than
the change is refused. A parent directory that does not exist holds no node,
and CreateNode creates it with the node; execution never deletes a directory.
Rendering validates the complete operation receipt and emits a native envelope, or, in the TEXT form, the coordination node statement the host's embedded engine writes (see Text units). Execution uses native CreateNode, AlterNode, and DropNode requests. A nonempty database identity must equal the selected session database. The provider validates every envelope and transaction constraint before executing any unit in the batch.
Before each native write, execution lists the parent directory, or proves that it does not exist, and reads the node. The current configuration and access metadata must match the captured observation, or creation must have established absence. A stale operation is refused before dispatch. Effective settings are sent explicitly, including omitted settings that reset to defaults. Alteration preserves the existing node. After a successful reply, execution reads back the configuration or confirms absence; alteration also checks that access metadata did not change.
A refusal before dispatch reports NOT_STARTED, or PARTIAL after an applied prefix. A failed or unconfirmed dispatched request reports UNKNOWN and is never retried. Native operations cannot join SQL transactions. Lease checks run before dispatch and guard the call, as they do for SQL execution.
These checks are not an atomic compare-and-set. Another actor can change a node between validation and dispatch; the native API has no conditional schema-write contract. Successful creation does not establish exclusive ownership. The SDK subprocess tests cover create, alter, reset, drop, stale plans, mixed SQL/native batch refusal, and lease loss on both tested YDB release lines. The host routes a coordination node's comparison, planning and execution to these services (see Coordination comparison); other object lifecycles remain pending.
Topics
The provider declares, compares, plans and applies standalone topics and their
consumers, with the syntax, the rules and the statements of the host's embedded
YDB engine (dialect/ydb/ydbtopic).
A YAML document declares topics under the document key topics, a Go file with
the directives ptah:schema:topic and ptah:schema:topic:consumer, and a YQL
schema with CREATE TOPIC and ALTER TOPIC ... ADD CONSUMER:
topics:
order_events:
schema: app
min_active_partitions: 2
retention_period: PT36H
supported_codecs: [raw, gzip]
consumers:
billing:
important: true
audit:
read_from: "2026-01-01T00:00:00Z"
//ptah:schema:topic name="order_events" schema="app" min_active_partitions="2" retention_period="PT36H" supported_codecs="raw,gzip"
//ptah:schema:topic:consumer name="billing" topic="order_events" schema="app" important="true"
//ptah:schema:topic:consumer name="audit" topic="order_events" schema="app" read_from="2026-01-01T00:00:00Z"
type Events struct{}
The settings are the host's: min_active_partitions, max_active_partitions,
auto_partitioning_strategy, the up and down utilization percentages and the
stabilization window, retention_period, partition_write_speed_bytes_per_second,
partition_write_burst_bytes and supported_codecs; a consumer takes
important, read_from, supported_codecs and availability_period. Each is
parsed and refused with the host's words. A Go consumer names a topic the same
file declares. Each source describes the topics of the directories it reads
completely, so a topic it leaves out is dropped; a ptah:not-described topic
header entry of a YQL document keeps one. HCL has no topic block. The model
keeps the consumers of a declared topic in the order the declaration lists
them, and those of a topic it reads in the order YDB describes them, which is
the order they were added in, so a statement Ptah writes for a topic lists
them as Ptah's built-in YDB engine did.
A setting a declaration leaves out takes the value YDB gives a new topic, and
the comparison reads both sides that way: one partition, a day of retention, a
write speed of 1 MiB/s and a burst of the speed, and with auto-partitioning on,
the maximum at the minimum, 90% and 30% thresholds and a five-minute window.
Consumers compare by name. YDB's own attributes and the access metadata are not
compared. A topic the read finds holding a setting no statement sets
(retention_storage_mb, partition_count_limit, a metering mode, the read
speeds or a metrics level), or a consumer whose codec list names a codec no
declaration names, refuses the comparison as the host refuses to read it,
whatever the source says of the topic: YDB topic <path>: <why>, the path
from the database root.
The planner writes one step per topic, outside a transaction. It refuses a
change YDB cannot make in place: fewer partitions than the topic has, and
switching auto-partitioning off once it is on (declare paused instead). It
refuses a consumer's availability_period on a server before 25.4, where the
session reports topic_consumer_availability_period false. The operation runs
the host's statements: one CREATE TOPIC naming what the declaration names, one
DROP TOPIC, or one ALTER TOPIC that names every setting at its resolved
value once any differs, drops, alters and adds consumers, and a second ALTER TOPIC that adds again a consumer whose codec list the declaration empties,
which YDB changes only by dropping the consumer. A drop of a topic or of a
consumer is destructive.
Execution reads the topic before the first statement and refuses one that changed since the plan read it, or a path that holds something else; it runs each statement once and reads the topic back, requiring the declared settings and consumers, the access metadata an alteration found, or the topic's absence after a drop. An unconfirmed result is UNKNOWN and is never retried.
TestTopicLifecycleSubprocess creates a topic, restarts a consumer, refuses a
change planned before another writer changed the topic, and drops it, reading
the topic back from the server after each step. TestHostAppliesATopic_HappyPath
drives ptah schema apply from a YAML, a Go and a YQL source: it creates a
topic with its consumers, finds nothing to change, changes the retention and
the consumers, finds nothing to change again, and drops the topic.
Streaming queries
The provider reads, compares, plans and applies streaming queries from YAML,
Go and SQL sources. A session enables streaming_queries only when the target
reports both EnableStreamingQueries and EnableExternalDataSources through
its monitoring endpoint. A target without that evidence refuses a streaming
operation. A listed query whose system view cannot establish every setting is
uninspected, so an incomplete reading cannot authorize its removal.
streaming_queries:
copy_events:
schema: app
text: INSERT INTO `app/sink` SELECT * FROM `app/source`;
run: true
resource_pool: default
allow_state_reset: false
The Go directive is ptah:schema:streamingquery, with the same attributes and
an explicit name. SQL accepts CREATE STREAMING QUERY ... DO BEGIN ... END DO, including IF NOT EXISTS and OR REPLACE. OR REPLACE grants the same
state-reset permission as allow_state_reset: true. HCL has no streaming
query block. A complete source drops a query it omits; the SQL header
ptah:not-described streaming_query preserves queries outside its coverage.
An omitted run means true and an omitted resource pool means default.
Comparison ignores query whitespace and comments but preserves literal values.
A changed body requires explicit state-reset permission because it discards
checkpoints. Stopping or changing the resource pool keeps topic offsets. The
planner stops a running query before changing its inputs and restarts it after
its new inputs exist; a change that only stops the query emits one statement.
A creation, a change or a restart runs after the host's common steps, and a
grant on the query after it, as the YDB engine built into the host at
74de0b65b placed them: measured on 26.2.1.14, a query between two topics beside
a group, a table and grants on the table, a third topic and the query plans
the topics, the group, the table, the grants on the table and the topic, the
query and then the grant on it. A principal change after a grant on the query
waits for the grant, so the query runs ahead of that change. A stop and a drop
run early.
This assessment also runs when the query declaration is unchanged. Captured
query values require planning.object-context (contract 25.7), and each query
receives an assessment receipt. A changed input restarts a running query only
when the desired capture establishes every surviving input and resource pool.
A resource pool altered in place is no changed input: YDB runs ALTER RESOURCE POOL beside a query running in the pool and the query keeps running, measured
on 26.2.1.14, so the plan alters the pool alone, as the host's embedded engine
did. A plan that drops the pool a running query names is refused.
Missing definitions, conflicting occupants, and foreign database inputs refuse
the complete plan. An unscoped inspection walks every directory for streaming
queries, including dot-prefixed user directories. A failed directory listing
refuses the read; an unread query keeps explicit unknown coverage. Scoped
inspection and reads that exclude other dev realms cannot establish the full
dependent set, so input mutations refuse when that inventory is incomplete.
A disabled query reader does not prove absence: directory listings do, and a
listed unread query remains unknown. Stopped queries retain their stopped
state. Native and text rendering carry the same operations and preconditions.
Input dependencies use physical scheme paths, including absolute paths inside
the selected database or dev realm. A constant PRAGMA TablePathPrefix applies
to subsequent relative paths. A foreign path or an unresolved prefix refuses
planning. Constant named paths, named subqueries, AS_TABLE, static CONCAT
and EACH inputs retain their dependencies. A qualified external reference
retains the local external data source as its dependency. Unresolved named
paths, USE, and dynamic table functions such as RANGE refuse planning:
their complete physical inputs cannot be proven from the captured body.
Ordinary strings outside a table expression create no path dependency.
A named resource pool remains a database-wide dependency, including
a literal dot in its name, and a query that names none depends on the pool
default. Scope selection keeps that pool when it keeps the query, unless
the pool is explicitly excluded, so an --include of a query plans the change
of the pool it runs in, as the host's embedded engine did. Access changes follow a query's
stop and restart; a final drop accounts for revoking that query's access.
Execution verifies the stored query settings before dispatch, checks cancellation and held locks before each statement, and reads back the result. This precondition contains no ACL or principal state; access ordering remains explicit in the complete plan. Each statement runs once. A dispatched operation whose result cannot be confirmed is UNKNOWN. The dev-realm guard checks the query body as well as the query's own path.
TestHostStreamingQueryGraph exercises the combined host graph on YDB 26.2:
it stops a query, removes its old source, replaces a table with the new source
topic at the same path, restarts the query, verifies a synced second apply,
and stops it without changing its body.
Async replications and transfers
The provider reads, declares, compares, plans and applies YDB async
replications and transfers, with the rules and the statements of the host's
embedded YDB engine (dialect/ydb/ydbreplication). The domain rules live in
internal/replication.
A YAML document declares them under the document keys async_replications and
transfers, and a Go file with the directives ptah:schema:async_replication,
ptah:schema:async_replication:item and ptah:schema:transfer, with the
host's spelling. In YAML a name key overrides the entry's key; slashes around
schema are dropped:
async_replications:
mirror:
schema: replicas
connection_string: grpcs://primary:2135/?database=/prod
user: replicator
password_secret_name: password
consistency_level: global
commit_interval: PT30S
items:
- source: accounts
target: replica/accounts
transfers:
ingest:
source: orders/feed
target: order_log
using: "($m) -> { return [<| id: $m._offset |>]; }"
flush_interval: PT10S
//ptah:schema:async_replication name="mirror" schema="replicas" connection_string="grpcs://primary:2135/?database=/prod" user="replicator" password_secret_name="password"
//ptah:schema:async_replication:item replication="mirror" schema="replicas" source="accounts" target="replica/accounts"
//ptah:schema:transfer name="ingest" source="orders/feed" target="order_log" using="($m) -> { return [<| id: $m._offset |>]; }"
type Replication struct{}
A replication takes connection_string, one credential (token_secret_name,
token_secret_path, or user with password_secret_name or
password_secret_path), consistency_level, commit_interval and at least one
item. A transfer takes source, target, using, the connection settings,
consumer, batch_size_bytes and flush_interval. Each is parsed and refused
with the host's words. A Go item joins the replication of its name and
directory in the same file. A YQL schema declares both through SQL schema
reading. An HCL file declares neither yet.
Inspection describes each REPLICATION and TRANSFER entry through the
replication service: the connection string rebuilt from its parts, the
credential by the secret it names, the items with their sources relative to the
source database and their targets relative to the database root, the
consistency and batch settings where they differ from YDB's defaults, a
transfer's lambda as written, and the state (running, paused, done or error).
local-ydb leaves the replication API out of its gRPC services unless
YDB_GRPC_SERVICES=replication names it; on such a cluster, and for a
description the provider cannot model, the object is reported uninspected with
the reason, and the rest of the snapshot is read. A table an async replication
writes (attribute __async_replica) is left out of the tables: YDB refuses
every write and ALTER on it, and a plan that read it as an ordinary table would
drop or alter it.
A replica no replication writes any more, left behind by a replication dropped
without CASCADE, stays read-only for good, and YDB takes no table at its
path. Where the host lists schema.unread-tables, the read names each such
table in unread_tables with the reason the host's embedded engine gives, so
the host refuses a schema that declares a table there before any step and
prints a note on db read and schema inspect. A replica that a replication
of the read writes is not named; the planner refuses a table declared there. A
filtered read lists the database's replications to tell the two apart when its
directories hold a replica, and a replication left uninspected makes the
reason say only that the read cannot describe the replication that writes it.
Without the feature, such a table is left out silently, and a declared table
there is refused when its creation runs (NOT_STARTED). Either way the replica
stays until an operator drops it by hand with DROP TABLE, which YDB accepts;
a schema that leaves it out plans nothing for it.
The comparison resolves both sides as YDB keeps them: the connection string in
its canonical form, the consistency level row and a global commit interval of
ten seconds where none is named, the batch size 8 MiB and the flush interval a
minute, a transfer's consumer as any where the declaration names none, and a
replication's items by the tables they replicate. An object the desired source
does not describe is kept; a replication YDB reports with no item, one that
failed before it resolved its tables, has no declaration that keeps it and is
left undecided. Session capabilities: async_replication on every line
(EnableReplication on 26.2), transfers from 25.2 (EnableTopicTransfer),
and replication_secret_paths from 25.4.
The planner writes one step per object, outside a transaction, and refuses what
the host refuses: a change of a replication's items, consistency level or
commit interval, or of a transfer's source, target or consumer, which YDB makes
in no object in place; a connection or credential change of an object that is
not paused, or of a replication that was failed over; a credential taken away;
a secret path before 25.4. It refuses a drop of a running replication whose
replica the plan declares as a table, a creation where a table stands at a
target, and a creation whose target lies at, above or below the target of
another replication the plan keeps or creates: YDB creates the replication over
the other's replica and both write it, and a later drop of either with
CASCADE drops the replica while the other runs on without it, measured on
25.1.4.7 and 26.2.1.14. Where the host negotiates planning.object-context, the planner also
assesses every replication the plan keeps, and refuses a table the plan
creates at the path of a live replica, and a failed-over replica the plan drops
while the schema keeps the replication. A creation names its replica paths as created and a drop of a running
replication with CASCADE names them as dropped, so each step is ordered
against the common steps at those paths, and a creation comes before a view
the plan creates over one of its replicas. A replication of this database also
reads its source tables, so it is created after a table the plan creates and
dropped before one the plan drops: a replication created before its source
creates no replica. A transfer reads its table and, at a local source, the
topic or the changefeed of a table. It names the changefeed as the changefeed
planner does, so the host creates it after a changefeed the plan adds; a
dropped transfer names the topic by the topic's identity rather than its path,
so the drop comes before the topic's even where the plan creates another object
at that path. A creation or a change of either object reads the secret its
connection names by path, so the plan creates the secret first,
and a source that drops the secret while it declares the object is refused. A
drop of a failed-over replication runs without CASCADE and keeps its tables. The provider never pauses, resumes or fails over an object.
A grant names a replication or a transfer by its directory and name
(on_async_replication, on_transfer), and GRANT and REVOKE name it by its
path. The planner puts a GRANT or a REVOKE after the creation or the change of
its object, and a drop replaces the REVOKE the host planned, since YDB drops an
object's access list with it and a REVOKE after the drop would name a path that
is gone. A change in place, such as a new lambda, keeps the entries.
Execution reads the object before the statement and refuses one that changed
since the plan read it, runs the statement once, and reads the object back.
YDB creates a replication's replica tables some time after the statement
returns, and refuses a view over a table that is not there yet. So a creation
whose replica a later step of the plan reads, such as a view over it, then
waits until each of its replica tables in this database exists, at most two
minutes. A table still missing then stops the run after the replication, with
the diagnostic ydb.replica-tables-not-created. A replication nothing in the
plan reads goes on at once. The
statement names the object, the replica targets, the transfer's table and a
secret path absolutely; a transfer's topic keeps its relative spelling, because
25.1.4.7 cannot find a topic of its own database named absolutely (Discovery error: ... SCHEME_ERROR). The TEXT form writes the host's statements, with
paths relative to the database root. A dev realm refuses both objects, which
reach past it.
On 25.1.4.7 with EnableTopicTransfer on, dropping a table a transfer wrote
into, after the transfer is gone, stops the server on a schemeshard VERIFY
(AliveChildrenCount >= 1); 26.2.1.14 drops it. The provider's CI therefore
keeps the flag at its default on 25.1 and checks the refusal there.
A consumer a transfer reads through is the transfer's. YDB makes one with a
generated name for a transfer that names none, and a declaration of the
changefeed or the topic cannot name it. The read marks each consumer a
transfer of the same read reads through (Consumer.transfer in the topic
description, which YDB never sets), and the comparison of the topic or the
changefeed keeps a marked consumer the declaration leaves out, as the host's
embedded engine adopts it: the run after a transfer's creation plans nothing,
and a change of the topic or the changefeed keeps the consumer. A topic
operation neither checks nor confirms a marked consumer, since the transfer's
drop, which the plan may run first, takes the one YDB made. A transfer read
outside the selected directories marks nothing.
A declared transfer whose table or topic the plan would not leave is refused
before anything is planned, in the host's embedded engine's words. The table
has to be declared, where the host lists every declared table among a
comparison's declared relations (models.declared-relations, contract 25.32):
YDB creates no transfer into a missing table (Path does not exist). A topic of
the transfer's own database has to be declared as a topic, where the host also
sends the transfer comparison the declared topics (models.declared-objects,
contract 25.33, ADR 0109 in Ptah), or be a changefeed of a declared table: YDB
accepts a transfer of a missing topic and then stops it (Discovery error). A
table's changefeeds are a facet the row-table comparison owns; a host that
sends the transfer comparison the declared changefeeds of the tables
(models.declared-facets, contract 25.37, ADR 0119 in Ptah) has a stream of a
declared table accepted only when it is one of the table's declared
changefeeds, as the host's embedded engine did. A host that does not send them
leaves the comparison to take any stream of a declared table for a changefeed.
A target without transfers refuses the transfer by the capability first, as the
embedded engine did.
Not modeled yet: HCL blocks.
TestReplicationLifecycleSubprocess creates a replication of the server's own
table, finds nothing to change, refuses a connection change while it runs,
makes it once the replication is paused, and drops it with its replica.
TestReplicationFailedOverSubprocess drops a failed-over replication and writes
to the table it kept. TestTransferLifecycleSubprocess creates a transfer from a
changefeed into a table, moves a row through it, changes its lambda and drops
it, and TestTransferRefusedWithoutTheCapabilitySubprocess checks the refusal on
a line without transfers. TestHostAppliesAnAsyncReplication_HappyPath and
TestHostAppliesATransfer_HappyPath drive ptah schema apply from YAML through
the same steps. TestHostGrantsOnAnAsyncReplication_HappyPath and
TestHostGrantsOnATransfer_HappyPath grant a user a permission in the plan that
creates the object, keep it through a change in place and drop the object
without a REVOKE. Each needs a server whose gRPC services include replication.
Resource pools and classifiers
The provider declares, compares, plans and applies resource pools and resource
pool classifiers, with the syntax, the rules and the statements of the host's
embedded YDB engine (dialect/ydb/ydbworkload). Both belong to the whole
database rather than to a directory, and YDB keeps them behind the
EnableResourcePools flag, off by default on every line: a session reports
resource_pools from the cluster's flags, so the address must name the
monitoring endpoint (see Feature flags).
A YAML document declares them under the document keys resource_pools and
resource_pool_classifiers, a Go file with the directives
ptah:schema:resourcepool and ptah:schema:resourcepool:classifier, and a YQL
schema with CREATE RESOURCE POOL, CREATE RESOURCE POOL CLASSIFIER and, for
the pool YDB creates with the database, ALTER RESOURCE POOL default SET:
resource_pools:
reporting:
concurrent_query_limit: 10
queue_size: 20
query_memory_limit_percent_per_node: 25.5
default:
resource_weight: 30
resource_pool_classifiers:
reporters:
resource_pool: reporting
member_name: analysts
rank: 100
//ptah:schema:resourcepool name="reporting" concurrent_query_limit="10" queue_size="20" query_memory_limit_percent_per_node="25.5"
//ptah:schema:resourcepool:classifier name="reporters" resource_pool="reporting" member_name="analysts" rank="100"
type Pools struct{}
A pool takes concurrent_query_limit and queue_size as whole numbers from 0,
and database_load_cpu_threshold, the three *_percent_per_node limits and
resource_weight as percentages from 0 to 100; a setting left out has no
limit, and zero is a limit. A queue needs a query limit or a load threshold
beside it, and the pool default takes neither of those. A classifier names
its pool and its rank, and its member when it matches one user or group.
Planning declarations without a read of the database, as schema render does,
refuses a classifier whose pool the declarations neither hold nor name
default: YDB takes such a classifier and runs the member's queries in
default without a word. A migration keeps a classifier routing to
a pool the plan drops, which YDB takes too. Each rule refuses with the host's
words.
A source declares the pools and classifiers it manages, not every one of the
database, so the comparison keeps one a source leaves out, as the host does;
an absence a source claims for one object is left undecided. A pool compares
setting by setting and a classifier as a whole. The default pool must be read
before its settings change, and it is never created or dropped. A schema
written as SQL, such as schema inspect --format sql, writes it as the ALTER RESOURCE POOL default SET of the settings it names, and writes nothing for it
when it names none, as the host's engine did.
The planner writes each workload statement of a plan as a step of its own,
outside a transaction, ordered after the steps that create or change a role and
before those that drop one, since a classifier names one. The steps are chained
in the host's order: classifier removals, pool removals, pool creations and
changes, classifier changes, then classifier creations. YDB keeps one
classifier per rank and checks it at every statement, so a classifier that
moves to a rank another changed classifier leaves is dropped and created again
around the move, in two steps. Each step runs after the common steps that need
neither it nor a later workload step, which is where the YDB engine built into
the host at 74de0b65b ran its workload statements: a pool right before the
first grant on it, and a classifier after every common step. Measured on
25.1.4.7 and 26.2.1.14 with a group, a table, a topic, a pool, its classifier
and grants on the table, the topic and the pool, schema apply and
migrations generate create the pool after the table, then the grants, then
the classifier, and the down file drops the topic, the table, the classifier,
the pool and the group. In a migration file each step is one statement.
Execution of a step reads .sys/resource_pools and
.sys/resource_pool_classifiers before its statement and refuses an object
that changed since the plan read it; it runs the statement once and reads both
views back. The workload
manager reads its classifiers from a cache, so for a while after a classifier
and its pool are dropped it can still route a query to the pool, and YDB
refuses that query with Failed to resolve pool id before it runs it. A read of
the views refused so is sent again, for up to 20 seconds, for an inspection and
for an execution's check and confirmation alike; a statement is never sent
twice. A classifier with no member name routes every query of the database,
the server's own included. Measured on 25.1.4.7: while such a classifier
routed every query to a pool that admits none, a read of
.sys/resource_pool_classifiers sent to the default pool ended in
System view: timeout. Inspection reads
the views on a target with resource pools, for a read of the whole database,
one that selects the root, /, and one filtered to other directories where the
host lists schema.database-kinds: the engine names both kinds database
kinds, so a filtered read reports them beside its scope, named at the database
(ADR 0065). So schema apply --schemas app creates a pool its source
declares, and keeps the database's pools when the source declares none, as a
run of the whole database does. With a host that does not list the feature, a
read filtered to other directories cannot report what belongs to the database,
so it leaves both kinds uninspected; selecting the root as well (--schemas app,/) reads them. A dev realm holds none of its own.
A complete workload read records absence inside the opened database's root namespace, including when no pools or classifiers were returned. It establishes no absence in another database or a directory namespace. The host preserves that boundary when it compares a new declaration against the inspection.
TestHostAppliesAResourcePoolInARunOfADirectory_HappyPath drives ptah schema apply --schemas the fixture directory alone: it creates a declared pool, reads
it as synced, keeps it when the source leaves it out, and changes it in place.
TestWorkloadLifecycleSubprocess creates a pool and two classifiers, swaps
their ranks from the text of a migration file, refuses a change planned before
another writer changed the pool, and removes them, reading the views back after
each step. TestHostAppliesResourcePools_HappyPath drives ptah schema apply
over the fixture directory and the root from a YAML, a Go and a YQL source: it
creates them, finds nothing to change, changes a pool and swaps two ranks,
finds nothing to change again, and keeps them when the source leaves them out. TestHostCreatesAClassifierAfterTheGrants_HappyPath pins the order of
schema apply --dry-run and of the up and down files of migrations generate.
Changefeeds
The provider declares, compares, plans and applies the changefeeds of row
tables and the consumers of their topics, with the syntax, the rules and the
statements of the host's embedded YDB engine (internal/ydbchangefeed).
A YAML table declares its changefeeds under the table key changefeeds, and a
Go file with the directives ptah:schema:changefeed and
ptah:schema:changefeed:consumer on the table's struct, or on another struct
with the table attribute:
tables:
orders:
columns:
id: {type: Uint64, primary: true}
changefeeds:
updates:
mode: NEW_AND_OLD_IMAGES
format: JSON
retention_period: PT12H
consumers:
billing:
important: true
search: {}
//ptah:schema:table name="orders"
//ptah:schema:changefeed name="updates" mode="NEW_AND_OLD_IMAGES" format="JSON" retention_period="PT12H"
//ptah:schema:changefeed:consumer name="billing" changefeed="updates" important="true"
//ptah:schema:changefeed:consumer name="search" changefeed="updates"
type Order struct {
//ptah:schema:field name="id" type="Uint64" primary="true"
ID uint64
}
The options are the host's: mode and format, which YDB requires,
virtual_timestamps, resolved_timestamps, initial_scan, user_sids,
schema_changes, topic_min_active_partitions, topic_auto_partitioning and
retention_period; a consumer takes important, read_from,
supported_codecs and availability_period. Each is parsed and refused with
the host's words, and a Go consumer names a changefeed its table declares. A
YAML or Go source describes the changefeeds of every table it declares, so a
table it declares with none is declared to have none and the changefeeds it
holds are dropped. A YQL source cannot declare one (see
SQL schema reading) and leaves them as they are. A
column table takes no changefeed.
The comparison reads both sides as the host's specification: a mode, a format
and a codec in any case, an interval by the seconds it denotes, YDB's 24 hours
for a retention left out, consumers by name and their codecs as a set, and a
topic's starting partition count only where both sides name one. The
statements of a change list the consumers as Ptah's built-in YDB engine did:
those it drops in the order the read lists them, which is name order, and those
it adds in the order the declaration lists them, so the rollback of a change
drops the consumers it added in the order it added them. A changefeed
the read cannot represent (an AWS region, attributes, a fraction of a second,
an auto-partitioning strategy no changefeed declares, a consumer attribute
other than YDB's own) leaves the table's changefeeds undecided, and so does a
change to a stream an asynchronous replication controller owns, or a
declaration of such a stream that does not retain the controller's binding, as
a desired side converted from a read retains it; such a stream the declaration
leaves out is kept. A binding is read only with every field, an absolute
replica path, a canonical positive item number and no other key; any other is
an attribute the read cannot represent. YDB drops a table whose stream a
controller owns and leaves the replication failing (Discovery for all topics failed), so such a table is dropped only with a replication of this database
the plan drops too. The table planner refuses the drop where the binding names
a replica outside the database, in a database nested in it, or at a path where
this database holds no table, which is a database of the same name on another
server; the read lists the replica's path to tell. The replication planner
refuses it where the plan keeps the replication.
The table lifecycle service plans every changefeed change of a table as one
step, outside a transaction: the host runs a step that drops an object after
every step that creates one, and YDB keeps a limit on the changefeeds of one
table, so a swap at the limit and a changefeed dropped and added again need
one step. The operation runs the host's statements: ALTER TABLE ... DROP CHANGEFEED for every removal, then ALTER TABLE ... ADD CHANGEFEED with an
ALTER TOPIC ... ADD CONSUMER per consumer for every addition, then ALTER TOPIC for a retention or a consumer that changes in place. YDB changes no
option of a changefeed in place (MODE alter is not supported), so such a
change drops the changefeed and adds it again, and a consumer whose codec list
the declaration empties is dropped and added again. A disabled changefeed the
declaration states disabled, as one converted from a read does, keeps its state:
its topic changes in place, since ALTER TOPIC disables nothing, and no step
adds a disabled changefeed, since no statement disables one. A new table's changefeeds
follow its creation. The planner refuses an option the session lacks with the
host's capability key: changefeed_user_sids before 26.1,
changefeed_schema_changes before 25.3, changefeed_topic_auto_partitioning
before 25.2 or with the feature flag off, and a consumer's
availability_period before 25.4. It refuses the shapes YDB refuses on every
line, a topic of several partitions on a first key column other than Uint32
or Uint64, a changefeed named after an index of its table, and a changefeed
and an index that trade a name in one plan. A removal or a recreation, and a
consumer dropped or restarted, is destructive.
Execution reads the table and each changefeed's topic before the first
statement and refuses a changefeed that changed since the plan read it; it runs
each statement once and reads the table back, requiring each declared
changefeed with its topic's retention and consumers, or a removed one's
absence. The one statement that runs again is one YDB refuses because another
schema operation holds the table's path (OVERLOADED, path ... has been locked by tx or path is under operation), which changed nothing: a changefeed added
with an initial scan holds the path until the scan has read every row, so the
next statement waits, 50 ms at first and up to a second between attempts,
until it runs or the request's context ends. Any other unconfirmed result is
UNKNOWN and is never retried.
In the TEXT form a changefeed operation is the statements the host's embedded
engine writes into a migration file for the same change, one to a line, in its
order: the removals, a changed changefeed among them, then the additions, each
with the consumers of its topic, then each topic's change in place. A
migration runner runs each statement alone, so an ALTER TOPIC finds the
topic the ADD CHANGEFEED above it created. A migration file then carries a
changefeed change, and a rebuild of a table that holds changefeeds.
TestChangefeedLifecycleSubprocess adds a changefeed with an initial scan and
two consumers, restarts a consumer, refuses a change planned before another
writer changed the topic, drops and adds the changefeed for a mode change, and
drops it, reading the server back after each step.
TestHostAppliesChangefeeds_HappyPath drives ptah schema apply from a YAML
and a Go source: it creates a table with two changefeeds, finds nothing to
change, changes a topic in place and recreates a changefeed, finds nothing to
change again, and drops both.
External data sources and external tables
The provider declares, compares, plans and applies YDB external data sources
and the external tables over them, with the syntax, the rules and the
statements of the host's embedded YDB engine (dialect/ydb/ydbexternal). One
comparison service and one planner own both kinds, because a data source the
plan drops and creates again takes the tables over it along.
A YAML document declares them under the document keys external_data_sources
and external_tables, a Go file with the directives
ptah:schema:externaldatasource and ptah:schema:externaltable, and a YQL
schema with CREATE [OR REPLACE] EXTERNAL DATA SOURCE and CREATE [OR REPLACE] EXTERNAL TABLE; OR REPLACE is not part of the declaration, because whether a
plan replaces an object is the plan's decision:
external_data_sources:
events_bucket:
schema: ext
source_type: ObjectStorage
location: https://storage.example.test/events/
auth_method: NONE
external_tables:
events:
schema: ext
data_source: ext/events_bucket
location: 2026/
columns:
- {name: id, type: Int64, not_null: true}
- {name: kind, type: Utf8}
options:
FORMAT: csv_with_names
CSV_DELIMITER: ";"
//ptah:schema:externaldatasource name="events_bucket" schema="ext" source_type="ObjectStorage" location="https://storage.example.test/events/" auth_method="NONE"
//ptah:schema:externaltable name="events" schema="ext" data_source="ext/events_bucket" location="2026/" columns="id Int64 NOT NULL, kind Utf8" options="FORMAT=csv_with_names;CSV_DELIMITER=\;"
type Externals struct{}
A data source takes name, schema, source_type, location, auth_method
and options; a table takes name, schema, data_source, location,
columns and options. A Go options attribute is NAME=value pairs
separated by ;, where \; writes a semicolon and \\ a backslash, and a Go
columns attribute is name Type [NOT NULL] entries separated by commas.
Option names are read in upper case; an option that has an attribute of its
own, REFERENCES, which the server keeps, a column with DEFAULT, PRIMARY KEY or FAMILY, and an object declared twice are refused with the host's
words. Each source describes the external objects of the directories it reads
completely, so an object it leaves out is dropped; a ptah:not-described external_data_source or external_table header entry of a YQL document keeps
one. HCL has no block for either kind.
The comparison reads both sides as the host does, against the database the
current side was read from: source type, location and auth method exactly,
options by upper-case name, and a *_SECRET_PATH option relative to the
database, which the server keeps absolute; a table's data source relative to
the database, its location, its columns in order with their types and
nullability, and its options, with PARTITIONED_BY read as the list it holds.
REFERENCES and the access metadata are not compared. A declaration on a
target without external_data_sources is refused, and so is a plan that drops
a data source a declared table reads. A data source dropped and created again,
or replaced with a new source type, takes each declared table over it along,
as a change whose operands may describe the same table.
YDB alters neither object (Alter operation for EXTERNAL_DATA_SOURCE objects is not implemented), so the planner replaces a changed one: with CREATE OR REPLACE where the session reports external_object_replace, and otherwise by
a DROP and a CREATE. A data source dropped and created again is one operation;
a table is a drop before the statement on its data source and a restore after
it. Tables are dropped before the data sources they read and created after,
and every step asks for the host's dependent phase, the one window creations
and drops share. YDB refuses to drop or replace a data source a column table's
TTL names, so the TTL of a column table the plan keeps is reset before a
replacement of such a source and set again after it, as the host's embedded
engine carries it (a target without tiered_ttl refuses the replacement), a
column table the plan drops is dropped before the sources its TTL names, and a
plan that leaves a table with a TTL naming a data source it drops is refused
(its TTL reads external data source ..., which the plan drops). The planner refuses, with the host's words, every statement
on a target without external_data_sources, an option ending in
_SECRET_PATH before 25.4, where the session reports
external_data_source_secret_paths false, a secret path outside the
database, two objects at one path, and a table over a data source the batch
writes that is not ObjectStorage. No statement holds data YDB stores, so a
drop or a replacement is behavioral and never destructive.
Execution reads the object before the first statement and refuses one that changed since the plan read it, REFERENCES aside, or a path that holds something else. It runs each statement once, gives an object dropped and created again the owner and the direct access entries the old one held, and reads the object back, requiring the declaration, the owner and the access entries, or the object's absence after a drop. An unconfirmed result is UNKNOWN and is never retried. A dev realm session refuses both kinds, which reach past the realm.
TestExternalLifecycleSubprocess creates a data source and a table over it,
moves the data source with access entries on both objects, refuses a change
planned before another writer changed the table, and drops both, reading the
server back after each step. TestExternalReplacementSubprocess turns
EnableReplaceIfExistsForExternalEntities on through the server's dynamic
configuration and replaces the data source, which keeps the table, and then the
table in place, each keeping its owner and access entries.
TestHostAppliesExternalObjects_HappyPath drives
ptah schema apply from a YAML, a Go and a YQL source: it creates both
objects, finds nothing to change, moves the data source, adds a column to the
table, finds nothing to change after each, and drops both.
Common column semantics
Inspection requires the negotiated schema.observed-columns feature. Row and
column tables report complete common column facts, including decimal precision
and scale, literal default presence, and serial start and increment settings.
The last sequence restart remains observed evidence; it is not a request to
restart the sequence during source conversion. Native column facets retain the
exact settings needed to check execution before-state. Unknown or contradictory
native metadata refuses the complete inspection reply.
Column default changes use captured native before-state and typed values. The
provider plans one ALTER COLUMN SET DEFAULT or DROP DEFAULT, outside a
transaction, and verifies the column before and after execution. An empty string
is a present literal. A stale before-state refuses execution; an unconfirmed
write has an unknown outcome and is not retried. The session reports
alter_column_default from the release line and EnableSetDropDefaultValue.
A default change, a column made nullable, a column removal and a sequence change each hold the native column they change. The side the host builds from a schema file for a diff of two files states its column facets completely and carries one only for a column whose declaration states a native setting, so a column without one is held as the common column says, its common default read back as the native literal. A diff of two files plans these changes offline, as Ptah's built-in YDB engine did. A side that does not state its column facets refuses them.
The parent planner derives column additions and removals from the complete
captured table. Nullable additions without defaults use native AlterTable.
Additions with typed literal defaults use one pinned YQL ADD COLUMN; existing
rows receive the default, including an empty string for a NOT NULL column.
They require add_column_with_default, established from the release line and
EnableAddColumsWithDefaults. A NOT NULL addition without a default is refused.
A removal checks its captured column definition and refuses key, index, TTL, or
partitioning dependencies before dispatch. Each operation verifies the remaining
columns and table settings after its single write. The graph orders additions
before the default and sequence changes, and removals after them. The table's
family, TTL, partitioning, index and changefeed steps are outside that
sequence. Each carries its own edges to the column steps it depends on, and
otherwise runs where the host places it: the host drops an index before it
adds a column.
The negotiated schema.columns service compares the exact columns the host
pairs inside their full captured tables. It reports type and default
equivalence, effective nullability, and sequence start and increment differences.
Primary and serial columns have effective NOT NULL; the original declaration
is retained. Readback distinguishes absent defaults, empty literal values and
expressions. Creation projection reports every effective column in declaration
order, including its typed literal default and comment.
Table compilation maps supported SQL aliases using the selected target's capabilities. Row-table literal defaults compile into typed native values; expressions are refused. A default has one source declaration, either a common column field or native column settings. Wide date/time types and parameterized Decimal follow the cluster flags; small integer and document defaults follow the measured release line. A column table accepts no defaults or serial columns. Serial columns are created with their sequences and the sequence of an existing Serial changes in place; see Serial columns.
Offline table preparation
The experimental schema.prepare-tables service derives column key membership
from a complete captured row-table declaration. It uses the same native compiler
as CREATE, including ordered composite keys and native properties. Source values,
observations, and explicit existence knowledge remain unchanged. The service
opens no session and refuses the whole batch when any declaration is unsupported.
It does not predict server defaults or enable the host's common SQL table planner.
Offline CREATE prediction
The experimental schema.project-table-creations service predicts column key
membership and effective desired table properties from complete declarations.
It uses the native CREATE compiler, including composite key order and authored
settings. Computed values remain on the captured table owner. Explicitly excluded
table properties stay excluded, and source declarations remain unchanged.
Offline representation conversion preserves an absent database binding. Live
capture still requires the database that supplied the observation.
Unspecified server defaults remain unspecified. The service returns no observed overrides and makes no claim that a table exists or a migration ran. Both process boundaries validate complete batches, identities, exclusions, and key membership. Live tests feed the prediction into native planning, execute CREATE, and inspect the resulting table on both qualified server lines.
Offline schema validation
The experimental schema.validate service checks the captured desired schema
before the host plans. The host asks for it and shows the reply to
the user; without the service ptah schema plan stops with target "ydb" has no schema validation service. The service reads only the request. It opens no
session and loads no source file.
Each table declaration is compiled with the CREATE rules, so validation refuses
what planning would refuse: a missing or unknown key column, a type with no YDB
counterpart, a table without columns, populated declaration fields and facets
the provider does not carry (including common indexes), and invalid names. Access
requirements and temporary tables are valid declarations that only CREATE
refuses, so validation accepts them. A setting the target takes only with a
capability is refused where the request's capabilities lack it: the settings of
a Serial's sequence, and a family's cache mode, which the host's bundled
validation refuses for --server-version 25.1 and 25.3 and accepts from 25.4.
Table paths that coincide or nest are refused
across the schema, because a YDB table holds no children. Directory names are
checked as path elements. The native models in the whole-schema capture must
normalize.
A refusal of the declaration is a diagnostic in a completed reply. Cancellation,
an identifier context the provider cannot interpret and a capture whose receipt
differs from the provider's own normalization are errors and carry no
diagnostics. Diagnostics are sorted, so the same schema gives the same reply in
any capture order. A key, type or unsupported-field refusal is
UNSUPPORTED_FEATURE, as in the host's bundled YDB validation; other refusals
are INVALID_SCHEMA. A standalone enum, and a directory comment, character set or
collation, are reported as unsupported and OMITTED_DECLARATION only when the
request asks that nothing be skipped, which is also the host's behavior. So is
what an offline render of a table's creation goes without (see Offline schema
rendering), in the host's bundled validation's
words: ydb: column "app.accounts.email": type modifier=VARCHAR(255): length 255 would be skipped and identity start=100 would be skipped.
internal/schemavalidation/testdata/corpus holds declarations and what the host's
bundled validation answered, recorded by testdata/oracle (the program header says
how to run it against a host checkout). Rows named refused- agree with the host on
code, deferred- rows are ones the host accepts and this provider refuses, and the
others agree on acceptance and on what would be skipped. The provider imports no host
code. A subprocess test runs the service through the SDK client, and a host contract
test (PTAH_HOST_TEST_BIN) checks that the host reaches the service and shows its
refusal.
Native row-table creation
The SDK planner accepts captured CREATE lifecycles for row tables. It retains
ordered composite primary keys, native column types and nullability, TTL,
storage settings, column families, attributes, partitioning settings, starting
layouts, bloom filters, read replicas, and tiering. Column families and typed
literal defaults come from native column facets. Missing creation settings
request server defaults. The common and native primary key declarations must
agree on order. A declaration that orders no key, such as a YAML table with
two columns marked primary: true, is keyed by the columns it marks primary in
the order it declares them, as the host's embedded engine keys it; one that
marks none is refused.
A family's cache mode travels as field 6 of the request's family, its upstream
wire number, which the pinned Go bindings do not declare and keep as an unknown
field; the operation payload writes it as the family's cacheMode, its
upstream JSON name. The table service of 25.1.4.7 does not know the field and
creates the table without it, so a creation that states one is refused where
the session reports no column_family_cache_mode, and the creation is
confirmed by the cache mode the table reads back with.
A starting layout becomes the partitions of the CreateTable request, checked
against the key columns: uniform_partitions needs a Uint32 or Uint64 first key
column; a split point holds at most one value per key column, each value a whole
number in the range of its integer column or a string for a Utf8 or String
column, and no other key type takes one. Each value travels as an optional
element of a tuple over the leading key columns. Split points out of order are
left to YDB, which refuses them. The creation is confirmed by the minimum
partition count YDB sets from the layout, unless the declaration states a
minimum of its own. The operation payload is checked again against the same
rules, so a forged request cannot state a layout the rules refuse.
The planner can replace a complete common CREATE TABLE step in the shared graph. Its replacement carries table and physical-path effects and forbids SQL transactions. A refusal returns no successful planning prefix. Rendering emits a native operation envelope and validates its safety metadata again.
Execution establishes absence through the complete listing of the parent directory, or through the proof that the directory does not exist, which CreateTable then creates with the table. An existing path is refused, including an existing table with the same schema. The provider binds the session and absolute path, checks its guarding leases, sends CreateTable once, and reads the result back. It confirms columns, key order, requested settings, and the absence of unexpected indexes or changefeeds. A failed reply or unconfirmed result remains UNKNOWN. The native API provides no atomic compare-and-set; these checks do not establish exclusive ownership against another schema writer.
A table is created with the global indexes it declares, synchronous or
asynchronous, unique or not, with their key and covered columns, in the one
CreateTable request, as the host's embedded engine writes them inside its
CREATE TABLE. That is the only way to give a new table a unique index on a
release that refuses one on an existing table. What a YDB index has no clause
for is refused in the embedded engine's words: an expression, an order, a
prefix, a NULLS order or an operator class on a key column, an index on the
table's key, a covered key column. A vector, full-text or local index is
refused as a common index, since the pinned table protocol builds none of them
with a table. An index names its table as the host names it: by its name, by
its directory and name, or, where a part holds a dot, by the quoted reference
"app.records" for the root table app.records and app."v.1" for the table
v.1 of the directory app. Execution confirms each index by reading it back. In
the TEXT form each index is an INDEX clause of the CREATE TABLE.
YDB has no UNIQUE constraint: it refuses one as a parse error and holds the
same rows with a global unique index, a second row with the same value refused
and rows whose value is NULL not. A session reports unique_constraints as
false, and a host that reads it so turns a declared UNIQUE constraint, and a
column's UNIQUE, into that index before it compares and plans, under the name
its YDB renderer gives it: the constraint's name, or <table>_<columns>_key.
A UNIQUE over the key folds into the key. The provider then creates and reads
an index, never a constraint. Schema validation and the creation projection
read the source the operator wrote past its UNIQUE, since the index changes no
column of the table; a UNIQUE the host does not turn into an index, such as a
deferrable one, is refused when it is planned.
The vector and full-text indexes of the table's native index set are built by
the creation itself, with the rules of
Vector and full-text indexes, as the host's
embedded engine built them: the native CreateTable call carries each one as
the upstream arm of its index, and the TEXT form, an inspection and a
migration file write every index of the table inside its CREATE TABLE, in
name order, the vector or full-text one as INDEX name GLOBAL USING vector_kmeans_tree ON (...) WITH (...). YDB then builds no index over rows the
table holds, so the operation service lists no index build for the table. A
target that cannot build one refuses the whole creation before the table is
created. A global index the native set states is refused at creation: a new
table takes its global indexes from the common declaration.
Access requirements, temporary tables, changefeeds, column compression or encoding, and richer common constraints are refused. Removal and rebuild are described below, and alteration covers column families, the addition and removal of columns, the TTL, the automatic partitioning settings, the read replicas, the key bloom filter, and the partitioning and read replicas of an index (see below). An unknown populated field cannot disappear during compilation. A mixed SQL/native batch is validated before its first unit executes.
Subprocess tests cover creation, inspection, stale plans, retained table data,
and refusal of a malformed later unit on YDB 25.1.4.7 and 26.2.1.14. Unit tests
exercise lost replies, lost confirmation, lease failure, and session cleanup.
With the validation service in place, ptah schema plan through this provider
gets past validation and stops at the host's common-step stage ("A common step is
outside the supported table creation lifecycle"), because the host's common table
comparison does not yet preserve complete native declarations. These SDK checks do
not establish a complete native CLI table migration workflow.
Serial columns
A column is a Serial when its type is Serial, BigSerial or SmallSerial
(or one of the spellings YDB shares with PostgreSQL: SERIAL, SERIAL4,
BIGSERIAL, SERIAL8, SMALLSERIAL, SERIAL2), or when an Int16, Int32 or
Int64 column is declared auto_increment or with an identity, as the host's
embedded engine reads it. The host sends the start and the increment of its
sequence with the common column, as identity_start and identity_increment:
Go and YAML write them as attributes of the field, and a YQL schema as the
START WITH and INCREMENT BY of an ALTER SEQUENCE on the sequence's
absolute path. No native facet holds them. A Serial is NOT NULL whatever the
declaration says, and its default is its sequence.
//ptah:schema:field name="id" type="BIGSERIAL" primary="true" identity_start="100" identity_increment="5"
//ptah:schema:field name="id" type="BIGINT" primary="true" auto_increment="true" identity_increment="3"
The native CreateTable call creates the column with its sequence, named
_serial_column_<column>, starting at the declared start, stepping by the
declared increment and ending at the maximum of the column's type. Measured on
25.1.4.7 and 26.2.1.14, a sequence created this way issues its start first and
records no restart. The call gives a sequence the Int64 maximum unless the
request names one, whatever the column's width, so the request names the
maximum YQL's Serial and SmallSerial have. Execution confirms the sequence
by reading it back, and the operation payload is held to the same sequence, so
a forged request cannot name another one, count down or cycle.
The TEXT form writes the column as its Serial type and, for a start or an
increment other than 1, the ALTER SEQUENCE the host's embedded engine writes
after the CREATE TABLE, with RESTART WITH the start where it is not 1:
START alone leaves the next value at 1. The statement names the sequence by
its absolute path, so a creation that names no database, such as an offline
render's, is written without it, and the render reports the start and the
increment as omissions, as the host's embedded engine did (see Offline schema
rendering). A plan against a database carries the
path of the database it read in the creation, since the host names the table it
plans by no database, so a migration file holds the statement, and YDB
records the restart, which it replays on every later ALTER SEQUENCE. A table
created from such a file therefore keeps the settings it was created with (see
below), where a table the native call creates can change them.
The planner refuses, in the embedded engine's words, an auto-increment column
of a type YDB has no Serial for, GENERATED ALWAYS, identity options other than
the start and the increment, a default on a Serial, a start or an increment
below 1 or not a whole number, and a start or an increment a creation or a
change would give a 16-bit or 32-bit Serial, a change back to the defaults
included. Every line reports serial_sequence_keeps_range false: any ALTER SEQUENCE raises such a sequence's maximum to the Int64 maximum, and the column
then stores the value after its own maximum as a negative number without an
error. The native call could create such a sequence with its range kept, but no
later change and no migration file could, so it is refused at creation too. A
table whose 16-bit or 32-bit Serial already holds the declared start and
increment needs no statement and plans nothing, and validation, which cannot
tell, takes the declaration, as the host's embedded engine does. A column table
takes no Serial, and YDB adds no Serial column to a table that exists (Column addition with serial data type is unsupported).
When the declared start or increment of an existing Serial differs from what
the read reports, the planner writes one step, serial-sequence/N: one YQL
ALTER SEQUENCE ... START WITH ... INCREMENT BY ... on the sequence's absolute
path through the query service, outside a transaction and without a restart,
since a restart onto a value a row holds fails the next insert. The table
service has no call that changes a sequence: an AlterTable that names it
answers SCHEME_ERROR. Execution reads the column first and refuses one that
changed since the plan (NOT_STARTED), sends the statement once, and reads the
sequence back. The next row still takes the value the sequence computed before
the change: with an increment of 3 moved to 7 after the rows 1 and 4, the next
rows are 7 and 14. The planner refuses to change a sequence some
ALTER SEQUENCE restarted, since the next insert would take the restart's
value again (Conflict with existing key), and the sequence of a 16-bit or
32-bit Serial. A column that becomes or stops being a Serial changes type,
which only a rebuild makes, and a rebuild of a table with a Serial is refused.
The session reports serial_columns and serial_sequence_options on every
line, and serial_sequence_keeps_range on none. Subprocess tests create a
Serial by the native call and by its TEXT form and change a sequence both ways;
host tests apply Go and YQL schemas with Serial keys, read the sequences back,
insert rows, change an increment, and show each refusal against the server
state that makes it necessary, on 25.1.4.7 and 26.2.1.14.
Native table removal
A table the declaration no longer holds, which the host plans to drop, is
dropped by this provider: the planner replaces the host's steps for the table
with one removal step, and its receipts name it for every table facet. A row
table goes with its indexes and changefeeds, and a column table the same way.
The removal is written as YQL's DROP TABLE, as the host's embedded engine
writes it, in either form: execution runs it through the query service once,
outside a transaction, and confirms that the path is gone. A table whose read
records its changefeeds as uninspected or unrepresentable, and carries none, is
not dropped (dropping table "app/t": the changefeed namespace is not fully described: ...): DROP TABLE takes every changefeed with it, and the plan
cannot tell whether a replication reads one.
Before the statement is sent, execution reads the path: it must still hold a
table of the store the plan read, with the columns the plan read, in order, and
the same key columns. A table that changed or went since the plan is refused
with nothing dropped (NOT_STARTED). This is not an atomic compare-and-set: the
native API has none. The live tests drop a row table natively and by text, a
column table, and refuse a table a column was added to after planning.
Native table rebuild
YDB changes neither a table's key nor a column's type in place, makes no
column NOT NULL, and takes a starting layout only when it creates a table. With
the host's planning policy asking for a rebuild (contract
25.3, planning.policy; --allow-table-rebuild on the native CLI), the
planner makes such a change the way the host's embedded engine does, in five
steps and one more for each index whose held settings differ from a new
index's (see below): it creates the table anew from the declaration under a
scratch name (__ptah_rebuild_<table>), copies the rows with one INSERT ... SELECT, renames the old table out of the way (__ptah_replaced_<table>),
renames the new one into its place, and drops the old one. Where a table the
declaration holds or the plan drops already has a scratch name, the rebuild
takes the next free one (__ptah_rebuild_<table>_1 and on). The host's steps for the table
are rewritten to the rename that puts the new table in place. A REBUILD the
host proposes is planned the same way, and refused like any other rebuild
without the policy.
A key change reaches the planner from a host that lists
schema.observed-primary-keys and planning.primary-key-changes (contract
25.26): the read reports each table's key in key order, under the name
<table>_pkey the host's embedded engine gave it, so a declaration that orders
the same columns otherwise is a key change. The host hands it over as an
alteration of the table, and the planner rebuilds the table with the declared
key, or without the policy refuses it as the embedded engine did:
adding constraint <table>_pkey, the primary key, which requires target capability primary_key_alterable, unavailable on this ydb target. The reverse
plan rebuilds the table with the key it held.
The copy reads each column as the new table declares it. A changed type is
converted with CAST and unwrapped, so a value that does not convert fails the
copy with a message naming the table and the column instead of becoming NULL;
a column made NOT NULL is unwrapped too, which fails the copy on a NULL row.
The copy is one data query, so YDB's refusal shows that nothing of it applied:
schema apply stops PARTIAL after the new table's creation, and the reason
carries the message (see Error reasons).
The new table keeps the old table's attributes, and with them the comments Ptah
keeps there, and each storage setting the declaration does not state, as the
old table holds it: every column family, the default one and one no column
sits in included, with the settings the declaration leaves out (its cache mode
among them, which a target without column_family_cache_mode refuses to
write), the family each column sits in where the declaration does not say (see
Column families), the automatic partitioning, the read replicas, the key bloom
filter (KEY_BLOOM_FILTER = DISABLED for a table that states none, as the host's
embedded engine names it), the TTL where the declaration states no table properties, and the run
interval of the TTL it holds where the declaration states a TTL, which names
none since YQL has no setting for one. A cluster's table profile may have
given the old table any of them, and YQL drops neither a family nor a setting,
so a rebuild changes none that nobody declared. Each index of the new table
takes the automatic partitioning and the read replicas its own facet declares
(see Index settings from a source), and for
every setting the facet leaves out what the old index under its name holds,
or the old index the host renames it from, as the host's embedded engine
resolves a rebuilt index's declaration. An index the rebuild adds takes its
declared settings over the ones YDB gives a new index. The call that creates a
table takes no index settings, so a table-rebuild/N/index-settings/M step
after the creation and before the copy gives the index every setting with
ALTER TABLE ... ALTER INDEX ... SET, over the settings YDB gives a new index.
The read describes each index the common model describes beside the native
index set for that reason. A facet for an index the new table does not create
refuses the rebuild. Without the policy, the change is refused in the embedded
engine's words, naming the request the host passes, such as
--allow-table-rebuild.
The host's own steps for the table, such as the addition or the removal of an index or a column, are rewritten to the rename that puts the new table in place, and that rename names every object they change, as the host's plan graph requires. An index the plan drops and adds again under its name, such as one whose columns change, cannot be named twice by one step, so the removal, and every removal the host orders before it, is rewritten to the rename that moves the old table out of the way. A host step that changes something before such a removal refuses the rebuild.
Every step writes as a YQL statement in the TEXT form, so a migration file
holds the rebuild, unless the old table holds user attributes or a TTL run
interval other than YDB's own: YQL's CREATE TABLE sets neither, so such a
rebuild has no TEXT form (NO_TEXT_FORM) and runs natively. A native execution checks each step's precondition first:
the copy needs the new table empty and the source holding every copied column,
a rename needs the table holding the captured columns and key, and a free
target. YDB renames no table that carries a changefeed, so a rebuild of a
table that holds changefeeds drops them in a step before the old table moves
and gives the new table its changefeeds in a step after it is in place: the
ones the declaration states, or the ones the old table held where the
declaration does not state them, with their consumers. Each stream restarts.
The rebuild's first step carries the note, after the rebuild's own notes, in
the host's embedded engine's words: Changefeed feed of table app/cf is dropped before the swap and added again after it, because YDB moves no table that carries a changefeed, and a changefeed the new table does not get is noted as
dropped with its topic. A changefeed an asynchronous replication
controls refuses the rebuild, as does a column table, whose rebuild needs an
explicit data migration. So does a read that does not say which changefeeds
the table holds, since the rename would fail after the new table is created and
filled: a read that captured none while the declaration states some, a read
whose coverage records them as uninspected or unrepresentable and carries none,
as the host records them for two HCL documents (the changefeed namespace is not fully described: ...), and a changefeed the read cannot represent.
A native execution gives the new table the access control entries the old
table holds before it copies the rows, as the host's embedded engine gave a
rebuilt table back its grants: the new table is created with its directory's
permissions alone, and the old one is dropped with its own. A grant or revoke
the plan makes on the table runs after the rebuild, on the new table. A
migration file runs later and reads no server, so under planning.table-grants
(contract 25.33, ADR 0111 in Ptah), where the host sends the grants the read
found on each held table, the TEXT form grants them on the new table after the
old one is dropped, where the host's embedded engine wrote them, one GRANT to
a line: every grant except a privilege the table holds by default and one the
plan revokes. They follow the drop so the rebuild's statements stay the
consecutive steps the migration lint reads as one rebuild. A read that did not
describe the table's grants leaves the file without them, and a note above the
rebuild says so; a host that does not send the grants leaves the new table
with its directory's permissions.
The live tests rebuild a table natively and by text on both release lines,
read the copied rows back, and fail a copy whose value does not convert.
A table whose declaration holds a Serial column is not rebuilt: the new
table's sequence would start at 1 while the copied rows keep their values, so
the next insert would collide (Conflict with existing key), and ALTER SEQUENCE ... RESTART WITH takes only a literal, so no statement can move it
past the copied rows.
Secrets
The ydb-secrets comparison and planning services manage scheme-secret
presence. Inspection records the secret's path without reading its value.
A declaration names an approved environment input:
secrets:
pg_password:
schema: app
value_env: PTAH_SECRET_PG_PASSWORD
The equivalent Go annotation is
//ptah:schema:secret name="pg_password" schema="app" value_env="PTAH_SECRET_PG_PASSWORD".
A YQL schema declares
CREATE SECRET \app/pg_password` WITH (value = $PTAH_SECRET_PG_PASSWORD);. Schema sources refuse inline values. Input names must start with PTAH_SECRET_`
and contain only ASCII letters, digits, and underscores. A refused declaration
gives the reason a host prints, in the words of Ptah's built-in YDB engine: the
secret, by its YAML key, the attribute and what it takes. It never repeats the
value a declaration wrote or the variable it names:
provider decode source failed (InvalidArgument): secret "pw": invalid value_env: the variable it names does not start with PTAH_SECRET_ and a name after it; Ptah reads a secret's value only from a variable under that prefix, so a migration file cannot copy any other variable of the machine that applies it
An absent secret is created. An existing secret is unchanged unless the
operator requests rotation with --rotate-secret app/pg_password. Rotation
of an absent declared secret creates it. A rotation request must name a
secret retained by the selected desired schema. An omitted secret is removed
only when desired and current coverage establish that removal. A declaration
that omits a secret a declared object still reads by its path is refused: YDB
drops a secret an external data source, an async replication or a transfer
names and keeps the object, which then names a secret that is gone.
The units.sensitive-inputs service reports input names before approval.
Previews and saved operations contain references only. Immediately before
execution, the host checks those references again and resolves the approved
inputs. The provider receives ephemeral bindings and never reads its process
environment for secret values. A missing binding refuses the entire batch
before its first write; an explicitly empty value is accepted. SQL migrations
can use these references as the complete value of CREATE SECRET or
ALTER SECRET. Expressions, assignments, and references in unrelated
statements are refused. Query errors redact expanded values.
Native execution checks presence before dispatch and again afterward. Rotation
also confirms that the owner and permissions remain unchanged. Values cannot
be read back, so these checks cannot detect a concurrent value rotation.
Secret operations run outside SQL transactions and are never retried
automatically. Targets without the secrets capability refuse declarations
and mutations. Any secret names still returned by their directory listing
remain unknown observations; they cannot become planned removals.
External data source planning records its old and new secret-path dependencies. Creation follows its secret's creation, and a reference is removed before its old secret is dropped. Absolute references must resolve inside the selected database or dev realm. The creation or change of an async replication or a transfer reads the secret its connection names by path, since YDB looks the secret up when it creates the object or changes its connection, so it follows the secret's creation; a path outside the database is refused.
Native table alteration
The same planning service (ydb-row-table-create) accepts captured ALTER
lifecycles for row tables. It owns five facets of an altered table: its column
families, the addition and removal of its columns, its TTL, its settings (the
automatic partitioning, the read replicas and the key bloom filter), and its
global secondary indexes with their partitioning and read replicas. Each
becomes native AlterTable calls, except the settings of an index, which the
native call cannot state and YQL's ALTER INDEX changes. Everything else about an altered table stays with the host:
other table settings, and any column step the native call cannot state. The
service also plans the changefeeds of a new or an altered table (see
Changefeeds).
Column families
The service reads the declared and the observed table and column properties, and plans one call that adds families, changes the settings the declaration states, and moves columns between families. The rules follow the host's family rules:
- A setting the declaration leaves out is kept as the table holds it. A cluster
profile gives new tables settings of their own, so the table is not reset.
A declared
COMPRESSION_NONEmatches a family that reads back unspecified. - A cache mode the declaration states is set where the family holds another
one or none. A family that never had one reads back with none, so a declared
regularis set once and then holds, as the host's embedded engine sets it. The request carries it as field 6 of the family, as a creation does (see Native row-table creation). A declared cache mode,regularincluded, is refused on a target withoutcolumn_family_cache_mode, with the flag's name, whatever the table holds: the table service of 25.1.4.7 would alter the table without it. - A family only the table holds stays. No family is dropped, because YDB has no way to drop one.
- A column sits where the declaration places it. A column property record names its family, and an empty family in a record means the default family. A source that describes the column properties of every column it declares (a Go, YAML or YQL source) places a column it names in no family in the default family, so a column the table holds elsewhere moves back, as the host's embedded engine moves it. A source that could not describe them, such as HCL, says nothing of where a column sits, and the column keeps its family. A rebuild places the columns of the new table by the same rule.
- A key column stays in the default family. A column that names a family
neither the declaration nor the table has is refused. So is a
keep_in_memorythat only one side sets, and a declaration that states families when the table's current families were not captured. - A column the plan removes is never named, so it is never moved.
A table whose families already match yields no family step and receipts that say so.
Column addition and removal
The host plans one common step for each column it adds or drops. The service replaces such a step with a native step of its own, so the column steps and the family step form one graph with one owner.
- A step is replaced when its footprint is a read of the table and the creation or removal of one column of that table. A step with any other footprint stays with the host.
- An added column must be nullable, with no default, key membership, unique, check, reference, generated, identity, collation or comment, and no column property but its family. YDB refuses a NOT NULL column on an existing table (measured on 25.1.4.7 and 26.2.1.14), and accepts a default on 26.2 only. The host keeps the step for any column outside this subset.
- An added column starts in the default family. A column property record that names another family moves it in a separate family step, which runs after the addition.
- A dropped column must not be one the declaration states. The service refuses a request in which the host drops a declared column, adds an undeclared one, or changes one column twice.
- Each replaced step becomes one native call, named
row-table-add-column/Norrow-table-drop-column/N. The family step isrow-table-alter/N. Ordering among the host's steps is kept, and the family step runs after the addition of every column it moves and before every removal. Both ends of those edges are steps of this provider. Where a step stays with the host, the edge names the host step.
TTL
The service sets, changes and removes the TTL of an existing row table with one
native AlterTable step, row-table-ttl/N. A step carries the whole TTL the
table holds afterward (set_ttl_settings) or removes it
(drop_ttl_settings), and also the TTL the table held when the plan was made.
- The TTL is managed when the declaration states table properties for this
target, or when the desired coverage establishes the table properties of the
table: complete knowledge of the kind, or an absence or a request for
defaults recorded for the table. A table the declaration leaves without a
ttlSettingsthen requests none, and a TTL the table holds is removed. Unknown or unrepresentable desired knowledge with no table properties leaves the TTL alone. A declaration that states table properties while the table's current TTL was not captured is refused; a table managed by its coverage alone, whose current properties were not captured, is left alone. - The declared and the held TTL are the same when the mode, the column, the
unit and the seconds are the same. A
runIntervalSecondsthe declaration leaves out is not compared. A step that replaces a TTL carries the run interval the table held unless the declaration states one, because the call resets an interval it does not carry (measured on 25.1.4.7 and 26.2.1.14). The server refuses a run interval below its configured limit, 900 seconds on the test servers, after the call; the plan does not check it. - The column must be declared, with a type the mode reads: a Date, Datetime, Timestamp, Date32, Datetime64 or Timestamp64 column for a date TTL, and a Uint32, Uint64 or DyNumber column with a unit for an integer TTL. The server refuses each other combination only after the call, so the plan refuses it. A key column is allowed.
- The TTL step runs after the addition of the column it reads, and before the removal of the column the table's TTL reads now, whoever carries those steps out. The server refuses a TTL on a column the table does not hold, and refuses to drop the column a TTL reads. The family step and the TTL step do not depend on each other.
- Impact is behavioral for a set and for a removal: a TTL decides which rows YDB deletes, and restoring a prior TTL does not recover deleted rows.
- The host capabilities
RowDeletionPolicyandRowDeletionPolicyEpochColumnare not session capabilities of this provider. The host keeps refusing a TTL its target cannot write.
Partitioning settings, read replicas and key bloom filter
The service changes the automatic partitioning settings, the read replicas and
the key bloom filter of an existing row table with one native AlterTable
step, row-table-partitioning/N, as the host's embedded engine changes them
with one ALTER TABLE ... SET. The step carries only the settings that change,
and also the value the table held for each group it changes: the whole
partitioning, the filter, or the replicas. The settings are AUTO_PARTITIONING_BY_SIZE,
AUTO_PARTITIONING_BY_LOAD, AUTO_PARTITIONING_PARTITION_SIZE_MB,
AUTO_PARTITIONING_MIN_PARTITIONS_COUNT and
AUTO_PARTITIONING_MAX_PARTITIONS_COUNT, which the declaration spells
partitioningBySize, partitioningByLoad, partitionSizeMb,
minPartitionsCount and maxPartitionsCount.
- The settings are managed when the declaration states table properties for this target, and only the settings it states are compared. A setting the declaration leaves out stays as the table holds it, so the defaults a table reports for the settings the declaration omits never count as a difference. The native call has no reset, so removing a setting from the declaration does not return it to its default.
- Partition columns are set when the table is created. A declaration that names other columns than the table holds is refused; the same columns are no change.
- A starting layout is set when the table is created, and YDB keeps no record of
it except the minimum partition count it sets from it: the count of the layout,
unless the declaration states a minimum of its own. That minimum is the only
witness an existing table has, so a declaration whose layout sets the minimum
and whose minimum differs from the table's is a change only a
rebuild makes. The new table is created with the
layout and the held value of every other setting, the minimum aside, which
the layout sets. Without a rebuild asked for, the planner refuses it with
UNIFORM_PARTITIONS alter is not supported(or thePARTITION_AT_KEYSspelling), the two minimums, and the request that asks for a rebuild, such as--allow-table-rebuild. A layout beside a declared minimum leaves no witness and is not compared. The comparison reports the layout as a difference, so that the plan reaches the planner. - A minimum count above a nonzero maximum is refused, checked against the value the table holds for a bound the declaration leaves out.
- A partition size declared without
partitioningBySizeturns partitioning by size on, as the server does when a size is set and as the host's embedded engine reads the declaration (measured on 25.1.4.7 and 26.2.1.14 for a table and an index that did not split by size). A size beside a declaredpartitioningBySizeofDISABLEDis refused. - When the step switches partitioning by size or by load on, it names every setting the table keeps: the ones the declaration states and the held value of the others. Switching either on resets the minimum count the call leaves out to 1, and switching partitioning by size on resets the size too (measured on 26.2.1.14 through the table service for partitioning by load), so a minimum nobody declared would otherwise drop to 1. The size the table takes is 2048 MB unless the declaration states one, and the TEXT form names it. An index's settings follow the same rule.
keyBloomFiltertakesENABLEDorDISABLED. A table that reports the filter as unspecified keeps none, so a declaredDISABLEDmatches it (measured on 25.1.4.7 and 26.2.1.14 by the host's embedded engine).readReplicasSettingstakesperAzReadReplicasCountoranyAzReadReplicasCount. A count of zero in either mode is no replicas, and a table that holds some loses them throughPER_AZ:0, since YDB has no reset for them. Replicas the declaration leaves out stay.- Setting one of the three groups resets nothing of the others, so the step names a filter or replicas only where they change. Execution confirms that the groups the step does not name read back as they were.
- Impact is behavioral: the settings decide when YDB splits and merges partitions and where it serves reads. The step reads the table and alters its settings only, and does not depend on the family, column or TTL steps.
Index alteration
The service adds and drops global secondary indexes of an existing row table.
Each index is one native AlterTable step: row-table-add-index/N reads the
table and creates one index object, row-table-drop-index/N reads the table and
drops one. The index object is a host subject of kind index, named by the
index and parented by the table, so two index steps of one table never share an
effect. An addition is ADD INDEX name GLOBAL [SYNC|ASYNC] [UNIQUE] ON (keys) [COVER (columns)]; a removal is DROP INDEX name. Impact is additive for an
addition and destructive for a removal.
A host that lists planning.index-renames pairs an index the table holds with
one the declaration states under another name and one definition, and sends
the pair as one rename step. The service claims it with
row-table-rename-index/N, a native AlterTable call with rename_indexes,
whose text form is RENAME INDEX old TO new. The step reads the table, drops
the old name and creates the new one. The index keeps its kind, its columns
and its partitioning settings, and is not built again (measured on 25.1.4.7
and 26.2.1.14). Impact is
behavioral: a query that names the old name stops reading through the index.
The host moves the index's comment to the new name with its own steps.
Execution refuses before it writes when the table no longer holds the index as
planned or holds an index under the new name. After the call it requires the
old name to be gone and the new one to hold the index as it was, settings
included. YDB renames an index only with EnableMoveIndex on, so a rename
step on a session without index_rename is refused when it is planned. A session
reports the capability only to a host that lists the feature; any other host
plans a rename as a removal and an addition.
The common indexes a source declares, such as a ptah:schema:index directive,
are compared by the host. Inspection reports a row table's global indexes as
its common indexes: a synchronous one, unique or not, with no method, and an
asynchronous one with the method async. For a host that lists
schema.index-methods, table preparation states the kind of each declared
index in the same spelling, reading its type with the host's embedded engine's
spellings: none, BTREE, GLOBAL, SYNC and GLOBAL SYNC are synchronous,
and ASYNC and GLOBAL ASYNC are asynchronous, in any case and with _ for a
space. The host compares the two spellings, so a declaration that changes only
an index's kind is a removal and an addition of the index under its name, which
this service carries out as the two steps above, the removal first. A host
without the feature compares no kind, and such a change plans nothing.
Index settings from a source
A Go ptah:schema:index directive, the index entry of a YAML table and the
index block of an HCL document state a global index's partitioning and read
replicas with the attributes the host's embedded engine reads there:
auto_partitioning_by_size, auto_partitioning_partition_size_mb,
auto_partitioning_by_load, auto_partitioning_min_partitions_count,
auto_partitioning_max_partitions_count and read_replicas_settings, with the
spellings a table directive takes. They decode into the index's own facet
(index-properties), owned by the index, and a value an index cannot take,
such as auto_partitioning_by_load="true" or a count of zero, is refused where
it was written. For a host that lists schema.observed-index-facets (contract
25.21, ADR 0068 in Ptah), inspection reports the same facet on each common
index, with every setting the server describes on it, defaults included, so
the host compares one owner's facet on both sides. A host that does not list
the feature reads no index's settings, and the provider refuses a declared
setting from it, since the comparison could not see what the index holds.
//ptah:schema:index name="by_body" fields="body" auto_partitioning_by_load="ENABLED" auto_partitioning_min_partitions_count="2"
- A setting the source leaves out keeps what the index holds, as the host's
embedded engine compares it. A differing setting is a
github.com/stokaro/ptah-ydb-plugin/row-table-properties-changeon the index, planned as therow-table-index-settings/Nstep above. - A table the plan creates is created with its indexes, and each index whose
facet declares settings then takes them in a
row-table-index-settings/Nstep after the creation, since no call that creates an index takes them. An index added to a table that exists takes them after its addition. - An index the plan renames in place keeps its settings: the host compares a
renamed index's facet under its new name, and a changed setting is a
settings step after the rename, as the host's embedded engine writes
RENAME INDEXand thenALTER INDEX ... SET. An index the plan drops is left alone. - Settings declared on an index that is not a global sync, async or unique index, or on one the table neither holds nor gains, are refused.
- A table rebuild gives each index of the new table its declared settings, and what the old index holds for every other (see Native table rebuild).
migrations generatewrites the reverse of a change of an index's settings into the down file: every setting the index held, restored in place (see Reverse migrations).- An export writes an index's facet as the settings that differ from the ones
YDB gives a new index, as the host's embedded engine writes them, so
ptah introspectwritesauto_partitioning_by_load="ENABLED" auto_partitioning_min_partitions_count="2"on the directive, and the export applies back with nothing planned.
The declaration is the index set model (table-indexes, desired side) as a
facet of the table, beside the table properties. It is managed when the
declaration states it: an index the table holds and the set leaves out is
dropped, and a declaration with no index set leaves the indexes alone. The
comparison reads the specification of each declared index: the name, the kind,
the ordered key columns and the ordered covered columns. The declared
implementation requirements on the server-owned index table are part of the
model and have to be valid, but they are not compared and not sent: YDB builds
that table itself.
- YDB cannot alter an index in place. An index the table holds under a declared name with another kind, other key columns, other covered columns or another key order is a change the planner refuses with a diagnostic that names both definitions. It does not plan a drop followed by an addition, because that rebuilds the index. Declare the removal and the addition in separate plans if a rebuild is wanted.
- The rules above are those of global sync, async and unique indexes. A vector
or full-text index follows its own (see
Vector and full-text indexes). An index to
add that states its partitioning or read replicas is added without them,
since no call that adds an index takes them, and a
row-table-index-settings/Nstep after the addition gives it the declared ones over the settings YDB gives a new index (automatic partitioning by size at 2048 MB, none by load, at least one partition, no read replicas; measured on 25.1.4.7 and 26.2.1.14), as the host's embedded engine writesALTER INDEX ... SETafterADD INDEX. Execution of that step requires the new index to hold exactly those settings. - A unique index added to a table that exists needs
unique_index_on_existing_table. YDB 25.1 and 25.2 never add one, and later lines keep it behindEnableAddUniqueIndex, off by default; without it the server refuses the call (Adding a unique index to an existing table is disabled) with an outcome the provider cannot confirm. So a session without the capability refuses such an addition when it is planned, in the host's embedded engine's words:adding unique index "<name>" to table "<table>", which exists already, which requires target capability unique_index_on_existing_table, unavailable on this ydb target. A table the plan creates takes its unique indexes with it and needs nothing. - An index reads columns the declaration states. A declared index that reads a column the declaration does not state is refused, and so is a kept index whose column the declaration drops.
- Ordering against column steps of the same table, whoever carries them out: an index is added after the addition of every column it reads, and dropped before the removal of every column it reads or covers. A column removal step that does not name its column is assumed to remove any column. Execution reads the indexes of the table before dropping a column as well, so a column an index still reads is refused before the call.
- The partitioning and the read replicas of an index the table holds, stated in
the
settingsof its global kind, change in place with one step,row-table-index-settings/N, that reads the table and alters the index. It runsALTER TABLE ... ALTER INDEX ... SET (...)through the query service, as the host's embedded engine writes it, because the native AlterTable call has no field for an index's settings. The rules are the table's: a setting the declaration leaves out keeps what the index holds, the statement names every partitioning setting the index ends with when the partitioning changes, and replicas declared as none are written asPER_AZ:0. A starting layout and partition columns are refused by name. The server describes an index's settings on the index itself (measured on 25.1.4.7 and 26.2.1.14), and the comparison reads them there. Impact is behavioral. - YDB builds a new index in the background. The provider waits for the index to read back as ready, bounded by ten minutes, before it confirms the step. Inspection refuses an index that is not ready, so a table with an index in the build is never compared.
Vector and full-text indexes
A vector index (vector_kmeans_tree) and a full-text index (fulltext_plain,
fulltext_relevance) are entries of the native index set, with their settings,
and the comparison, the planner and execution follow the rules of the host's
embedded engine for them:
- An index the set states and a table that exists does not hold is added with
one step,
row-table-add-index/N, of its own operation kind,row-table-native-index-operation. The nativeAlterTablecall of the pinned bindings carries neither kind, so execution runs YQL'sALTER TABLE ... ADD INDEXthrough the query service, as the host's embedded engine writes it:INDEX name GLOBAL USING vector_kmeans_tree ON (prefix, vector) [COVER (columns)] WITH (distance=cosine, vector_type=float, vector_dimension=3, levels=1, clusters=2), the metric spelledsimilarity=where the index names a similarity, andINDEX name GLOBAL USING fulltext_relevance ON (column) WITH (...)with the analyzers in name order and the language quoted. The TEXT form is the same statement. - An index the table holds and the set leaves out is dropped by the native
AlterTablecall,row-table-drop-index/N. - YDB changes nothing of a vector or full-text index in place (
ALTER INDEX ... SET (levels=2)answersUnknown table setting: levels). An index whose columns, kind or settings differ is dropped and added again under its name, the removal first, and a renamed index is dropped under its old name and added under its new one. The table is not rebuilt. A global index under the name of a vector or full-text one, or the other way round, is refused. - The comparison reads an index's name, its ordered key and covered columns, its kind and its vector or full-text settings, exactly; a full-text filter switched off is read as one the index does not name, because YDB applies no filter it is not told to. The settings of the tables that implement the index are not compared: the server reports its defaults for them on every index, and YDB changes none of them on a vector index that exists. An addition that states other settings for them than YDB gives a new index is refused.
- A vector index names one metric, a vector type, a dimension from 1 to 16384,
levels from 1 to 16 and clusters from 2 to 2048, with clusters to the power
of levels at most 2^30, the limits 25.3 and later hold every index to; 25.1
and 25.2 check none of them, and the planner holds every line to them, as the
host's embedded engine does. Its last key column is the vector, a
String; the others are its prefix. Overlap settings are refused by name. - A full-text index reads one
StringorUtf8column of a table whose key is oneUint64column (26.2 answersFulltext index requires primary key column ... to be of type 'Uint64'), analyzed by the one column entry of its settings, with a tokenizer. An n-gram filter names both its lengths, a language goes with the snowball filter, and the snowball filter takes no n-gram filter, as 26.2 answers. - A target without
vector_indexesrefuses a vector index before a statement is sent, one withoutvector_bit_typean index over bit vectors, and one withoutfull_text_indexesa full-text index (see Feature flags). A server answers each withVector index support is disabled,bit vector type is not supported(25.1, over rows),FULLTEXT_RELEVANCE index subtype is not supported(25.1) orFulltext index support is disabled(26.2 withEnableFulltextIndexoff). - Execution reads the table, requires the index to be absent and its columns to exist, runs the statement once, reads the table back until the index is ready, and requires it to hold the requested definition and everything else to be unchanged.
- A table rebuild of a table that declares or holds a vector or full-text index is refused: the new table is created with its global indexes alone.
Index declarations of the provider's types
The host's frontends build a common index from every index declaration. For a
host that lists source.index-kinds (contract 25.24), the provider claims the
index types it models outside the common model, and the host sends a
declaration of one of them whole, as one fragment, and builds no common index
for it: vector_kmeans_tree, fulltext_plain and fulltext_relevance, which
declare the table's native index set, and bloom_filter, bloom_ngram_filter
and min_max, which declare a column table's local indexes in its storage. A
type may be written with a space for each underscore and with GLOBAL USING or
LOCAL USING in front, in any case. The fragment carries the index's columns,
the columns it covers and a pgvector operator class (ops, which names a vector
index's metric as the host's embedded engine reads it: vector_cosine_ops is
distance=cosine, vector_ip_ops similarity=inner_product), and the
settings the index's own directive, entry or block writes:
//ptah:schema:index name="docs_emb" fields="gen,emb" include="body" type="vector_kmeans_tree" distance="cosine" vector_type="float" vector_dimension="3" levels="1" clusters="2"
//ptah:schema:index name="ft" fields="body" type="fulltext_relevance" tokenizer="standard" use_filter_lowercase="true"
//ptah:schema:index name="bf" fields="body" type="bloom_filter" false_positive_probability="0.01"
A YAML index entry takes the same keys, and an HCL index block the same
attributes, a count as a number. Every vector or full-text declaration of a
table makes one native index set, and every local one joins the column
storage its table directive or entry states. Refused by name: a unique index,
an index comment, a setting of another kind of index, an operator class on an
index other than a vector one, a local index of a row table, a vector or
full-text index of a column table, and an index declared twice. A setting the
index cannot take is refused with the host's embedded engine's words, at the
source for its grammar and at planning for its limits.
A Go or YAML source of such a host states the native index set of every table
it declares, so a table that writes none of these indexes is declared to have
none, and one the table holds is dropped; a column table's local indexes are
managed likewise. Without the feature a source cannot declare them, and a table
keeps the ones it holds. An export writes each index back as a declaration of
its type, spelled as the host's embedded engine wrote it in HCL: type = "GLOBAL USING vector_kmeans_tree" with the columns, the covered columns and
the settings, and a local index's method alone, type = "bloom_filter". The
embedded engine wrote no full-text or local setting in HCL, and refused the
full-text document it wrote; the provider writes and reads the full-text
settings there. HCL loses a column table's local indexes with its storage.
Execution
Execution reads the table first. A family operation requires every touched family and moved column to match the plan, and every family to add to be absent. An addition requires the column to be absent. A removal requires the column to exist and not to be a key column, an index column or stored column, the TTL column or a partitioning column; the server refuses each of those only after the call, which would leave the outcome uncertain, so the provider reads them first. A TTL operation requires the table to hold exactly the TTL the plan captured, or none, and a TTL to set must read a column the table holds with a type its mode reads. A settings operation requires the table to hold exactly the partitioning settings, the key bloom filter and the read replicas the plan captured for each group it changes. An index addition requires the index to be absent and to read columns the table holds, and an index removal requires the table to hold the index as the plan captured it. A change of an index's settings requires the table to hold the index as captured, settings included. A table that changed since planning is refused before the call.
After the call the provider reads the table back and checks that the requested change holds and that nothing else moved: other families and columns, column names and types, their order, the key, and the TTL. The TTL must read back as the one the request sets, run interval included, or as none after a removal, and as before for every other operation. After an index operation the added index must be ready and hold the requested kind and columns, a dropped index must be gone, and every other index must read back unchanged. After a settings operation the table must hold every partitioning setting the request names and the partition columns it held; the settings the server derives from them, such as the partition size it no longer reports once partitioning by size is off, are not compared. The key bloom filter and the read replicas must read back as the ones set, or as they were, and a request that names no partitioning setting leaves the partitioning as it was. After a change of an index's settings the index must keep its definition and be ready, and hold the settings by the same rules, while every other index, every column and the key read back unchanged. An added column must read back with its type, nullable and in the default family. A failed reply or unconfirmed read remains UNKNOWN and is never retried. As for creation, these checks do not establish exclusive ownership against another schema writer.
Live tests alter a table on YDB 25.1.4.7 and 26.2.1.14: families and column placements; adding columns, placing one in a family and dropping others in a single plan; changing each partitioning setting on its own and together, including switching partitioning by size off and on again; setting, changing, moving and removing a TTL, including the move of a TTL to an added column in the same plan that drops the column it read; and the refusal of an indexed column, of a TTL column the server would refuse, and of a stale alteration before the call; and adding and dropping sync, async, covering and unique indexes, with the refusal of a changed index definition. They set, change and remove the read replicas and the key bloom filter of a table, alone and with its partitioning, and the partitioning and read replicas of sync, async and unique indexes, including switching partitioning by size off and on again with a minimum the declaration leaves out, and refuse a stale settings plan before the call. The TEXT form of each change is run on a twin table and reads back the same. They plan again from the read back to show the declaration no longer changes anything, and check that rows survive.
Table property comparison
The offline models.compare service ydb-row-table-properties compares the
table and column properties of a row table that exists on both sides. It reports
one github.com/stokaro/ptah-ydb-plugin/row-table-properties-change for a table whose TTL, partitioning settings, read replicas,
key bloom filter or column families differ from the declaration, one for each column that sits in another
family, and one for a table whose index set differs from the declared one: an
index added, removed or changed, or an index whose partitioning or read
replicas differ from the ones it declares. An index change carries both complete index
sets and names them with a facet key, so a table can have a property change
and an index change at once. A changefeed set that differs is reported the same
way, with the changefeed set as its facet; a source that describes the
changefeeds of every table it declares states a table with none as one without
changefeeds (see Changefeeds). The comparison applies the rules of the alteration planner, so a
difference it reports is one the planner acts on, and a table that differs only
in its TTL, its settings or its families still reaches the planner. A change carries both
complete facets, replaces no owner, removes no state and leaves access
unchanged. A facet present on one side only states nothing to compare; the
planner decides what the missing read means. The planner accepts exactly these
changes, accounts for each one with the steps of its table alteration, and
refuses every other change kind.
The host names a table and its columns without a database, because they live in the session's, while the provider's inspection names that database in the owner of every facet it reads. The comparison and the planner read a facet whose subject names no database without its owner's, so both sides of one table name one owner. A facet whose owner is another table than its subject names is refused.
SQL schema reading
The provider serves schema.read-sql for the ydb engine in document mode.
ptah schema plan and ptah schema apply hand it the documents of a
--schema-file or of a schema directory of .sql files, and it reads them as
one load without a database session. The reader is a port of Ptah's own YQL
schema reader at commit a4e286dbf5ac42a609bd6ccd2b60dcc3335c210d, so a load
reads to the model Ptah's bundled YDB owner reads, and a load Ptah refuses is
refused with Ptah's words and at the position Ptah names.
The reply carries the tables with their keys, columns, defaults, serial
columns, comments and indexes; views; users, groups and memberships; grants and
revocations; coordination nodes as this provider's coordination model; and
topics with the consumers CREATE TOPIC and ALTER TOPIC ... ADD CONSUMER
declare, as this provider's topic model; and the data sources and external
tables CREATE [OR REPLACE] EXTERNAL DATA SOURCE and CREATE [OR REPLACE] EXTERNAL TABLE declare, as this provider's external object models; and
streaming query definitions as the streaming query model; and secrets whose
values are approved input references; and the async replications and transfers
CREATE ASYNC REPLICATION and CREATE TRANSFER declare, with what a later
ALTER ASYNC REPLICATION ... SET or ALTER TRANSFER ... SET of the load
changes folded in, as this provider's replication and transfer models. For a
host that lists schema.sql-facets (contract 25.21), it carries the
changefeeds ALTER TABLE ... ADD CHANGEFEED declares, with the consumers
ALTER TOPIC <table>/<changefeed> ADD CONSUMER adds to their topics, as one
facet of each table in this provider's changefeed model, the value a YAML
changefeeds: key of the table declares.
Under the same feature it carries the settings of a CREATE TABLE as the
facets a YAML or Go source declares for the same table (see
Offline row-table declarations), so a table
means the same in all three formats:
- a row table's partitioning (
AUTO_PARTITIONING_*), read replicas, key bloom filter, starting layout (UNIFORM_PARTITIONSorPARTITION_AT_KEYS), column families and TTL, as one row-table facet of the table; - for each column that
FAMILY <name>places in a family other than the default one, a column facet that names the family; - a column table's hash key (
PARTITION BY HASH), shard count (AUTO_PARTITIONING_MIN_PARTITIONS_COUNT) and TTL, as one column-storage facet. A TTL that only deletes is the storage's one deleting tier, as therow_deletion_*properties beside a YAML or Go column table are; - for a host that also lists
schema.observed-index-facets, the partitioning (AUTO_PARTITIONING_*) and read replicas a global sync, async or unique index declares in itsWITHclause, as one facet of the index, the value a Go index directive or a YAML index entry declares.
A setting the statement leaves out keeps what the table or the index holds, and a row table declared without a TTL has its TTL removed, as for a YAML or Go source.
The reply says what each document declares, traces every object to its
statement, and claims complete coverage of the
row-table, column, column-storage, coordination, topic, external data source,
external table, streaming query, secret, async replication and transfer models,
of the changefeed model under that feature, so a table a load declares
without a changefeed is one with none, of the index settings model where
the host also lists schema.observed-index-facets, and of the native index
model where it also lists source.index-kinds, so a table a load declares
without a vector or full-text index is one with none. A vector or full-text
index of a CREATE TABLE is no common index then: it reaches the host in the
native index set of its table, and a local index in the storage of its column
table, the values a YAML or Go source of the same index declares, so a YQL
schema renders, plans and applies them as the host's embedded engine did. A
ptah:not-described header entry of kind coordination_node, topic,
external_data_source, external_table, streaming_query, secret,
replication or transfer limits that claim, for one object or, without a
name, for the kind. An object the claim leaves out keeps the entries of its
access list too: the host plans no REVOKE on an object its source does not
describe.
A replication or a transfer is read as the host's reader reads it: the items
of FOR ... AS ..., ENDPOINT and DATABASE as the connection string they
spell, the lambda of USING byte for byte, and in an ALTER only the
settings YDB changes in place. A malformed statement is refused without naming
a token, since its connection clause may hold a credential written by mistake.
A limit names an object by its path from the database root, a dot being part
of a name, so app.events names a topic at the root and app/events one in
the directory app. A dotted limit of a topic, a secret or an external object
that names nothing the database holds, while the plan would drop the object
the same path written with a slash names, refuses the comparison with the
spelling that keeps that object, as the host's embedded engine refuses it. A
dotted limit that names an object the database holds is read as written.
A refused token is quoted with at most its first 64 bytes, where Ptah quotes it whole: a schema file can hold a secret in a string literal.
A statement that changes an object no document of the load declares is refused
in Ptah's words: an ALTER TABLE ... ADD CHANGEFEED of a table, a COMMENT ON
an object, an ALTER TOPIC ... ADD CONSUMER of a topic, an ALTER USER of a
user, or an ALTER SEQUENCE of a Serial the load does not declare. Under
schema.read-sql-undeclared (contract 25.38, ADR 0120 in Ptah) the refusal is
an UNDECLARED error, which the host reads as its own reader's refusal, so a
YQL file beside an HCL file that declares the table is told that the YQL files
change only what the YQL files declare, as the host's embedded engine said. A
host that does not list the feature gets an INVALID error with the same
words.
A load that declares something no reply field or model of this provider can carry is refused by name, with no partial reading:
- the settings of a table, for a host that does not list
schema.sql-facets, which has no home for a facet of a table; - a column family on a column table, and a row table that declares both
UNIFORM_PARTITIONSandPARTITION_AT_KEYS, with the words a YAML or Go source of the same table is refused with; - the partitioning and read replicas of an index, for a host that does not list
both
schema.sql-facetsandschema.observed-index-facets: without the second the comparison could not see what an index holds, as for a YAML or Go source; - a vector, full-text or local index, for a host that does not list both
schema.sql-facetsandsource.index-kinds; - a local index of a row table, a vector or full-text index of a column table, and a comment on one of them, with the words a YAML or Go source of the same index is refused with, and a vector index's partitioning, which the native index set does not hold;
- changefeeds and the consumers of their topics, for a host that does not list
schema.sql-facets, which has no home for a facet of a table. The load then claims nothing about changefeeds and leaves those of its tables as they are; - a user's password, for a host that does not list
source.sql-role-passwords: the reply's role carries none, so the host would create the user without it; - a grant on a resource pool whose name holds a dot, a quote or a bracket: Ptah names a pool it reads from a provider as it names a table, so the grant would come back on a renamed pool.
Under source.sql-role-passwords (contract 25.33, ADR 0112 in Ptah) the reply
carries the password a document gives a user as the document writes it: a
literal after PASSWORD, a YDB password hash after HASH, or the
$PTAH_SECRET_... reference a migration file Ptah generated names it by. The
host binds it as it binds a password a YAML or Go source declares, so the plan
names the user's execution input and no value (see Common
objects), and execution writes HASH for a hash, as the
host's embedded engine did. Measured on 25.1.4.7 and 26.2.1.14: a user
created from PASSWORD 'x' logs in with x, one created from HASH logs in
with the password the hash stands for, and the next run plans nothing.
Some of what a reading carries is not planned yet. Table creation refuses
column defaults, and table comments on a host that does not list
schema.common-objects, which carries them. Ptah refuses views and roles from
a provider's reading. A coordination node is planned as the coordination
services describe.
internal/yqlschema/testdata holds the differential corpus: every load of YQL
schema documents Ptah's own tests read, the YDB fixtures Ptah renders, and edge
cases, each with the answer Ptah's reader recorded. The unit suite compares the
provider's reading with each answer, refusal for refusal. The corpus README says
how the answers are recorded again.
Offline SQL analysis
The SQL analysis service reads default-mode YQL without a database session. It preserves source byte ranges and line numbers, original tokens, and quoted case. Semicolons inside quoted or multiline literals, comments, action definitions, inline actions, and lambda bodies remain inside their enclosing statement. Leading and trailing comments stay in the returned source ranges.
The scanner and value decoder were extracted from Ptah's YQL implementation, with its lexical regression corpus. The provider keeps no host-package import. Live tests compare reconstructed statements with results from both supported YDB test lines, including escaped quotes, multiline strings, compound bodies, and numeric suffixes.
Native safety judgments cover topic and consumer positions, transfer consumers, replica tables, coordination state, secrets, external data access, and streaming checkpoints. Common table creation and a single ADD COLUMN clause retain the host's common DDL assessment. Unmodeled effects, including arbitrary compound bodies and changes to automatic expiration, report UNKNOWN and require review. The analysis does not replace the server's full SQL grammar validation.
Malformed literals, incomplete compound bodies, invalid UTF-8, NUL, and unsupported
translation settings refuse the whole batch. Only the default lexer and
--!syntax_v1 are accepted; ANSI mode is refused even when its directive appears
later in a source, because splitting could move it to a new query's head.
Whitespace-only sources are accepted. A source containing only comments or
terminators is refused because the SDK requires tokens for every
non-whitespace source range. No prefix of a refused batch is returned.
A request contains at most 256 sources and 3 MiB. Each source contains at most 65,536 significant tokens, and the complete response is bounded by 3 MiB. Cancellation returns no analysis. The native CLI still requires integration of YDB's table comparison rules; the provider does not claim the common table identity contract to bypass that requirement.
Offline capabilities
The provider lists engine.default-capabilities (contract 25.27) and states
the facts an offline command plans ydb with: those a session states for a
26.2 server whose address names no monitoring endpoint, with the facts of the
common objects. Each release line states its own the same way, and a
--server-version selects the line it equals or the longest line it starts
with followed by a dot, so 26.2.1.14 selects 26.2. ptah schema render, sql lint and the other commands that plan without a database read them. A command
that connects plans with the session's facts.
The facts also carry the keys the host reads and no session states, with the
values of the built-in engine's presets, on every line. The host reads them
through its capability registry when it plans a connected command too:
index_covering_columns, for one, makes an unchanged list of covered columns
compare as unchanged, so a second apply of the same schema plans nothing for
the index.
A host of an earlier contract receives no facts, plans offline against unknown
facts, and the provider refuses what needs one, such as a table creation,
which needs nontransactional DDL established.
Text scanning
The provider lists sql.scan (contract 25.26). ScanSQL returns the tokens of
one YQL text with the same scanner, so the host reads YQL wherever it reads SQL
text without parsing it: the lineage of view bodies, the indentation of the
sql template function, and the scans of migration bodies. Both quote styles
and @@...@@ delimit strings, a type suffix belongs to its string, and a
backticked name carries the path it stands for. A --! comment is a setting
the server reads only at the head of a text; the host clears head for a range
inside one, where the comment is a comment. A string whose escape YDB refuses
carries no value. A semicolon is a SEMICOLON token only where it ends a
statement of the text: one that ends a statement inside a DEFINE ACTION or
DEFINE SUBQUERY body, a DO BEGIN block or a lambda is an OPERATOR, so the
host's statement splitter keeps such a body whole, as Ptah's built-in YDB
engine split it. A host of an earlier contract never calls the RPC and reads
YQL by its shared rules.
Migration lint
The provider serves the SDK's lint.migrations service for the ydb engine
without a database session. It publishes its rules as data, with the codes,
titles and severities of the host's built-in YDB owner, whether a rule also
reads the down half of a migration, and the shared rules a finding replaces on
the same statement.
A split reads each migration file as YQL, with the lexer the SQL analysis uses. It returns every statement's byte range, line, words and the comments directly above it, which the host reads suppression directives from. It also reports the facts the host's shared rules read: a drop that loses state (a topic, a transfer, a coordination node, a streaming query, a replication dropped with CASCADE), YQL's unique index, the steps of a table rebuild, and a safety judgment for the statements only YDB's owner recognizes. The splitter reads what a rebuilt table holds from the directory itself: the up migrations of lower versions in version order, then the statements of the file before the rebuild. An annotation judges the statements a host split without a dialect, and declines a statement YQL would split differently.
A check judges every file of a run under the rules the request enables. A rule reads the statement text and the capability map the request carries, never the server version. The rules that need to know what a table looks like before a statement read it from the directory itself, as the splitter does: whether an index or the TTL uses a column, a table's partitioning settings, the views that read it, its changefeeds and column families, the Serial columns and sequence restarts, failed-over replications, and the comments Ptah's COMMENT ON statements left. A table created outside the directory is unknown and reports nothing.
internal/migrationlint/testdata holds the host's answers for a corpus of
migration directories and requests, recorded at one host commit, and the tests
require the same statements, facts, annotations and findings, message for
message. Its README says how to record them again.
Statement facts for ptah sql lint
For a host that lists lint.statement-facts, which the engine lists beside
lint.migrations, a split also says what each statement is, which table it
creates without a primary key, and which capabilities it needs. ptah sql lint reads a YDB file through these facts when this provider serves the
engine, in place of the host's built-in YQL reading, and reports what that
reading reports:
- a CREATE TABLE or an ALTER TABLE is examined; any other CREATE, ALTER or DROP is a schema statement no rule examines, named by its first two words;
- any other statement changes no schema object and is named by its first word, or as a named expression;
- a CREATE TABLE without a PRIMARY KEY clause names its table, and a table written as a named expression keeps the name as written;
- the capabilities are those the migration lint rules read: a unique index or a column with a default added to an existing table, a vector index, and a vector index over bit vectors. Each names the index or the column and the clause that needs it. The host compares the key with the capabilities of the server version it lints for.
A statement that starts with no word, such as a string or a parenthesized query, is an expression the linter does not lint, and the first statement of a file that opens with a UTF-8 byte order mark is read like any other. The built-in reading skips both and reports nothing, which lets such a file read as clean; the provider is stricter on purpose.
internal/migrationlint/testdata/statementfacts holds YQL files with the
built-in reading's findings on three release lines, recorded at one host
commit. The unit tests and an end-to-end run of the host's ptah sql lint
through this provider require the same rules at the same positions about the
same tables and capabilities. Its README says how to record them again.
A host that does not list the feature is offered none of these facts, and an annotation request that asks for them is refused.
The host owns output formats, severity overrides, suppression and the rules every dialect shares. The host's built-in owner reports nothing from these rules on a run that names no dialect; the provider is only asked about its own engine, so a host does not send it the codes of such a run.
Rows, row writes and seeds
The provider serves the row services of the contract (data.type-rows,
data.read-rows, data.render-writes) and the seed service (data.seeds),
with the answers of the host's embedded YDB engine: its row typing
(internal/ydbdata), its row diff statements (migration/datadiff) and its
seeds (migration/seeder and the YDB seed service). The host keeps which rows
are declared, the row diff, the ordering and the seed policy.
The engine declares the host's YDB query dialect for the host's query
builder, and a session reports the capabilities the builder reads, at the
host's line presets: returning_clause from 25.3, since 25.1 and 25.2 fail
UPDATE ... RETURNING on a table with a unique index (wrong returning expr type), and common_table_expressions, correlated_subqueries,
non_equi_joins and offset_without_limit on no line. The builder refuses a
clause whose key a session leaves out.
YQL types every literal and converts few of them, so every value is compared
and written in its column's YDB type: the live table's type where the column
exists, and otherwise the type the declared type lands on for the request's
capabilities (VARCHAR(255) is Utf8, TIMESTAMP is Timestamp64 with
wide_date_time_types and Timestamp without it, DECIMAL(10,2) needs
parameterized_decimal). TypeRows returns the declared and the live rows in
one canonical form, so a declared 12.50 pairs with the 1.5 a Decimal reads
back, a moment in another zone with the one in UTC a read returns, PT26H
with the interval of the same length and an upper-case UUID with the lower-case
one YDB stores. A Decimal is a decimal at its column's scale, a Date a date, the
other date and time types a timestamp, an Interval a count of nanoseconds,
String and Yson bytes, Json and JsonDocument JSON text (JsonDocument with sorted
keys and no spaces), and Utf8, Uuid and DyNumber text. A value its column
cannot hold refuses the request with the host's words, and a column no side
types keeps its values.
RenderRowWrites writes the host's statements: INSERT INTO with the columns
sorted by name, UPDATE ... SET ... WHERE with the columns outside the key
sorted and the key predicate in sorted order, DELETE FROM ... WHERE, and a
key column holding null as IS NULL. An UPSERT is YQL's UPSERT INTO in the
shape of the INSERT; the host's row diff writes none. A value a column cannot
hold, or a column the write names no type for, refuses the batch with
UNSUPPORTED_VALUE and a message that names the write and the column but never
the value.
ReadRows reads one page of a row or column table in the order of the
requested key columns and then of the primary key columns the request leaves
out, so the order is total, and resumes after the last row of a page through
its token. YDB orders a null first, and the cursor steps past nulls and equal
keys. A page holds at most the requested rows and 3 MiB. A missing table is a
complete page that says so. A Decimal value with more digits than its
precision, which is how YDB stores infinity and NaN, fails the read rather than
being returned as digits the column never held.
A seed file runs as the queries YQL reads it into, in one serializable
transaction with its ledger row, so the file and its row commit together or not
at all. A transaction YDB aborts for a conflicting one runs again, at most five
times. A file holding a scheme statement, a BATCH UPDATE or BATCH DELETE, a
translation setting other than --!syntax_v1 or a delimiter directive is
refused before anything runs, with the position of the statement, and so is a
statement that leaves the dev realm of a realm session. A file that hits an
existing key is KEY_CONFLICT, and nothing of it stayed. A failed commit that
YDB did not answer with a key conflict or an abort is UNKNOWN.
The ledger is the host's table schema_seeds at the root of the session's
database, created with the host's statement, so a ledger the embedded engine
wrote is read and extended here, and the other way round. A table of that name
with another layout is refused and never adopted. A missing ledger reads as
missing and is not created by a read.
The engine also declares the query-builder spelling the host's YDB descriptor
states (query.dialect): named parameters, YQL quoting with paths, unsigned
row bounds, UPSERT INTO, and the capabilities the builder assumes for YDB
when its caller names no target.
internal/rowdata/testdata holds row cases and the host's answers for them,
and internal/yqlquery/testdata YQL texts and the queries the host splits each
into; the README there says how to record them again. The one difference is
the wording of a write refusal, which names the value in the host's message and
never in the provider's. A live test writes rows of every value kind through
the services, reads them back in pages and types both sides into one form; the
seed tests run in a dev realm and read a ledger written with the host's
statements. Against a Ptah whose schema apply and ptah seed serve declared
rows and seed files through a provider, the end-to-end tests run both through
this one in a dev realm and read the table back.
Embedding store
The provider serves the SDK's embedding store (embeddings.store, ADR 0032)
for the ydb engine, for a host that lists the feature too; the engine
declares the immutable consistency mode. YDB has no triggers, so the outbox
mode, which records each source change in the transaction that makes it, is not
offered. The host keeps the lifecycle of ptah inference; the provider stores
what the host decided and refuses nothing for a lifecycle reason.
The layout is the host's embedded YDB engine's (internal/embedydb), so a
database either one wrote runs on through the other:
- the run state is the four tables
ptah_embedding_generation,ptah_embedding_run,ptah_embedding_eventandptah_embedding_pointerat the root of the session's database, with the host's columns, keys and nullability, a timestamp as Timestamp64 on 25.2 and newer and as Timestamp on 25.1; an existing table of another shape is refused withNOT_OWNEDand never altered; - a generation beside its source adds the vector column, a nullable
String, and fourUtf8columns named by the suffixes_generation,_input_hash,_source_versionand_state; a chunked generation keeps a table of its own keyed by the source key and<column>_chunk_ordinal, marked as the generation's by its comment; - a vector is stored as the bytes
Knn::ToBinaryStringFloatproduces, and the index is avector_kmeans_treewith the metric'sdistanceorsimilarityand the specification'slevelsandclusters.
A compare-and-write (MutateEmbeddingState, CommitEmbeddingBatch) is one
serializable YDB transaction that reads what it compares, compares the token,
writes and commits. A transaction YDB aborts, or a server that is briefly
overloaded or unavailable, runs again, at most twelve times; a commit that fails
any other way is UNKNOWN. A token is a digest of the records read, so a
record that changed or appeared gives another one. Written records come back as
a later read returns them: optional text empty, times in UTC to the
microsecond.
A batch makes each written key's rows equal to its set: on the source's
columns an UPDATE by the key, so a key the source no longer has creates no
row, and in a table of its own an UPSERT that selects the key out of the
source, with the rows past the set's end removed and a tombstone removing them
all. A write into a generation whose index exists is refused as UNSUPPORTED
on 25.2 and older, whose vector index answers a search as the table stood at
its build.
A source is paged by its primary key, which the specification's key fields
have to be. The verification walk reads both sides in one query per page, one
table read once or a full join of two, and never splits a key's rows across
pages. A search asks twice, through the index with VIEW and by exhaustive
scan. Building an index needs vector_indexes, which 25.1 has with
EnableVectorIndex on; the provider reads the flag where the address names the
monitoring endpoint. A drop checks ownership before anything goes: a table by
its mark, columns by their comment and by holding no other generation's rows.
internal/embedstore/testdata holds the host's answers for the run-state
tables on 25.1 and 26.2, a corpus of specifications and a set of vectors, and
the tests require the same statements, objects or refusals, and bytes. Its
README says how to record them again. The live tests run the store on both
lines in dev realms. Against a Ptah build they read a database the host's
embedded engine prepared, backfilled and indexed, search it through the
engine's index, and append an event the engine then reads; and against a Ptah
whose ptah inference takes --provider-config they run the host's YDB
inference flows through this provider: two generations from plan to cutover, a
rollback and a retirement, a chunked generation in a table of its own, the
refusals, and the answers an agent reads, which carry no source content.
Offline schema rendering
The provider uses schema.declarations to create authored coordination nodes,
topics, external sources and tables, resource pools, classifiers, secrets and
streaming queries. The host
validates the whole schema and combines these creations with table and common
object steps before rendering SQL. Declaration operations retain their desired
definitions and contain no observed-state claims.
A rendered schema opens no session. Capability limits, conflicting object paths,
duplicate classifier ranks, and explicit native access requirements refuse the
whole batch. Use live schema planning for access requirements that a CREATE
statement cannot express. External tables follow their declared data sources;
classifiers follow their declared resource pools. External sources follow their
declared secrets. A streaming query follows each topic and table its text reads
or writes, since YDB compiles a query when it creates it. A path the text names
by its database is read below the database the query's value names, the one a
read took it from, as schema inspect --format sql renders it, and the text
stays as written. A value that names no database, such as a query a source
declares for an offline render, cannot place such a path and is refused.
Secret statements retain approved input references; rendering opens no session
and reads no input value.
Under render.omissions (contract 25.33, ADR 0110 in Ptah) a render reports
what its statements go on without, as the host's embedded engine reported it,
and the host carries the omissions into the result of a schema render and of a
node render, where a caller that must not lose anything refuses the rendering.
They are the part of a created column's declared type YDB does not keep, such
as VARCHAR(255): length 255 for a Utf8 column or TIMESTAMP(3): precision 3 (YDB keeps microseconds), in either form, and in the TEXT form of a creation
that names no database, such as an offline render's, the start and the
increment of a Serial's sequence: only an ALTER SEQUENCE naming the sequence
by its absolute path sets them, so the table is written without them, where a
plan against a database writes the statement (see Serial columns).
Each names its column <directory>.<table>.<column>, as the host did. The
creation carries the declared type a column keeps only in part, since the
operation holds the YDB type alone.
Operator statements and object scopes
The sessions.statements service runs operator checks, migration test
assertions and script steps on a pinned connection. It returns typed rows in
bounded pages and leaves the affected-row count absent because YDB does not
report it. A result ends at its last page, explicit close, transaction end or
session close. Cleanup failures remain failures on a repeated close.
A read-only request proves that its single SELECT reads local tables or views, then runs in a snapshot read-only transaction. External tables, views that reach them, UDFs, code evaluation and file or secret functions are refused before execution. A transport failure has an unknown outcome and is never retried. Confirmed atomic query failures preserve the server's issue text for expected-error assertions, without the driver's address and transport wrapper. Bound arguments retain JSON, decimal, date, timestamp and interval types; precision the engine cannot represent is refused before execution.
The provider offers sessions.typed-arguments. A host that lists it sends each
argument with the type its caller bound it at, and the provider binds the
parameter at that YDB type: INT8 to INT64 as Int8 to Int64, UINT8 to
UINT64 as Uint8 to Uint64, FLOAT32 as Float, FLOAT64 as Double,
STRING as Utf8, BYTES as String, TIMESTAMP as Timestamp and
INTERVAL as Interval. An optional type binds as Optional<T> and its null
as a null of T, a list as List<T> (an empty one keeps its element type),
and an untyped null as Void, as a plain null binds. So a Go int32 writes an
Int32 column, which an Int64 parameter cannot, and a nil *string writes a
null into an Optional<Utf8> one. A typed timestamp outside the Timestamp
range is refused rather than bound as a Timestamp64.
The negotiated scope.selections declaration makes secrets, streaming queries,
coordination nodes, topics, external data sources, external tables, async
replications and transfers selectable by their directory and name. Resource
pools and classifiers use database-wide names, including literal dots. A kept
classifier or streaming query keeps the pool it requires when the input
contains that pool, the pool default for a query that names none; an
explicit exclusion of the pool still wins. A table's changefeeds go with the
table. An external table keeps no data
source and a data source no secret: an --include of a table plans that
table alone, whether it is created or dropped, as the host's embedded engine
did. The host keeps a requirement on each side of a comparison alone, so a
kept source would be dropped with a table the declaration leaves out, and
created again beside a held one. Grants follow the object their
target names: each kind except the classifier declares the grant object type
the host's embedded engine names it with, such as ASYNC REPLICATION or
TRANSFER. A grant on a type no selection declares is read as a grant on a
table of the same name, so a scoped plan would leave it out.
Extraction still required
The source reference is Ptah commit 7b4b19f49fadf03ce81def2f6168ea60b3261a71.
Remaining work includes more source formats, comparison, the alteration of table
settings other than column families, planning, rendering and operation models for other objects,
and the capabilities of the feature flags the host plans with. The provider advertises implemented
inspection, normalization, coordination YAML, Go and HCL source decoding and encoding, index entry key claims, comparison, planning,
rendering, text units, table rebuilds the planning policy asks for, views and comments on the common wire, SQL, offline schema validation, native coordination, topic, external object, resource pool, streaming query, async replication, transfer and secret lifecycles, approved execution inputs, row-table creation and row-table alteration execution, transaction, history, metadata,
lock, offline SQL analysis, SQL schema reading, migration lint, migration splits, row and seed services, dev database cleanup, replay guard, embedding store and object count services. The remaining
capabilities stay unadvertised.
The host's embedded implementation still needs removal when these operations are wired through this executable. Provider release workflows, default Ptah bundling, and independent installation acceptance remain pending. No v1 stability or full YDB support is claimed by this development state.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
provider-manifest
command
Command provider-manifest prints the ptah-provider.json document published beside each release: the provider identity, the oldest provider contract a host must implement to start it, and the archives the release carries.
|
Command provider-manifest prints the ptah-provider.json document published beside each release: the provider identity, the oldest provider contract a host must implement to start it, and the archives the release carries. |
|
ptah-plugin-ydb
command
Command ptah-plugin-ydb serves YDB through the experimental Ptah protocol.
|
Command ptah-plugin-ydb serves YDB through the experimental Ptah protocol. |
|
release-lines
command
Command release-lines prints the release lines the provider declares as the JSON document published beside each release.
|
Command release-lines prints the release lines the provider declares as the JSON document published beside each release. |
|
internal
|
|
|
authored
Package authored keeps the text the provider writes for an operator apart from the text of the errors it receives.
|
Package authored keeps the text the provider writes for an operator apart from the text of the errors it receives. |
|
backend
Package backend implements the YDB provider's experimental services.
|
Package backend implements the YDB provider's experimental services. |
|
buildversion
Package buildversion says which release of the provider this binary is.
|
Package buildversion says which release of the provider this binary is. |
|
changefeeds
Package changefeeds reads a YDB changefeed as the specification Ptah's embedded YDB engine keeps for one, and writes the statements that add, drop and change one.
|
Package changefeeds reads a YDB changefeed as the specification Ptah's embedded YDB engine keeps for one, and writes the statements that add, drop and change one. |
|
changefeedsource
Package changefeedsource decodes the changefeeds a YAML document or a Go file declares for its tables, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML table key changefeeds, and the Go directives ptah:schema:changefeed and ptah:schema:changefeed:consumer.
|
Package changefeedsource decodes the changefeeds a YAML document or a Go file declares for its tables, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML table key changefeeds, and the Go directives ptah:schema:changefeed and ptah:schema:changefeed:consumer. |
|
cmd/lintcontract
command
Command lintcontract reports how this module's .golangci.yml departs from the host's lint contract, one line per departure, and exits 1 when it does.
|
Command lintcontract reports how this module's .golangci.yml departs from the host's lint contract, one line per departure, and exits 1 when it does. |
|
cmd/native-descriptors
command
Command native-descriptors exports the pinned YDB dependencies for protoc.
|
Command native-descriptors exports the pinned YDB dependencies for protoc. |
|
cmd/replycut
command
Command replycut is a test harness: it serves a Ptah provider by passing every call to the provider executable REPLYCUT_PROVIDER names, and loses the reply of one call the way a network that drops it after the provider answered does.
|
Command replycut is a test harness: it serves a Ptah provider by passing every call to the provider executable REPLYCUT_PROVIDER names, and loses the reply of one call the way a network that drops it after the provider answered does. |
|
columnsemantics
Package columnsemantics compares captured YDB column definitions without I/O.
|
Package columnsemantics compares captured YDB column definitions without I/O. |
|
columnstore
Package columnstore decodes the monitoring description of a YDB column table together with the table service's description, without network access.
|
Package columnstore decodes the monitoring description of a YDB column table together with the table service's description, without network access. |
|
commonobject
Package commonobject carries the YDB objects beyond tables that travel on the common provider wire: views, principals, permissions and comments.
|
Package commonobject carries the YDB objects beyond tables that travel on the common provider wire: views, principals, permissions and comments. |
|
connection
Package connection owns native YDB connections and their credentials.
|
Package connection owns native YDB connections and their credentials. |
|
coordinationcompare
Package coordinationcompare compares captured YDB coordination states offline.
|
Package coordinationcompare compares captured YDB coordination states offline. |
|
coordinationplan
Package coordinationplan lowers captured coordination changes into a shared graph.
|
Package coordinationplan lowers captured coordination changes into a shared graph. |
|
coordinationsql
Package coordinationsql writes and reads the statements Ptah keeps a YDB coordination node with.
|
Package coordinationsql writes and reads the statements Ptah keeps a YDB coordination node with. |
|
declaration
Package declaration interprets YDB source settings without database access.
|
Package declaration interprets YDB source settings without database access. |
|
declarationplan
Package declarationplan creates YDB objects from authored definitions without asserting that a live catalog contains or lacks them.
|
Package declarationplan creates YDB objects from authored definitions without asserting that a live catalog contains or lacks them. |
|
devclean
Package devclean lists and empties a YDB dev database, and removes the dev realm a run created, as the Ptah host's embedded YDB engine does (internal/dbschema/ydb, Writer.ResetObjects, DropDatabaseRealm and RemoveRealm).
|
Package devclean lists and empties a YDB dev database, and removes the dev realm a run created, as the Ptah host's embedded YDB engine does (internal/dbschema/ydb, Writer.ResetObjects, DropDatabaseRealm and RemoveRealm). |
|
devdatabase
Package devdatabase provisions disposable YDB databases for the provider's database.dev service.
|
Package devdatabase provisions disposable YDB databases for the provider's database.dev service. |
|
embedstore
Package embedstore is the YDB provider's embedding store (ADR 0032): the run state of `ptah inference` in row tables of the provider's layout, and a generation's storage, rows, vectors, index and searches.
|
Package embedstore is the YDB provider's embedding store (ADR 0032): the run state of `ptah inference` in row tables of the provider's layout, and a generation's storage, rows, vectors, index and searches. |
|
externalcompare
Package externalcompare compares the declared and the read external data sources and external tables offline, in one batch, as Ptah's embedded YDB engine compares them: a data source the plan drops and creates again takes the external tables over it along, and a plan that drops a data source a declared table reads is refused.
|
Package externalcompare compares the declared and the read external data sources and external tables offline, in one batch, as Ptah's embedded YDB engine compares them: a data source the plan drops and creates again takes the external tables over it along, and a plan that drops a data source a declared table reads is refused. |
|
externalpath
Package externalpath resolves YDB external-object dependencies.
|
Package externalpath resolves YDB external-object dependencies. |
|
externalplan
Package externalplan lowers captured changes of external data sources and external tables into the host's shared graph, in one batch, as Ptah's embedded YDB engine plans them: a data source the plan drops and creates again takes the external tables over it along, tables are dropped before the data sources they read and created after, the TTL of a column table the plan keeps lets go of a data source the batch replaces and is set again after, and a statement the target cannot take refuses the whole batch.
|
Package externalplan lowers captured changes of external data sources and external tables into the host's shared graph, in one batch, as Ptah's embedded YDB engine plans them: a data source the plan drops and creates again takes the external tables over it along, tables are dropped before the data sources they read and created after, the TTL of a column table the plan keeps lets go of a data source the batch replaces and is set again after, and a statement the target cannot take refuses the whole batch. |
|
externals
Package externals reads YDB's external data sources and external tables as the declarations Ptah's embedded YDB engine keeps for them, compares them as that engine compares them, and writes the statements that create, replace and drop them.
|
Package externals reads YDB's external data sources and external tables as the declarations Ptah's embedded YDB engine keeps for them, compares them as that engine compares them, and writes the statements that create, replace and drop them. |
|
externalsource
Package externalsource decodes the external data sources and external tables a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document keys external_data_sources and external_tables, and the Go directives ptah:schema:externaldatasource and ptah:schema:externaltable.
|
Package externalsource decodes the external data sources and external tables a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document keys external_data_sources and external_tables, and the Go directives ptah:schema:externaldatasource and ptah:schema:externaltable. |
|
featureflags
Package featureflags reads the feature flags a YDB database runs with from the monitoring endpoint's page and turns the flags that gate a capability the provider owns into that capability's value in the session.
|
Package featureflags reads the feature flags a YDB database runs with from the monitoring endpoint's page and turns the flags that gate a capability the provider owns into that capability's value in the session. |
|
history
Package history owns the YDB revision storage format and exact row codec.
|
Package history owns the YDB revision storage format and exact row codec. |
|
inspect
Package inspect reads native YDB descriptions without SDK type coercion.
|
Package inspect reads native YDB descriptions without SDK type coercion. |
|
lintcontract
Package lintcontract compares this module's golangci-lint configuration with the host's, so the provider stays held to the host's lint contract.
|
Package lintcontract compares this module's golangci-lint configuration with the host's, so the provider stays held to the host's lint contract. |
|
locking
Package locking owns YDB's coordination sessions and lease freshness checks.
|
Package locking owns YDB's coordination sessions and lease freshness checks. |
|
metadata
Package metadata owns exact YDB migration journal and tag storage.
|
Package metadata owns exact YDB migration journal and tag storage. |
|
migrationlint
Package migrationlint is the YDB provider's migration lint: the splitter that reads a YDB migration as YQL, the facts it reports about each statement, the annotation of a split the host made without a dialect, and the YD family of rules, published as data and checked once per lint run.
|
Package migrationlint is the YDB provider's migration lint: the splitter that reads a YDB migration as YQL, the facts it reports about each statement, the annotation of a split the host made without a dialect, and the YD family of rules, published as data and checked once per lint run. |
|
migrationunits
Package migrationunits splits a YDB migration body into the units the host runs it as under the contract's units.migration-split feature (ADR 0054).
|
Package migrationunits splits a YDB migration body into the units the host runs it as under the contract's units.migration-split feature (ADR 0054). |
|
monitoring
Package monitoring reads JSON pages from the monitoring endpoint a YDB address names in its monitoring parameter: the feature flags of a database and the scheme description of a column table.
|
Package monitoring reads JSON pages from the monitoring endpoint a YDB address names in its monitoring parameter: the feature flags of a database and the scheme description of a column table. |
|
nativeapi
Package nativeapi preserves complete native table descriptions across readers.
|
Package nativeapi preserves complete native table descriptions across readers. |
|
objectcompare
Package objectcompare compares the declared and the read standalone YDB objects of one model kind offline, such as coordination nodes: it pairs both sides by path, decides from coverage whether an absence is established, and asks the object's family what a pair means.
|
Package objectcompare compares the declared and the read standalone YDB objects of one model kind offline, such as coordination nodes: it pairs both sides by path, decides from coverage whether an absence is established, and asks the object's family what a pair means. |
|
objectplan
Package objectplan lowers captured changes of standalone YDB objects, such as coordination nodes, into steps of the host's shared graph: one native operation per change, ordered against the common steps and captured tables that occupy the same scheme path.
|
Package objectplan lowers captured changes of standalone YDB objects, such as coordination nodes, into steps of the host's shared graph: one native operation per change, ordered against the common steps and captured tables that occupy the same scheme path. |
|
ownedtable
Package ownedtable validates provider-owned native table storage.
|
Package ownedtable validates provider-owned native table storage. |
|
readproof
Package readproof proves that an operator's YQL query reads only local database objects before it runs in a snapshot read-only transaction.
|
Package readproof proves that an operator's YQL query reads only local database objects before it runs in a snapshot read-only transaction. |
|
releaseline
Package releaseline reads the YDB release line a server reports, so a capability that arrived in a release can be decided from the version a session measured.
|
Package releaseline reads the YDB release line a server reports, so a capability that arrived in a release can be decided from the version a session measured. |
|
releasemanifest
Package releasemanifest renders the release lines a provider declares as the JSON document published beside each release, so a host can read them without starting the provider.
|
Package releasemanifest renders the release lines a provider declares as the JSON document published beside each release, so a host can read them without starting the provider. |
|
replication
Package replication owns the rules of a YDB async replication and of a transfer: what a declaration may say, what YDB keeps differently from how it was written, when two descriptions are the same, and the statements that create, change and drop one.
|
Package replication owns the rules of a YDB async replication and of a transfer: what a declaration may say, what YDB keeps differently from how it was written, when two descriptions are the same, and the statements that create, change and drop one. |
|
replicationcompare
Package replicationcompare compares the declared and the read YDB async replications and transfers offline, as Ptah's embedded YDB engine compares them: each side resolved as YDB keeps it, so a declaration naming a default and one leaving it out are the same object, and a replication's items compare by the tables they replicate.
|
Package replicationcompare compares the declared and the read YDB async replications and transfers offline, as Ptah's embedded YDB engine compares them: each side resolved as YDB keeps it, so a declaration naming a default and one leaving it out are the same object, and a replication's items compare by the tables they replicate. |
|
replicationplan
Package replicationplan lowers captured async replication and transfer changes into the host's shared graph: one step per object, refused where YDB cannot make the change in the state the object reported, where the target lacks a capability the declaration needs, or where the plan's tables would break what a replication owns.
|
Package replicationplan lowers captured async replication and transfer changes into the host's shared graph: one step per object, refused where YDB cannot make the change in the state the object reported, where the target lacks a capability the declaration needs, or where the plan's tables would break what a replication owns. |
|
replicationsource
Package replicationsource decodes the YDB async replications and transfers a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document keys async_replications and transfers, each a mapping of entries keyed by name, and the Go directives ptah:schema:async_replication, ptah:schema:async_replication:item and ptah:schema:transfer.
|
Package replicationsource decodes the YDB async replications and transfers a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document keys async_replications and transfers, each a mapping of entries keyed by name, and the Go directives ptah:schema:async_replication, ptah:schema:async_replication:item and ptah:schema:transfer. |
|
reversal
Package reversal undoes the changes the provider's comparisons return, for the reverse migration the host writes beside a forward one.
|
Package reversal undoes the changes the provider's comparisons return, for the reverse migration the host writes beside a forward one. |
|
rowdata
Package rowdata types, reads and writes the rows of YDB tables for the row services of the provider contract: TypeRows, ReadRows and RenderRowWrites.
|
Package rowdata types, reads and writes the rows of YDB tables for the row services of the provider contract: TypeRows, ReadRows and RenderRowWrites. |
|
schemavalidation
Package schemavalidation checks a captured desired schema against what the YDB provider can carry, offline.
|
Package schemavalidation checks a captured desired schema against what the YDB provider can carry, offline. |
|
secretcompare
Package secretcompare compares secret presence and explicit rotation requests.
|
Package secretcompare compares secret presence and explicit rotation requests. |
|
secretplan
Package secretplan places value-free secret operations in the shared graph.
|
Package secretplan places value-free secret operations in the shared graph. |
|
secrets
Package secrets defines YDB scheme-secret declarations without their values.
|
Package secrets defines YDB scheme-secret declarations without their values. |
|
secretsource
Package secretsource decodes value references from YAML and Go declarations.
|
Package secretsource decodes value references from YAML and Go declarations. |
|
secretvalue
Package secretvalue expands approved YDB secret inputs only at valid secret value positions, and writes a user's approved password input into the statement that sets it.
|
Package secretvalue expands approved YDB secret inputs only at valid secret value positions, and writes a user's approved password input into the statement that sets it. |
|
seeds
Package seeds runs YDB seed files and keeps their ledger, the table schema_seeds, as the Ptah host's embedded YDB engine does (internal/dbschema/ydb Seeds and migration/seeder).
|
Package seeds runs YDB seed files and keeps their ledger, the table schema_seeds, as the Ptah host's embedded YDB engine does (internal/dbschema/ydb Seeds and migration/seeder). |
|
servicequery
Package servicequery finds the statements this provider runs through a YDB service rather than the query service.
|
Package servicequery finds the statements this provider runs through a YDB service rather than the query service. |
|
sourceencode
Package sourceencode writes this provider's desired values as the YAML keys and Go directives its source decoders read back.
|
Package sourceencode writes this provider's desired values as the YAML keys and Go directives its source decoders read back. |
|
statementrows
Package statementrows pages a single YDB query result without buffering the whole response or converting an unsupported value into text.
|
Package statementrows pages a single YDB query result without buffering the whole response or converting an unsupported value into text. |
|
streaming
Package streaming reads, checks, compares and writes YDB streaming queries as Ptah's embedded YDB engine does (dialect/ydb/ydbstreaming): a query that runs continuously over topics, its body, whether it runs, and the resource pool it runs in.
|
Package streaming reads, checks, compares and writes YDB streaming queries as Ptah's embedded YDB engine does (dialect/ydb/ydbstreaming): a query that runs continuously over topics, its body, whether it runs, and the resource pool it runs in. |
|
streamingcompare
Package streamingcompare compares captured streaming query declarations.
|
Package streamingcompare compares captured streaming query declarations. |
|
streamingplan
Package streamingplan places streaming query stops before their inputs change and places creation or restart after those changes.
|
Package streamingplan places streaming query stops before their inputs change and places creation or restart after those changes. |
|
streamingsource
Package streamingsource decodes the streaming queries declared by a YAML document or Go file.
|
Package streamingsource decodes the streaming queries declared by a YAML document or Go file. |
|
tablecompare
Package tablecompare compares the table and column facets of captured YDB tables offline: the TTL, the automatic partitioning settings, the column families, the family each column sits in, the set of secondary indexes and the changefeeds of a row table, and the storage of a column table.
|
Package tablecompare compares the table and column facets of captured YDB tables offline: the TTL, the automatic partitioning settings, the column families, the family each column sits in, the set of secondary indexes and the changefeeds of a row table, and the storage of a column table. |
|
tablecreate
Package tablecreate compiles captured declarations into native row-table requests.
|
Package tablecreate compiles captured declarations into native row-table requests. |
|
tablesource
Package tablesource decodes the table-scope declarations a YAML or Go annotation source writes for a YDB table: a row table's automatic partitioning, read replicas, key bloom filter, starting layout, TTL and column families, and a column table's storage, hash key, shard count and TTL.
|
Package tablesource decodes the table-scope declarations a YAML or Go annotation source writes for a YDB table: a row table's automatic partitioning, read replicas, key bloom filter, starting layout, TTL and column families, and a column table's storage, hash key, shard count and TTL. |
|
testcheck
Package testcheck builds the quicktest checkers the provider's tests assert through.
|
Package testcheck builds the quicktest checkers the provider's tests assert through. |
|
testcomparison
Package testcomparison constructs captured coordination exchanges for tests.
|
Package testcomparison constructs captured coordination exchanges for tests. |
|
testplanning
Package testplanning constructs captured native planning exchanges for tests.
|
Package testplanning constructs captured native planning exchanges for tests. |
|
testsource
Package testsource builds captured JSON-form YAML fixtures with byte spans.
|
Package testsource builds captured JSON-form YAML fixtures with byte spans. |
|
topiccompare
Package topiccompare compares the declared and the read standalone YDB topics offline, as Ptah's embedded YDB engine compares them: each setting resolved to the value YDB gives a new topic, and the consumers by name.
|
Package topiccompare compares the declared and the read standalone YDB topics offline, as Ptah's embedded YDB engine compares them: each setting resolved to the value YDB gives a new topic, and the consumers by name. |
|
topicplan
Package topicplan lowers captured topic changes into the host's shared graph: one step per topic, refused where YDB cannot make the change in place or the target lacks a capability the topic needs.
|
Package topicplan lowers captured topic changes into the host's shared graph: one step per topic, refused where YDB cannot make the change in place or the target lacks a capability the topic needs. |
|
topics
Package topics reads a standalone YDB topic as the settings and consumers Ptah's embedded YDB engine keeps for one, and writes the statements that create, change and drop it.
|
Package topics reads a standalone YDB topic as the settings and consumers Ptah's embedded YDB engine keeps for one, and writes the statements that create, change and drop it. |
|
topicsource
Package topicsource decodes the standalone YDB topics a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document key topics, and the Go directives ptah:schema:topic and ptah:schema:topic:consumer.
|
Package topicsource decodes the standalone YDB topics a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document key topics, and the Go directives ptah:schema:topic and ptah:schema:topic:consumer. |
|
ttlgrammar
Package ttlgrammar reads the two values a row table's TTL declaration spells as text: the interval after which YDB deletes a row, and the unit an integer column counts since the Unix epoch.
|
Package ttlgrammar reads the two values a row table's TTL declaration spells as text: the interval after which YDB deletes a row, and the unit an integer column counts since the Unix epoch. |
|
workload
Package workload reads, checks, compares and writes YDB's workload management objects as Ptah's embedded YDB engine does: resource pools, which limit the queries that run in them, and resource pool classifiers, which send a user's or a group's queries to a pool.
|
Package workload reads, checks, compares and writes YDB's workload management objects as Ptah's embedded YDB engine does: resource pools, which limit the queries that run in them, and resource pool classifiers, which send a user's or a group's queries to a pool. |
|
workloadcompare
Package workloadcompare compares the declared and the read resource pools and classifiers of a database offline, as Ptah's embedded YDB engine compares them: a pool setting by setting, an unset one apart from zero, and a classifier as a whole.
|
Package workloadcompare compares the declared and the read resource pools and classifiers of a database offline, as Ptah's embedded YDB engine compares them: a pool setting by setting, an unset one apart from zero, and a classifier as a whole. |
|
workloadplan
Package workloadplan lowers captured resource pool and classifier changes into the host's shared graph: one step per statement, chained in the order YDB needs, so that a classifier trading ranks with another runs as the drop, the moves and the creation, in that order.
|
Package workloadplan lowers captured resource pool and classifier changes into the host's shared graph: one step per statement, chained in the order YDB needs, so that a classifier trading ranks with another runs as the drop, the moves and the creation, in that order. |
|
workloadsource
Package workloadsource decodes the resource pools and classifiers a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document keys resource_pools and resource_pool_classifiers, and the Go directives ptah:schema:resourcepool and ptah:schema:resourcepool:classifier.
|
Package workloadsource decodes the resource pools and classifiers a YAML document or a Go file declares, with the syntax and refusals of the host's bundled YDB source (internal/ydbsource): the YAML document keys resource_pools and resource_pool_classifiers, and the Go directives ptah:schema:resourcepool and ptah:schema:resourcepool:classifier. |
|
ydbacl
Package ydbacl holds the answers about YDB's access model that the YQL schema reader gives as the Ptah host's internal/ydbacl gives them: which YDB permission a declared privilege names, which names YDB takes for a user or a group, how a declared password is told from a password hash, and how a grant names the object it is about.
|
Package ydbacl holds the answers about YDB's access model that the YQL schema reader gives as the Ptah host's internal/ydbacl gives them: which YDB permission a declared privilege names, which names YDB takes for a user or a group, how a declared password is told from a password hash, and how a grant names the object it is about. |
|
ydbcomment
Package ydbcomment stores Ptah's comments on YDB objects, as the host's internal/ydbcomment stores them.
|
Package ydbcomment stores Ptah's comments on YDB objects, as the host's internal/ydbcomment stores them. |
|
ydbdecl
Package ydbdecl reads the YDB settings and objects a YQL schema document declares into the specifications the Ptah host's YDB owner keeps for them: a row table's partitioning, starting layout, column families and TTL, a column table's storage, a secondary index's settings, a changefeed, a topic and its consumers, a coordination node, and an external data source or table.
|
Package ydbdecl reads the YDB settings and objects a YQL schema document declares into the specifications the Ptah host's YDB owner keeps for them: a row table's partitioning, starting layout, column families and TTL, a column table's storage, a secondary index's settings, a changefeed, a topic and its consumers, a coordination node, and an external data source or table. |
|
ydbfailure
Package ydbfailure reads what YDB answered a failed query with, and says what the answer proves about the query: that YDB refused it and applied nothing of it, that YDB aborted the transaction that held it for a conflict, or neither.
|
Package ydbfailure reads what YDB answered a failed query with, and says what the answer proves about the query: that YDB refused it and applied nothing of it, that YDB aborted the transaction that held it for a conflict, or neither. |
|
ydbsequence
Package ydbsequence names the sequence behind a YDB Serial column and reads the start and the increment a declaration gives it.
|
Package ydbsequence names the sequence behind a YDB Serial column and reads the start and the increment a declaration gives it. |
|
ydbtype
Package ydbtype maps a declared column type to the YDB type that holds it, and writes a value a data statement stores as the typed YQL literal that type takes.
|
Package ydbtype maps a declared column type to the YDB type that holds it, and writes a value a data statement stores as the typed YQL literal that type takes. |
|
ydbview
Package ydbview holds what YDB does with a view that the renderer, the reader and the preparation of a declaration have to agree on: the clause every CREATE VIEW carries, the form in which the server keeps the view's query, and the pragma a dev realm session puts in front of it.
|
Package ydbview holds what YDB does with a view that the renderer, the reader and the preparation of a declaration have to agree on: the clause every CREATE VIEW carries, the form in which the server keeps the view's query, and the pragma a dev realm session puts in front of it. |
|
yql
Package yql owns YDB lexical rules and lossless SQL analysis.
|
Package yql owns YDB lexical rules and lossless SQL analysis. |
|
yqlddl
Package yqlddl reads what one YQL schema statement does: the table, view or topic it names, the columns, key, indexes and TTL column a CREATE TABLE declares, the actions an ALTER TABLE takes, the settings and consumers a CREATE TOPIC declares and the actions an ALTER TOPIC takes, the restart an ALTER SEQUENCE makes, the resource pool or backup collection a DROP names, the table an ANALYZE names, the settings of an async replication or transfer, the tables a query reads, and the table a data statement writes.
|
Package yqlddl reads what one YQL schema statement does: the table, view or topic it names, the columns, key, indexes and TTL column a CREATE TABLE declares, the actions an ALTER TABLE takes, the settings and consumers a CREATE TOPIC declares and the actions an ALTER TOPIC takes, the restart an ALTER SEQUENCE makes, the resource pool or backup collection a DROP names, the table an ANALYZE names, the settings of an async replication or transfer, the tables a query reads, and the table a data statement writes. |
|
yqlquery
Package yqlquery splits a YQL text into the queries YDB runs it as, as the Ptah host's internal/yqlquery does for its embedded YDB engine.
|
Package yqlquery splits a YQL text into the queries YDB runs it as, as the Ptah host's internal/yqlquery does for its embedded YDB engine. |
|
yqlschema
Package yqlschema reads the YQL schema documents of a schema.read-sql request without a database session.
|
Package yqlschema reads the YQL schema documents of a schema.read-sql request without a database session. |
|
Package schema owns YDB schema models and their wire definitions.
|
Package schema owns YDB schema models and their wire definitions. |