Common interface v1
This page defines the shared public data structures, ownership rules, state transitions, and client call order for the syntax in the DSL. Implementation status is recorded in the implementation matrix. A defined interface does not prove that every client implements it.
1. Call boundaries and change rules
The schema manifest defines entities, fields, operators, styles, and errors. Each language generates its models from the manifest with its own build tool. The client library validates a typed request shape and plans it in the calling process into an immutable plan. An executor receives the plan, parameter values, and the connection selected for the model.
The change order is: update the design plan and the DSL, update the manifest and generated artifacts, update all clients, run shared vectors, then update paired documentation. A client cannot add a private field or alternate call order to satisfy a shared interface.
2. Module call flow — IF-01
flowchart LR
Schema[Schema] --> Manifest[Manifest]
Manifest --> Generator[Language generator]
Generator --> Go[Go client]
Generator --> PHP[PHP client]
Generator --> Rust[Rust client]
Generator --> TypeScript[TypeScript client]
Go --> Request[Request]
PHP --> Request
Rust --> Request
TypeScript --> Request
Request --> IR[Request IR]
Manifest --> Planner[In-process planner]
IR --> Planner
Planner --> Plan[Immutable Plan]
Plan --> Executor[Native executor]
Binding[Model connection] --> Executor
Values[Parameter values] --> Executor
Executor --> Rows[Execution rows]
Rows --> Result[Row, collection, page, or scalar]
The planner does not receive database credentials or parameter values. The executor owns database access. A model receives its connection through connect; inside a transaction callback, a model without connect uses the active transaction of the current execution flow. A join or relation child without connect uses the parent connection.
3. Types and values — IF-02
- INT-1
I64— Go:int64; PHP:int; Rust:i64; TypeScript:number; Python:int - INT-2
F64— Go:float64; PHP:float; Rust:f64; TypeScript:number; Python:float - INT-3
Bool— Go:bool; PHP:bool; Rust:bool; TypeScript:boolean; Python:bool - INT-4
Text— Go:string; PHP:string; Rust:String; TypeScript:string; Python:str - INT-5
Bytes— Go:[]byte; PHP:string; Rust:Vec<u8>; TypeScript:Uint8Array; Python:bytes - INT-6
DateTime— Go:time.Time; PHP:DateTimeImmutable; Rust:NaiveDateTime; TypeScript:Datefor interface values (generated model getters use the canonical dbspec textstring, and setters take text; see DBS-112 and DBS-121); Python:str - INT-7
StyledValue, the field of a column with a value codec, holding SQL NULL or the decoded value — Go:orm.StyledValue; PHP:\Polyspec\Orm\StyledValue; Rust:polyspec_orm::StyledValue<T>,Tthe decoded type of the codec; TypeScript:StyledValue<T>; Python:StyledValue - INT-8
Optional<T>— Go:*T; PHP:?T; Rust:Option<T>; TypeScript:T | null; Python:T | None - INT-9
List<T>— Go: slice; PHP: array; Rust:Vec<T>; TypeScript:T[]; Python:list - INT-10
Result<T>— Go:(T, error); PHP: return or exception; Rust:Result<T>; TypeScript:Promise<T>; Python: return or exception - INT-11
Key— Go:orm.Key; PHP:int|string|bool|Bytes|DateTimeInterface; Rust:polyspec_orm::Key; TypeScript:Key; Python:Key(int | str | bool | bytes | datetime.datetime). PythonCollection.keys()retains the type of each supported key and rejects values outside this key type.
Null, an empty list, a missing field, and a default value are different states. Serialization preserves the logical type and field order required by the codec specification.
4. Models and condition trees — IF-03 to IF-08
classDiagram
class Model {
Entity entity
Request request
Optional~Connection~ connection
connect(connection) Model
and(callbackOrModel) Model
or(callbackOrModel) Model
relation(child) Model
relations(child) Model
on(callback) Model
get() Result~OptionalRow~
gets() Result~Collection~
getsPage(page, perPage) Result~Page~
getCount() Result~I64~
}
class Request {
RequestIR ir
List~Value~ params
Optional~Error~ deferredError
}
class Group {
List~ConditionOrGroup~ items
}
class Relation {
String resultName
Model child
}
Model --> Request
Request --> Group
Model --> Relation
A model is both the query builder and the loaded row type. A group contains conditions or nested groups in order, and each item after the first carries an explicit AND or OR connector. and(fn) and or(fn) create nested groups whose callback receives an empty model of the same type, and not(fn), andNot(fn) and orNot(fn) create negated ones (NOT (…)). and(model) and or(model) place the conditions of a joined child as a group. Join ON conditions are stored separately from WHERE conditions.
A terminal does not change the stored request, so repeated terminals produce the same statement.
- INT-88 The query base of a generated model (Go
Core, PHPModel, TypeScriptCore, PythonCore) keeps the first error that a builder call records for its request. The terminal returns that error before any statement is sent.
5. Public API — IF-09 to IF-12
5.0 Connection input
Every public client accepts one DSN URI. mysql://, postgres://, and sqlite:// select the database driver. Every connection reads and writes datetime values in UTC; the optional timezone parameter accepts only UTC or +00:00. The caller does not pass a second driver value.
- INT-12 The
github.com/polyspec/orm/packages/goGo module — Public connection call:model.Connect(dsn, config); Result:(*orm.DB, error) - INT-13 The
polyspec/ormPHP client — Public connection call:\Polyspec\Orm\Tests\Model\connect(dsn, new Config(…)); Result:Db - INT-14 The
polyspec-ormRust crate — Public connection call:model::connect(dsn, pool_size, config).await?; Result:polyspec_orm::Db - INT-15 The
@polyspec/ormTypeScript package — Public connection call:connect(dsn, options)of the generated module; Result:Promise<Db>
5.1 Creation and language forms
- INT-16 model creation — PHP:
new Product; Go:model.Product(); Rust:Product::new(); TypeScript:new Product(); Python:Product() - INT-17 connection — PHP:
->connect($db)or($db); Go:.Connect(db); Rust:.connect(&db); TypeScript:.connect(db); Python:.connect(db) - INT-18 collection terminal — PHP:
gets(); Go:Gets(); Rust:gets().await?; TypeScript:gets(); Python:gets() - INT-19 count terminal — PHP:
getCount(); Go:GetCount(); Rust:get_count().await?; TypeScript:getCount(); Python:get_count()
The creation spelling follows the host language. The method role, stored request, result type, error behavior, and call order remain the same.
5.2 Method groups
- INT-20 conditions: Use column chains, operator prefixes, value shapes, and explicit connectors.
- INT-21 groups: Preserve item order and nested group boundaries.
- INT-22 relations: Use
match<L>With<R>keys on the child and a separate statement per relation. - INT-23 joins: Keep
ONandWHEREconditions separate and place child conditions where the child is passed to a group. - INT-24 projection: Preserve positional output mapping and reject duplicate row names.
- INT-25 mutation: Record changed fields and original values.
- INT-26 terminals: Receive chain values only and use the model connection.
Finder chains
getBy<Chain>, getsBy<Chain>, and getCountBy<Chain> apply a column chain as the condition and call the terminal. A chain is a combination of column keys, operators and connectors, so the set of chains has no bound, and no client lists them all. Each client reaches a chain in one of two ways, and the same chain gives the same rows in every client:
- INT-27 Go static model types declare each chain found by scanning source calls; and Rust static model types do the same; and TypeScript static model types do the same. PHP and Python resolve names as the next item says. A chain no scanned call names has no method until code calls it and the models are generated again.
- INT-28 The
polyspec/ormPHP generator scan checks found names but declares no method; and PHP clientpolyspec/ormresolves call names dynamically through__call; and PHP-extension clientpolyspec/orm-php-extdoes the same; and Python packagepolyspec-ormresolves call names at runtime through__getattr.
The shared vector terminal_by in tests/conformance/vectors.json runs getsBy<Chain> on the non-unique columns service_seq and is_close in every runtime client, and the rows of each client must equal the recorded result.
5.3 Execution methods
- INT-29
get— Result: one row; null when no row matches; Connection: model connection or active transaction - INT-30
gets— Result: collection; Connection: model connection or active transaction - INT-31
getsCount— Result:GroupRowswith selected grouping values and checked row counts; Connection: model connection or active transaction - INT-32
getsPage— Result: page; Connection: model connection or active transaction - INT-33
getCount— Result: integer; Connection: model connection or active transaction - INT-34
create— Result: row with generated key; Connection: model connection or active transaction - INT-35
creates(rows, batch)— Result: inserted row count;CONFIGwhenbatchis not a positive integer andBATCH_TOO_LARGEwhen a batch binds more than the dialect bind limit, both before any statement; Connection: model connection or active transaction - INT-36
update— Result: row; Connection: row connection or active transaction - INT-37
delete— Result: row or collection; Connection: row connection or active transaction - INT-38
restore— Result: the restored row;NO_ROWSwhen no row has the key; Connection: model connection or active transaction - INT-39
restoreOrCreate— Result: the restored or created row;ROW_NOT_DELETEDwhen a row that is not deleted has the key; Connection: model connection or active transaction
A terminal without a connection outside a transaction returns CONFIG. A connection must carry the schema engine used by the generated request; otherwise the terminal returns CONFIG.
- INT-68
GroupRowscontains no partial models. Each row contains only selected grouping values and one nonnegativerow_count. A missing, duplicate, malformed, or lossy count fails. Selected values retain their declared types, including boolean and SQL NULL; requesting an unselected value returnsCOLUMN_UNSELECTED. - INT-69
GroupRow.toArray(GoGroupRow.ToArray, PHPGroupRow::toArray, TypeScriptGroupRow#toArray, PythonGroupRow.to_array) returns one map of the selected values by name, withrow_countas its checked integer count and SQL NULL as null. - INT-70
GroupRows.length(GoLen, PHPcount, TypeScriptlength, Pythonlength) returns the number of group rows; the rows are iterated in result order. - INT-71
GroupRows.toArray(GoToArray, PHPtoArray, TypeScripttoArray, Pythonto_array) returns thetoArraymap of each group row, in result order. - INT-90 A
GroupRowwhoserow_countis negative fails withCODEC_DECODE, as does everyrow_countthat is not an exact nonnegative integer (boolean, lossy, or beyond the safe integer range). A row with norow_countisINTERNAL. A row count is the database'sCOUNT, which is never negative, so a negative value is a received value that does not decode as a count, not a broken invariant of the library.
6. Connections, transactions, and utilities — IF-13 to IF-17
INT-72
connection.transaction(fn, options)runs the callback in one transaction. Begin, commit, rollback, and the executor are private. A callback error or exception rolls back the transaction; otherwise the transaction commits and the callback result is returned. A transaction whose start fails holds no connection afterwards: when a start step after BEGIN fails, the opened transaction is rolled back and its connection returns to the pool, and when BEGIN itself fails, the connection, whose session state is unknown, is closed rather than returned. Before the transaction ends, the client releases its MySQLlocklocks, clears the MySQL values ofsetLocal, and restores the SQLitequery_onlyandread_uncommittedmodes, because named locks, user variables, and pragmas outliveCOMMITandROLLBACKon a pooled connection. A connection returns to the pool only when every cleanup step and theCOMMITorROLLBACKsucceeded; after a failed step the state of its session is unknown, so the client closes the connection instead and the server ends its transaction, locks and values with the session. A pooled connection is therefore clean or discarded. The TypeScript SQLite driver holds its one connection, which it cannot close. Every cleanup step runs and every failed step is reported; aRELEASE_LOCKthat does not return 1 isCONFIGlock <key> was not held at transaction end. When that cleanup fails at commit, the transaction rolls back and returns the cleanup error. When the callback, the begin, or the commit cleanup fails and the cleanup or the rollback fails too, the error isROLLBACKtransaction failed (<cause>) and rollback failed (<transaction end error>); it keeps the cause and the transaction end error (protocol) and is not retried. A Go or Rust callback that panics, or a Go callback that leaves throughruntime.Goexit, rolls the transaction back and the panic continues; when that rollback fails, the panic value is theROLLBACKerror of the same form. A rollback that fails because the server or the driver closed the connection, for example after a cancelled statement or a killed session, is a failed rollback too and is reported in that form, although the server ended the transaction with the session; a client that ends a failed transaction by closing its connection reports the cause alone. A Go transaction whose context is cancelled cannot run the cleanup statements, so it closes its connection instead of returning it to the pool and reportsCANCELEDalone; the server ends the transaction, its named locks, user variables and SQLite modes with the session. A Rust transaction future dropped before its transaction ends closes the connection at once, over TLS too, so the server ends the transaction and its locks with the session.INT-40
isolation:default,read_uncommitted,read_committed,repeatable_read,serializableINT-41
readOnly: booleanINT-42
timeoutMs: positive integer; PostgreSQL appliesstatement_timeout, MySQL and SQLite returnCAPABILITY_UNSUPPORTEDINT-43
retry: deadlock retry count, default3; each retry runs the complete callback again, and0disables retryINT-44
audit: column name to value of the unit of work's audit record; the transaction inserts the values of the connection's audit source with these values (a value wins) into the audit record table right before its first write of an audited table, in every attempt, so a transaction that writes no audited row inserts none, and every audited write in the transaction writes the record's key (usage)
The connection configuration takes auditSource, a function that returns the audit values of the current request (configuration). A transaction with audit on a connection without a source, a value that is not a column of the audit record table, and an audited write without audit fail with CONFIG; an error of the source fails the transaction; a nested transaction keeps the outer audit and does not take audit.
INT-45 Each execution flow keeps a stack of active transactions: the goroutine in Go, the request in PHP, the async context in TypeScript, and the task in Rust. A model without
connectuses the innermost transaction.INT-46 A transaction on the same connection inside an active transaction creates a savepoint. An inner failure rolls back only the inner work unless the outer callback returns it. After a callback error, a panic or a Go
runtime.Goexit, the client runs bothROLLBACK TO SAVEPOINTandRELEASE SAVEPOINT; when either fails, the error or the panic value isROLLBACKtransaction failed (<cause>) and rollback failed (<savepoint end error>), and a failedRELEASE SAVEPOINTafter a successful callback is returned. A savepoint of a transaction that a cancelled transaction context has already ended ended with it.INT-47 Concurrent use of one transaction connection returns an error. A task or goroutine started inside the callback has no active transaction.
INT-48
transactionConflict(message)(Go:orm.TransactionConflict) creates the retryableDEADLOCKerror.INT-49 Row locks
forUpdate(),forShare(),forUpdateNoWait(), andforShareNoWait()are allowed only inside a transaction. MySQL and PostgreSQL append the lock clause; SQLite uses an ORM transaction-scoped lock row. A*_nowaitrequest that cannot acquire the lock returnsLOCK_NOT_AVAILABLEon every adapter.INT-73
connection.utils()provides operations outside the query syntax.INT-50
lock(key): transaction-scoped named lock: MySQLGET_LOCK, PostgreSQL advisory lock, SQLite ORM lock row; requires an active transactionINT-51
setLocal(key, value),local(key): transaction-local values; requires an active transaction;localreturnsNO_ROWSfor a missing keyINT-52
wasInserted(entity, sequence): reports whether a generated ORM insert for the sequence succeeded in the active transaction; the fact is adapter-neutral and restored across savepoint rollbackINT-53
backendWaitingForLock(ctx): reports PostgreSQL pool backends waiting for a lock; MySQL and SQLite returnfalsewithout exposing a driver-specific caller APIINT-54
schema().register(schema): takes the generated schema value and registers its set on the connection without reading or writing the database; fails withCONFIGwhen its manifest text does not hash to itsmanifestHashINT-55
schema().install(schema): takes the generated schema value, fails withCONFIGbefore any statement when its manifest text does not hash to itsmanifestHash, renders the dbspec document set for the dialect of the connection and creates every table when none exists, and registers the set on the connection; changes nothing when every table exists and returnsCONFIGwhen only some exist; then returnsCONFIGnaming each difference when the tables of the set in the database differ from the set; on MySQL a call inside a transaction returnsCONFIGINT-56
schema().addTablesAndColumns(schema): takes the generated schema value and upgrades its installed document set by the tables the database lacks, the missing columns of the existing tables that are null or have a default and the missing indexes of the existing tables, with the plan steps of the dialect, which create each table with its indexes, foreign keys, checks and triggers and replace the audit triggers of each changed table; returns the created tables astable, the added columns astable.columnand the created indexes astable.index, leaves the tables of other sets unchanged and registers nothing; any other difference returnsSCHEMA_DIFFERSbefore any change, and a missing unique key returns it with the reason that it can fail on the existing rows and belongs to a plan; on MySQL and SQLite a call inside a transaction returnsCONFIGINT-57
schema().exists(schema),schema().installed(schema, table): schema inspectionINT-58
schema().empty(): reports whether the database holds no user content. On PostgreSQL a schema other thanpublic,information_schemaand thepg_schemas is content even without objects, and so is a table, partitioned table, view, materialized view, or foreign table inpublic; functions, types, and sequences inpublicare not content. On MySQL a table or view of the connected database is content; on SQLite a table or view other than thesqlite_tables and the ORM'sorm__tables is contentINT-59
privileges().grantTable(table, role),revokeTable(table, privilege, role),inspectTable(table): table privileges; non-PostgreSQL dialects returnCAPABILITY_UNSUPPORTEDINT-60
aes().status(model, keyring),aes().rotate(model, keyring): AES key version status and rotation of every AES column and the version in one transactionINT-61
stats(): connection pool statisticsINT-74
AESKeyring.versions()(GoAESVersions, PHPversions, TypeScriptversions, Pythonversions) returns the declared key versions in ascending numeric order.INT-75
AESRotationStatushas the fieldscurrent(the keyring's current version),total(the rows of the model table),pending(the rows whose stored version is notcurrent) andversions(the row count of each stored version);aes().status(model, keyring)builds it.INT-76
execute(step, params)runs one compiled statement step with the request parameters on the connection or its active transaction, as execute a compiled statement states. A step that reads rows, or aparambind slot past the parameters, fails withIR_INVALIDbefore any statement runs;executereturns nothing.INT-77
subscribe(subscriber)registers a subscriber that receives the event of every statement of the connection and its handles after the statement ends, in registration order, and returns a function that removes it; a markdown connection returnsCAPABILITY_UNSUPPORTED.INT-78
withRequestHook(hook)returns a handle of the same connection whose hook receives each request and its bound parameter values right before it is planned. Bound parameter values are read-only; the hook may append parameters only through the supplied parameter-add operation. The handle shares the connection, the plan and statement caches, the statement event subscribers and the active transactions; callingwithRequestHookon a hooked handle replaces its hook.INT-79
stats()returns the pool counts of the connection: the maximum of open connections, the open connections, the connections in use, and the idle connections (GoDBStats, PHPStats, TypeScriptPoolStats, PythonDbStats).INT-67 Abandoning a handle releases what it holds: an abandoned query handle cancels its statement if it has not finished, and an abandoned transaction or connection handle rolls back its open transaction and returns its connection to the pool. Each language abandons by its own disposal form: Rust drops the value, Go cancels the context or calls
Close, Python leaves the context manager or callsclose(), TypeScript aborts theAbortSignalor callsclose(), and PHP destroys the object or callsclose().
Utilities that change data open a transaction when none is active and join the active transaction of the same connection otherwise.
7. Planning and assembly — IF-18 to IF-20
Request contains the schema hash, IR version, entity, predicate tree, relation requests, projection, and parameter count. Parameter values are stored separately from the request shape. The client planner produces an immutable plan with dialect-specific SQL and positional assembly metadata. The shared interface requires all six configured implementations (Go, PHP, PHP extension, TypeScript, Python and Rust) to produce the same SQL for the same request; conformance vectors check executable behavior separately.
Assembly uses {alias, column, output_name, index}. Join aliases remain separate from the root namespace. Duplicate output names return COLUMN_ALIAS_CONFLICT. A schema hash mismatch returns SCHEMA_HASH_MISMATCH before execution.
8. Row values, dirty state, and relations — IF-21 to IF-24
A model row stores declared fields, added columns, relation results, and values attached with new<Name> in one name space; duplicate names are rejected. Getters return the declared type. Setters update the field and mark it dirty. Update operations send dirty fields only, except fields required by optimistic locking. The original version is read before mutation and is used in the update predicate.
A relation result is either one row or a collection according to the schema. The Go and Rust relation getters also return an error: a row without a related row reads as no result, and a stored relation value of another type than the getter's result is INTERNAL. Collection keying is deterministic. A duplicate key follows the declared key policy; an undeclared key function is invalid.
- INT-87
relation(child)andrelations(child)add the child as a relation whose result is one row or a collection, respectively. The child needsmatch<L>With<R>()keys, and a relation added inside a group callback is refused; each refusal records aCONFIGerror that the terminal returns.
9. Collection, key, and page — IF-25 to IF-27
- INT-62
Collection<T>: ordered rows, length, first, key lookup - INT-63
Key: logical type tag and value - INT-64
Page<T>:items,totalCount,totalPages,page,perPage - INT-80
Collection.get(key)returns the model whose key has the same type and value askey. When no model has the key, the result is none: the zero value with no error in Go,nullin PHP,undefinedin TypeScript andNonein Python. A SQL NULL key fails withCODEC_DECODE; Go returns the conversion error ofKeyOffor a key it cannot convert. - INT-81
Collection.keys()returns the key of each model in collection order. - INT-82
Collection.values()(GoSlice, PHPall) returns the models in collection order. - INT-83
Collection.entries()(GoAll, which yields key and model pairs) returns each key with its model in collection order. - INT-84
Collection.toArray()returns thetoArraymap of each model, in collection order. - INT-85
Collection.connect(db)sets the connection of every model of the collection todband returns the collection. - INT-86
Collection.fetchedValue(key)returns the value fetched with the model ofkey. When no model has the key, the result is none: Gonilwith no error, PHPnull, TypeScriptundefined, PythonNone.
The integer key 7 and text key "7" are different keys. A boolean true and the integer 1, bytes and text with the same characters, and a date and time and the text of its form are different keys; a date and time in another zone is the key of its UTC instant. A NULL key component is CODEC_DECODE. Composite keys encode each typed component with its length, so ("1", "23") and ("12", "3") cannot collide. The plan records the ordered collection identity in Assemble.key. Regular rows use every primary-key component; grouped count rows use their group columns and expression aliases. A collection preserves database order unless an explicit order or key policy changes it. A page preserves the root result order and relation attachment order.
Go key conversion preserves the logical types of signed integers, booleans, text, bytes, finite floating-point values, and times. An unstyled byte column remains bytes after database scanning, including a joined column. Unsigned integers must fit a signed 64-bit value. A null or unsupported value returns an error; a null database identity component is absent. Composite keys retain component type, order, and length. KeyOf, Key.Value, and collection Get, Has, and FetchedValue report invalid keys with an error. Collection lookups, relation matching, and split-query deduplication propagate conversion errors instead of converting values to text.
10. Configuration, errors, codecs, and events — IF-28 to IF-31
Configuration selects the dialect, DSN, schema file, executor, AES key version map, and query event hook. It does not select another database or silently change the request path.
Errors use the codes in errors.yaml. Go callers use orm.ErrorCode, orm.IsDeadlock, orm.IsLockNotAvailable, orm.IsDuplicateKey, orm.IsForeignKey and orm.IsConstraint for adapter-neutral classification; they do not inspect driver error types. Codec styles are schema declarations. The AES version column is non-null integer plaintext metadata with no encoding style and is excluded from the default projection. AES rotation updates all AES payload columns and the version in one transaction.
INT-65 Every implementation (Go, PHP, the PHP extension, TypeScript, Python and Rust) exposes every code of errors.yaml as a native constant of its error-code catalog whose value is the code, and the catalog equals errors.yaml: it lacks no code and adds none.
INT-89 An error of every client is a library error that carries one stable code of errors.yaml and is classified by that code: Go
ir.Error(orm.Error) withCode, PHPOrmExceptionwithcode_, TypeScriptOrmErrorwithcode, and PythonOrmErrorwithcode.INT-91 A statement event of every client records one statement that the connection sent, with exactly these seven fields:
sqlis the statement as sent;bindsis the bound values in order, where a secret is$SECRETand an executor clock value is$NOW;kindis the statement kind, one ofselect,insert,update,delete,begin,commit,rollback,savepoint,release,rollback_to,schemaandutility;tablesis the tables the statement names, sorted;elapsedis the time from sending the statement until the client read its result;transactionis the number of the connection's transaction the statement ran in, counted from 1 by every outermost begin, and none outside a transaction;erroris the error the statement ended with, or none. Each client names the fields in its own spelling and expresses "none" in its native form: GoSQL,Binds,Kind,Tables,Elapsed,Transaction(zero outside a transaction) andErr(nil), withElapsedatime.Duration; PHPPolyspec\Orm\StatementEventwith the propertiessql,binds,kind,tables,elapsed(seconds),transaction(null) anderror(null); TypeScriptStatementEventwith the same names,elapsedin seconds, andtransactionanderrornull; PythonStatementEventwith the same names,elapsedin seconds, andtransactionanderrorNone, whereerroris the exception the statement raised, of whatever type it is; RustStatementEventwith the same names,elapsedaDuration, andtransactionanderrorNone.INT-92 A connection that the server ends while the pool holds it idle is discarded by the pool: the process keeps running, the error of that connection is handled by the pool, and the next statement runs on a new connection.
INT-93 The
@polyspec/ormpackage's published TypeScript declarations type-check in a project that has none of the driver types (pg,@types/pg,mysql2) installed; they import none of the driver modules, so an application needs only the types of the driver it uses.INT-94 The
polyspec/orm-php-extpackage's PHP extensionpolyspec_ormdeclares thepdomodule as a required module, so PHP loads the extension only when pdo is present.INT-95 The
polyspec/orm-php-extpackage'spackages/php-ext/composer.jsonis of typephp-extand names the extensionpolyspec_orm, and its build-path holds theconfig.m4that builds that extension.INT-96 A statement event subscriber that raises an error fails its statement with
SUBSCRIBER. The message of that failure isSUBSCRIBER: statement event subscriber failed:followed by the text of the subscriber's error (for an ORM error, its code, a colon and its message), and the subscriber's error is the cause of the failure.INT-130
AESKeyring(GoAESKeyring, PHPAesKeyring, TypeScriptAESKeyring, PythonAesKeyring, RustAesKeyring) holds the declared AES key versions and the current version (GoCurrentAESVersion); it is the keyring argument of INT-60.INT-131
AESUtils(GoAESUtils, PHPAesUtils, TypeScriptAESUtils, PythonAesUtils, RustAesUtils) is the value ofaes()(INT-60) and exposesstatus(model, keyring)androtate(model, keyring)as the operations of INT-60.INT-132
Db.connectSchema(dsn, schema)(GoConnectSchema, PHPOrm::connectSchema, TypeScriptDb.connectSchema, PythonDb.connect_schema, RustDb::connect_schema) connects to the DSN and registers the schema value as INT-54 does; it fails withCONFIGwhen the manifest text does not hash to itsmanifestHash. The TypeScript generated module's publicconnect(dsn, options)helper callsDb.connectSchema(dsn, SCHEMA, options).INT-133
Schema(GoSchema, PHPSchema, TypeScriptSchema, PythonSchema, RustSchema) is the generated schema value: the manifest text, itsmanifestHash, and the external document text of the set when it uses external documents. It is the argument of INT-54 through INT-58 and of INT-132.INT-134
Utils(GoUtils, PHPUtils, TypeScriptUtils, PythonUtils, RustUtils) is the value ofconnection.utils()(INT-73) and holds the utility groups of INT-50 through INT-61, among themschema(),privileges(),aes()(INT-131) andstats().
Secrets and parameter values are excluded from logs.
11. Generator, schema, and extension boundaries — IF-32 to IF-34
The generator reads the schema manifest and emits models, fields, column methods, and error types. Go and Rust generation also reads the scanned source and emits the chain, relation key, join, and new<Name> methods that the source calls. It must not emit a method for an undeclared column or operator. Generated code is checked against the manifest and the interface symbol list.
- INT-66 The code that the generator emits passes the standard compiler of its language and the formatter and linter that the lint checks of this repository run for that language, and the generated text suppresses no finding: it carries no Rust
allowattribute, Go//nolint, Python# noqa, PHP@phpstan-ignoreorphpcs:ignore, or TypeScripteslint-disableor@ts-ignore.
Relations use relation(child) and relations(child) with match<L>With<R>() on the child. Joins use join<L>With<R>(child) and leftJoin<L>With<R>(child). Unknown names fail during PHP and TypeScript call resolution and during Go and Rust generation.
12. Verification
stateDiagram-v2
[*] --> Created
Created --> Bound
Bound --> Executed
Executed --> Bound
Bound --> Finished
Finished --> [*]
flowchart LR
Query --> Binding
Binding --> Transaction
Transaction --> Commit
Transaction --> Rollback
Commit --> Finished
Rollback --> Finished
flowchart TB
Root[Root query] --> Join[Join query]
Root --> Relation[Relation query]
Join --> JoinResult[Join namespace]
Relation --> RelationResult[Related collection]
flowchart LR
Predicate --> Group
Group --> NestedGroup
NestedGroup --> RequestIR
RequestIR --> Plan
flowchart LR
Row[Loaded row] --> Dirty[Dirty fields]
Dirty --> Update[Update]
Row --> Original[Original version]
Original --> Optimistic[Optimistic predicate]
Optimistic --> Update
flowchart LR
Error[Driver or planner error] --> Code[Stable error code]
Error --> Message[Original message]
Code --> Client[Client result]
Message --> Client
flowchart LR
Style[Schema style] --> Codec[Host codec]
Codec --> Value[Typed value]
Version[AES version metadata] --> Rotation[Row rotation]
Value --> Rotation
The verification suite checks the manifest, generated symbols, stored fields, request shape, state transitions, error codes, codec vectors, relation results, and database results. It also checks document pairs, static Pages output with its Mermaid sources, and repeated-build bytes.
The interface check rejects a declared or called multi_statement method in Go, PHP, Rust, TypeScript, and Python source. Its source mutation cases verify both failures. The CI workflow runs the generated Go model, interface, and schema checks; a test fails if any command is removed.
A passing symbol check proves the declared surface only. A passing conformance vector proves the tested input and result. A feature is complete only when all supported clients, required databases, tests, documents, and Pages checks pass.
Catalog grid query cells
Every client's read_only_grid_query(sql, params, limits) returns ordered column metadata and typed grid cells. Binary cells preserve bytes and remain distinct from text, including empty binary and valid UTF-8 binary. SQL NULL is a separate cell. Grid decoding does not modify catalog/schema coercion rules or add a compatibility fallback. It uses the same database-enforced read-only scope, connection disposal and explicit query budgets. Unsupported types fail explicitly with CATALOG_VALUE.
Floating-point cells preserve the exact IEEE-754 bits, including signed zero and nonfinite values, without JSON number coercion. Implementations may use their native typed representation at their public API. The MySQL unsigned integer cell preserves the full unsigned 64-bit range. These grid values do not change schema/catalog scalar conversion rules.
Finite grid decimal cells use exact plain decimal strings, preserving the database's declared result scale, including trailing zeros. Do not pass through binary floats or fixed-precision model codecs. SQLite has no native decimal storage class: its actual text/integer/real values keep their existing tags. Nonfinite PostgreSQL numeric values fail explicitly with CATALOG_VALUE.
Temporal grid cells hold the dbspec text forms: Date is YYYY-MM-DD, Time is HH:MM:SS and DateTime is YYYY-MM-DD HH:MM:SS in UTC, the latter two with fraction digits. MySQL DATE, TIME and DATETIME and PostgreSQL date, time and timestamp without time zone decode into them; MySQL TIMESTAMP and PostgreSQL timestamptz and timetz remain unsupported. A read of a described table (table page, row lookup, insert reads) writes exactly the declared precision p of the column, where MySQL time and datetime without p have 0 and PostgreSQL time and timestamp without p have 6; a read-only grid query has no column declaration and writes six digits. A time outside 00:00:00 to 23:59:59.999999, a date outside 0001-01-01 to 9999-12-31 and a PostgreSQL infinity fail. SQLite has no temporal storage class: a read of a described table turns the stored text of a DATE, TIME or DATETIME column into the same temporal cell as MySQL and PostgreSQL, whose fraction digits the dbspec CHECK fixes at p, and a value that is not in the dbspec form fails with GRID_TEMPORAL_VALUE; a read-only grid query has no declaration and keeps the stored text.
Qualified catalog table metadata
current_namespace() reports the selected namespace. describe_table resolves an explicitly qualified table through bound catalog names, preserving native column types, declared primary-key order, actual nullability and generated-column flags. Do not derive identity from logical schema column order or arbitrary query results. SQLite supports its connected main namespace; nullable legacy primary keys are not reliable identity, while a genuine INTEGER rowid alias is non-null. A descriptor is not edit authorization: table mutation requires explicit commands, validated schema/row revisions, typed binds and optimistic conflict checks.
TableMetadata contains the qualified table, native relation kind, ordered columns (name, native_type, nullable, generated), constraint-ordered primary_key and reliable_row_identity. Identity requires an ordinary or partitioned table with a nonempty, entirely non-null primary key. Generated flags also cover PostgreSQL always-generated identity and SQLite hidden columns; they are not a complete writable-column permission model. Preserve an empty SQLite native type for an untyped column rather than inventing a type.
Names require 1..1024 UTF-8 bytes and no NUL. Each catalog result is bounded to 2048 rows and 4 MiB. Unsupported relation kinds and SQLite namespaces fail explicitly. This descriptor is not a complete physical schema import or an atomic schema revision; mutation must revalidate metadata in its owning scope.
MySQL SYSTEM VIEW catalog relations are classified as views, never as tables or writable row identity. Unknown native relation kinds still fail explicitly.
Catalog typed bind values
Catalog parameters preserve their declared logical type and are bound through the selected driver's typed parameter API, never formatted into SQL text. Unsigned integers are MySQL-only. SQLite rejects Decimal and Float32, and preserves its native integer/float64/text/blob storage classes without implicit widening or numeric-affinity coercion. PostgreSQL supports decimal and both native float widths. Reject nonfinite MySQL floats and SQLite NaN before execution; SQLite infinities remain native float64 values. Reject malformed decimals, PostgreSQL text NUL, more than 65535 parameters or total value bytes exceeding 16 MiB before preparing or executing. Typed NULLs use the selected native bind kind, not empty text or zero. Date, Time and DateTime binds take the dbspec text of the grid cells and reject any other form before execution; MySQL and PostgreSQL bind them as native date, time and timestamp values and SQLite binds the text, so a catalog write of a temporal column, a temporal row identity included, reads back the written cell when the value has the column's declared fraction digits. Native Boolean binding preserves Boolean; PostgreSQL grid reads preserve Boolean, while MySQL TINYINT and SQLite INTEGER storage return integer 0/1 rather than inventing a distinct native boolean type. SQLite storage class is owner-tested. These are bind semantics, not protection from column/server coercion: mutation execution still needs schema validation, strict post-write verification and rollback/conflict handling. Each client has its own owner cases for these values.
Catalog row snapshots and writes
Automatic/default-key insertion must receive database-owned identities, never derive them from row counts, maxima or a later unrelated connection. PostgreSQL and SQLite use bounded typed INSERT ... RETURNING on the owning transaction. MySQL uses the executing statement's insert acknowledgement for an omitted AUTO_INCREMENT key and rereads the row on that same transaction. Literal default keys use server-side DEFAULT(column) predicates and declared composite-key ordering, without frontend default evaluation. expression_default distinguishes MySQL expression defaults from literal defaults. For omitted expression keys, select a bounded transactional input locator before writing. It must match no existing rows under a target-table lock, then match exactly one row whose supplied values are exact after inserting. Return that row's actual primary keys and revalidate it by key before commit. Never reevaluate a volatile default. An existing locator match fails before writing with ROW_IDENTITY_AMBIGUOUS; an ambiguous or coerced post-write result requires rollback. Empty assignments can identify only an initially empty table. This strategy is not a retry or fallback after another strategy fails. Acquire MySQL target-table metadata locking before descriptor/engine safety checks; catalog reads alone are insufficient. Verify the declared key-generation strategy before writing; unsupported returning strategies fail explicitly. Concurrent inserts must return their own rows.
PostgreSQL prepared query metadata must use the explicit bind codec types, not untyped inferred placeholders. A grid i64 key can select an INTEGER column via the database's integer comparison, without sending i64 bytes as an inferred int4 parameter. Native functions still require explicit SQL casts where their signature differs from a declared bind type; no codec guessing is permitted.
Before writing, the snapshot update validator checks named assignments and must reject duplicates, unknown names, generated columns, invalid NULLs and oversized values. After writing under the same lock, compare exact requested values and untouched ordinary columns; generated columns may be recomputed. A coercion or unrequested ordinary-column change requires rollback, never success. Pure verification alone is not transactional execution or mutation authorization.
Rust-specific transaction lifecycle controls
The Rust catalog API additionally requires a fallible synchronous pre-commit permit for insert, update and delete. The permit runs after transactional write verification, before publishing CommitStarted or spawning the commit owner. A rejected permit rolls back; it cannot silently become commit success. Successful permission transfers commit ownership without an intervening cancellation await. The phase observer remains observational; durable authorization and recovery belong to the caller.
The Rust update executor accepts an original RowSnapshot, explicit typed P assignments, an atomic cancellation flag and a required phase event subscriber. Use a fresh discard-on-drop transaction, original-key row locking (BEGIN IMMEDIATE on SQLite), descriptor/original comparison and exact post-write verification before commit. Reject non-InnoDB MySQL tables and MySQL triggers. Trigger absence is valid only with a verified direct account grant; role-only grants and enabled partial revokes are currently rejected, not assumed visible. Cancellation checkpoints before commit roll back; cancellation after commit begins does not claim rollback. Commit runs in an owned task publishing Committed/Indeterminate, even if its awaiting caller drops. Unconfirmed commit errors are indeterminate; never automatically retry. Explicit PostgreSQL constraint/transaction rejection is distinguished from missing acknowledgement and requires rollback confirmation before reporting ROW_COMMIT_REJECTED. Preserve the owning native error cause. These events are not a durable operation journal and give no durable operation identity or recovery.
Rust's delete_row uses the same explicit baseline, cancellation checkpoints and phase subscriber. Lock and compare the original, require exactly one affected row and verify absence with an unchanged descriptor before commit. Constraint failure or pre-commit cancellation rolls back; never invent a successful deletion for a missing or changed original. Database FK effects require review before authorization; the native API does not independently authorize cascading effects.
Rust's insert_row takes a checked TableMetadata and explicit typed assignments, cancellation flag and phase subscriber. Columns expose automatic_key and default_expression; these participate in descriptor comparison and the bounded baseline revision. Require explicit primary-key Each omitted primary-key component must have an automatic_key or a default_expression; otherwise fail with ROW_INSERT_IDENTITY_REQUIRED. Return omitted keys from the database. A key shape the selected dialect cannot generate fails with ROW_INSERT_UNSUPPORTED. Lock/probe explicit keys, revalidate the descriptor, reject existing keys, write with typed binds, require one affected row and reread by the declared key. Compare every supplied value exactly; omitted defaults and generated values are returned from the database. Reject unknown/duplicate/generated assignments. These lifecycle controls are Rust-specific. They do not add a shared cancellation or phase-event requirement to other clients; every client still follows the shared key-generation, snapshot validation, mutation and rollback results above.
The row-snapshot operation captures one checked immutable row, reliable non-null declared primary-key values in constraint order and a value-sensitive SHA-256 revision of descriptor/typed cells. Bound the complete captured encoding to 8 MiB. Reject views/unreliable identities, bad projection/keys, malformed decimals, NULL in non-null columns and page bounds. The current-row check against table metadata and a grid result distinguishes changed descriptor, missing/changed row and ambiguous identity. Exact typed comparison includes float bits, decimal scale, binary and generated-column original values. This pure API neither reads, locks nor writes a database and does not authorize edits. Execution must re-read by original keys inside the owning locked writable transaction, compare the original descriptor/row, then perform typed writes. The descriptor is not a complete physical schema revision and cannot detect ABA. Prepared native types may be data-dependent; compare projection names and the native descriptor, not prepared type names as schema revisions.
Catalog table pages
table_page(table, limit, offset) reads one explicit qualified table in a fresh enforced read-only scope, selecting descriptor columns in physical order with dialect-quoted identifiers. Order by declared primary-key constraint order when present; no key means unspecified ordering, never an invented row identity. Require limit 1..1000 and offset 0..1000000; read one extra bounded sentinel row to report has_more, not a total count. The page carries metadata, typed result, limit, offset, order_by and has_more. Bound native row encoding to 8 MiB, reject rather than truncate excess payloads. Prepared column names must exactly match the descriptor, and changed metadata within the read scope fails. Preserve unsupported-type errors and rollback the scope on completion/failure. Independent offset pages can drift as data changes; metadata is not an atomic schema revision or authorization for row mutations.
Rust native tool rules
- INT-97 A finite grid decimal cell is the exact plain decimal string of the database's declared result scale, trailing zeros included; it is never a binary float or a fixed-precision model codec value.
- INT-98 A database cell that a typed read cannot represent in its declared type fails with
CODEC_DECODE: a MySQLUNSIGNEDvalue beyond the signed 64-bit range read as signed, the integer 2 read as a boolean, a PostgreSQLNaNread as a finite float, and an SQLite text read as an integer. - INT-99 A typed read returns each column as its declared type in the same representation on every database: a 16-bit integer as an integer, a uuid as its text, a
time(3)as text with three fraction digits, and a date as a date; a column that an insert omits takes its database default on every database. - INT-100 Rust native model generation: Generating the models of one document set twice yields the same model, manifest and external text; the model that the crate builds from the same documents equals the generated one once the build's paths are replaced with the generation's paths.
- INT-101 A
timeordatetimecolumn declared without a precision has the dialect's default precision: 0 on MySQL and 6 on PostgreSQL. MySQL refuses a precision above 6, and PostgreSQL reads a declared precision as declared. - INT-102 A grid floating-point cell carries the native IEEE-754 bits of its value (
Float32(u32),Float64(u64)), preserving signed zero and nonfinite values without JSON number coercion; a MySQL unsigned value is anUnsigned(u64)cell that preserves the whole unsigned 64-bit range. - INT-103 A temporal grid cell holds the dbspec text form of its value:
DateasYYYY-MM-DD,TimeasHH:MM:SSandDateTimeasYYYY-MM-DD HH:MM:SSin UTC, each with its fraction digits. A read of a described table writes exactly the declared precision p of the column; a read-only grid query has no declaration and writes six digits. A time outside 00:00:00 to 23:59:59.999999, a date outside 0001-01-01 to 9999-12-31 and a PostgreSQL infinity fail, and an SQLite text that is not in the dbspec form fails withGRID_TEMPORAL_VALUE. - INT-104 A read-only grid query runs in a database-enforced read-only scope on every database.
- INT-105 The
polyspec-orm-buildcommit permit rolls back a rejected native insert, update or delete; it never becomes a commit success. - INT-106 A native update locks the original row by its key in a fresh transaction, compares the descriptor and the original row, and verifies the exact post-write values before commit; a failed verification or a cancellation before commit rolls back. MySQL tables that are not InnoDB and MySQL tables with triggers are rejected.
- INT-107 A row baseline compares exact typed values: float bits, decimal scale, binary bytes and generated-column original values. A value that differs in any of them is a conflict.
- INT-108 A row baseline captures one checked immutable row: its reliable non-null declared primary-key values in constraint order and a value-sensitive SHA-256 revision of the descriptor and typed cells. A later change to the page does not change the captured cells.
- INT-109 A row baseline is rejected for a view, an unreliable identity, a bad projection or key, a malformed decimal, a NULL in a non-null column or a page bound. A changed descriptor, a missing or changed row and an ambiguous identity are distinct conflicts.
- INT-110 After a native write under the same lock, the exact requested values and the untouched ordinary columns are compared; generated columns may be recomputed. A coercion or an unrequested change of an ordinary column requires rollback and is never a success.
- INT-111 Validating a row update rejects duplicate, unknown and generated assignments, invalid NULLs and oversized values before any write.
- INT-112 A MySQL
SYSTEM VIEWcatalog relation is classified as a view, never as a table or a writable row identity. - INT-113 Describing a qualified table preserves its native column types, declared primary-key order, actual nullability and generated-column flags. Identity requires an ordinary or partitioned table with a nonempty, entirely non-null primary key.
- INT-114 A table page reads the descriptor columns in physical order, ordered by the declared primary-key constraint order when there is one, with a limit from 1 to 1000 and an offset from 0 to 1000000;
has_morecomes from one extra bounded row, not from a total count. - INT-115 A query whose result exceeds its explicit row or byte budget is rejected, never truncated to a partial success.
- INT-116 The
polyspec-orm-buildRust tool preserves a typed bind's value in the database. A typed NULL uses its selected native bind kind, not empty text or zero. - INT-117 A typed catalog write reads back the cell it wrote when the value has the column's declared fraction digits. A write that post-write verification or a conflict rejects rolls back.
- INT-118 Explicit PostgreSQL constraint or transaction rejection is distinguished from a missing acknowledgement and needs rollback confirmation before
ROW_COMMIT_REJECTEDis reported. An unconfirmed commit error is indeterminate and is never retried automatically. - INT-119 Trigger absence is valid only with a verified direct account grant. Role-only grants and enabled partial revokes are rejected, not assumed visible.
- INT-120 The
polyspec-orm-buildnative tool refuses a bind list of more than 65535 parameters, or of total value bytes beyond 16 MiB, before preparing or executing withTOOL_BIND_LIMIT. - INT-121 A table page is refused when its prepared column names do not match the descriptor, or when the metadata changes within its read scope.
- INT-122 The
polyspec-orm-buildRust tool rejects a malformed decimal, a nonfinite MySQL float, a SQLite NaN and PostgreSQL text with a NUL before execution withTOOL_BIND_INVALID. - INT-123
insert_rowrequires every omitted primary-key component to haveautomatic_keyordefault_expression, otherwise returnsROW_INSERT_IDENTITY_REQUIRED; it returns omitted keys from the database and returnsROW_INSERT_UNSUPPORTEDfor a key shape the dialect cannot generate. It rejects existing keys and unknown, duplicate or generated assignments, requires exactly one affected row, rereads the row by its declared key, and compares every supplied value exactly. - INT-124 The
polyspec-orm-buildcrate's Rusttool_dbquery result fails withTOOL_QUERY_COLUMNSif its column metadata exceeds 2048 columns or its column names and native types together exceed 64 KiB. - INT-125 The
polyspec-orm-buildcrate's Rusttool_dbbounded query fails withTOOL_QUERY_STATEMENTif its text does not split into exactly one statement. - INT-135 The
polyspec-orm-buildcrate's Rusttool_dbquery result lists each output column in statement order with its native type, including duplicate names, and keeps those columns when the result has no row. - INT-136 A read-only grid query accepts one statement that only reads: a
SELECT, or aWITHquery whose parts only read. A;inside a comment or a string literal does not end the statement. It rejects a statement that writes, commits, calls a procedure, selectsINTO, takes a lock withFOR UPDATE, or is followed by a second statement; on PostgreSQL it also rejects a query with 200UNION ALLoperands or 200+terms. - INT-137 The
polyspec-orm-buildcrate's Rust catalog connection does not return a read-only scope left open by a cancelled task to the pool: it discards the connection, and the next checkout can write. - INT-138 Each implementation's native package entry exposes its declared public client surface to a consumer import: the Go module package, PHP Composer autoloader, loaded PHP extension declarations, TypeScript package exports, Python package import and Rust crate root. The consumer resolves the entry through that native form and can use its public declarations; test-only entry points remain governed by their own requirements.
- INT-141 Rust's
polyspec-ormAPI exposesValfor row values:Null,I64,F64,Str,Bytes,DateTime,Date,Bool,JsonandOrdered.Nullrepresents SQL NULL;Orderedretains ordered JSON, including JSON literal null, distinct fromNull. Its public typed accessors reject invalid or lossy conversions withCODEC_DECODE; its publictake_*methods leaveVal::Nullon success and preserve the original value on conversion failure. The crate reexportschronoandordered_jsonfor the date, time and ordered-value payload types. - INT-142 Rust's
polyspec-orm::rowAPI adapts SQLx MySQL, PostgreSQL and SQLite rows asDriverRow;read_cellandread_rowdecode them toVal, andCellsandSrcexpose typed reads for i64, f64, bool, string, datetime, date and styled cells. Reads return errors instead of replacing invalid cells with defaults. - INT-143 Go's
github.com/polyspec/orm/packages/go/ormAPI exportsPlanID(key uint64) stringas a plan-cache-key formatter. It returns exactly 16 lowercase hexadecimal digits, left-padded with zeroes. This is a Go-specific package API. - INT-144 PHP package
polyspec/ormexposesPolyspec\Orm\Go::quoteandGo::json, plusGoMap.quotewrites Go-compatible quoted text, preserving printable Unicode and escaping controls and invalid UTF-8 bytes.jsonwrites compact or indented JSON text with Go-compatible string escaping (including<,>and&), replacing invalid UTF-8 with U+FFFD and rejecting unsupported values such as floats. PHP lists retain element order; ordinary associative arrays retain insertion order;GoMapwrites object keys in sorted order. - INT-145 The Python distribution declares its native host-integration entry points: the
orm-genconsole command (polyspec.orm.bin_orm_gen:main); model-generation functionspolyspec.orm.generate.python.generate_modelsandrender_models, and scannerpolyspec.orm.generate.scan.scan_model_calls; driver adapterspolyspec.orm.driver_cells.mysql_conversionsandpostgres_loaders; placeholder declarationspolyspec.orm.placeholders.MYSQL_QUOTES,POSTGRES_QUOTES,for_mysqlandfor_postgres; and wall-clock sourcepolyspec.orm.clock.wall_micros. Their existing semantics remain owned by DSL-128/151/152, COD-2, DIA-197 and PRT-33 respectively. This Python-native inventory does not claim common callable signatures or add equivalent APIs to other clients. - INT-146 PHP-extension package
polyspec/orm-php-extexposes the samePolyspec\Orm\Go::quote,Go::jsonandGoMaptext behavior as INT-144 through native extension declarations. Its quoted strings and JSON documents match the Go-compatible escapes, ordering, invalid UTF-8 handling and unsupported-value errors specified there. - INT-147 The Python
polyspec-ormpackage exposes its engine dialect hooks in Python form:MysqlDialect.restart_identity,PostgresDialect.restart_identity,SqliteDialect.restart_identity,PostgresDialect.read_uuid,PostgresDialect.write_uuidandSqliteDialect.row_lock. These are native declarations for the existing restart-identity, UUID and row-lock semantics (PRT-2/27, DIA-48/PRT-67 and PRT-8); they do not imply matching callable dialect methods in other clients. - INT-148 The Python wheel exposes its test-only fault facade as
polyspec.orm.testing.fail_next_rollback,fail_next_statement,fix_clockandlast_affected. These imports are explicit and are not re-exported bypolyspec.orm; their behavior is defined by PRT-47, PRT-52, PRT-57 and PRT-62.