Found while closing the keyed half of #16091 (PR #16298). Not fixed there: that card is about the COLUMN, this is about the INDEX.
The measurement
Live PostgreSQL 16.13, one object driven through all three producers — driver-sql via initObjects, os generate migration --format sql via db.raw, and the typescript format by importing the emitted module and calling up(db). Object:
{ name: 'probe', fields: { keyed_unique: { type: 'text', unique: true, maxLength: 100 } } }
pg_indexes for each producer's schema afterwards:
driver probe_pkey, uniq_probe_keyed_unique
sql gen probe_pkey
ts gen probe_pkey
The column itself now agrees in all three (character varying(100), after #16298). The CONSTRAINT does not exist in either generated table.
Why it matters, and why it is sharper now
Two rows with the same keyed_unique value are refused by the platform's table and accepted by both generated ones. A scaffold that creates the table for an object silently drops a uniqueness guarantee the object declares, and nothing reports it — the same shape as #16091's own hard-failure class, one declaration over.
It is sharper after #16298 because the generators now READ field.unique and indexes[] to size the column (indexedKeyColumns' key set is what the driver's text-family arm branches on). The key set is therefore already computed in generate.ts; the index it implies is still not emitted. driver-sql's own normalizers are uniqueIndexesFromFields (field-level unique, ADR-0120 D1/D3 scoping) and normalizeDeclaredIndex (object-level indexes[]), both exported from @objectstack/driver-sql.
Not free to answer, which is why this is filed rather than fixed
Reproduce
Drive the object above into three PostgreSQL schemas, one per producer, and read pg_indexes for each.
Found while closing the keyed half of #16091 (PR #16298). Not fixed there: that card is about the COLUMN, this is about the INDEX.
The measurement
Live PostgreSQL 16.13, one object driven through all three producers —
driver-sqlviainitObjects,os generate migration --format sqlviadb.raw, and the typescript format by importing the emitted module and callingup(db). Object:pg_indexesfor each producer's schema afterwards:The column itself now agrees in all three (
character varying(100), after #16298). The CONSTRAINT does not exist in either generated table.Why it matters, and why it is sharper now
Two rows with the same
keyed_uniquevalue are refused by the platform's table and accepted by both generated ones. A scaffold that creates the table for an object silently drops a uniqueness guarantee the object declares, and nothing reports it — the same shape as #16091's own hard-failure class, one declaration over.It is sharper after #16298 because the generators now READ
field.uniqueandindexes[]to size the column (indexedKeyColumns' key set is what the driver's text-family arm branches on). The key set is therefore already computed ingenerate.ts; the index it implies is still not emitted.driver-sql's own normalizers areuniqueIndexesFromFields(field-levelunique, ADR-0120 D1/D3 scoping) andnormalizeDeclaredIndex(object-levelindexes[]), both exported from@objectstack/driver-sql.Not free to answer, which is why this is filed rather than fixed
COALESCE(organization_id, '__global__')as a key part — an expression index. Whether the SQL format should emit that expression, and whether the typescript format can express it through knex at all, is a decision.packages/cliproduction modules may not statically value-import a driver (objectstack dev 在工作区未构建时刷 12 段无关命令的 MODULE_NOT_FOUND,唯一可执行的那条却指向错误修法 #5726), so the naming and scoping rules would be transcribed and pinned, as the widths already are.--format sqlis PostgreSQL-only (os generate migration's audit-stamp columns diverge from driver-sql — the generators emitNOT NULLwhere the driver emits nullable, and the SQL format emitsTIMESTAMPwhere both knex paths yieldtimestamptz#15521); index DDL is more dialect-divergent than column DDL.Reproduce
Drive the object above into three PostgreSQL schemas, one per producer, and read
pg_indexesfor each.