ORM design plan
This page is the single design plan for the ORM. It replaces the earlier initial design, revised design, and DSL v3 notes. The DSL and common interface pages specify the resulting syntax and structures; this page records the goal, the rules, and the work order.
1. Goal
The ORM provides one model-based query syntax in Go, PHP, Rust, TypeScript, and Python. Every client runs the same statement as the same SQL on MySQL, PostgreSQL, and SQLite.
2. Principles
- PERF-1 The six clients measure the same primary-key and descending 100-row reads on MySQL, PostgreSQL and SQLite. Each compares typed client and native results with 100 warm-up pairs and 1,000 alternating adjacent pairs, and reports native/client p50, ratio, CPU, wall time, sample count and machine. A value above the reference is a warning and does not fail correctness. The existing procedure and pending references are recorded in performance operations.
- Every syntax rule is written explicitly in the DSL before implementation.
- PLN-1 Every method name is derived mechanically from column, operator, and connector rules. Convenience methods tied to specific columns are not provided.
- PLN-2 Every syntax is written the same way in all five languages; only language spelling and creation forms differ.
- PLN-3 Syntax without a specified rule is not used in documents, examples, or generated code.
- PLN-4 The ORM does not provide compatibility layers, fallbacks, or data conversion. A failing rule is corrected in this plan; the test is not weakened.
3. Syntax provided in five languages
- PLN-6 Connection: model creation followed by
connect(connection); the sameconnectrebinds a loaded row or collection - PLN-7 Conditions: first condition
<Chain>, thenand<Chain>oror<Chain>, connectorsand()andor(), groupsand(fn)andor(fn), negated groupsnot(fn),andNot(fn)andorNot(fn), operator prefixesGt Lt Ge Le Eq Ne Lk Lb Between Fulltext FulltextBoolean,<ColA>With<ColB>(model) - PLN-8 Reads:
get,gets,getBy<Chain>,getsBy<Chain>,getCount,getCountBy<Chain>,getsCount,sum<Col>withgetSum,avg<Col>withgetAvg,keyName<Col>,fetchKey,fetchValue - PLN-9 Columns:
addColumn<Col>,removeColumn<Col>,removeAllColumns,addAllColumns,forceIndex<Name> - PLN-10 Relations and joins:
relation,relations,match<L>With<R>,alias<Name>,parentNode,possible<Col>,groupLimit,deleteLock,join<L>With<R>,leftJoin<L>With<R>,on(fn) - PLN-11 Order and range:
orderBy<Col>Asc,orderBy<Col>Desc,groupBy<Col>,limit(offset, count) - PLN-12 Writes:
set<Col>for stored columns,new<Name>for in-memory attributes,plus<Col>,minus<Col>,create,duplication(model)withcreate,update,update(true),save,delete,delete(true) - PLN-13 Transactions:
connection.transaction(fn)with deadlock retry - PLN-14 Pages: total count and one page of rows from the same model
- PLN-15 Codec stages (DBS-69):
ip,aes,hex,serialize,base64,gz,ordered_json,yaml
serialize and aes_serialize use the published PHP serialization format; each client implements that format.
4. Complements
4.1 Transactions
- PLN-16 Inside
connection.transaction(fn), a model withoutconnectexecutes in the active transaction of the current execution flow. Outside a transaction, executing a model withoutconnectreturnsCONFIG. - PLN-17 A transaction on the same connection inside an active transaction creates a savepoint. A failure inside the inner callback rolls back only the inner work; returning that failure from the outer callback rolls back the complete transaction.
- PLN-18 Concurrent use of one transaction connection returns an error. A task or goroutine started inside the callback has no active transaction.
- PLN-19 Begin, commit, rollback, the executor, and the execution context are private.
- PLN-20 The callback result follows each language: Go returns
error, Rust returnsResult, and PHP, TypeScript, and Python throw exceptions. - PLN-21 Row locks
forUpdate,forShare,forUpdateNoWait, andforShareNoWaitare allowed only inside a transaction.
4.2 Correctness rules
- PLN-22 The generator rejects a column name that matches a reserved method (
and,or,get,gets,limit,alias,connect,create,update,delete,save) or starts with a reserved prefix (and,or,get,set,new,plus,minus,orderBy,groupBy, or an operator prefix). - PLN-23 Conditions are stored as a tree. A terminal operation does not change the builder, so a second terminal produces the same statement.
- PLN-24 Deadlock retry runs for driver deadlock errors.
- PLN-25 A join index hint is placed on the joined table. Every join contributes its aggregates and ordering. The relation key default uses the parent key.
possible<Col>reads the joined parent row. - PLN-26
minus<Col>never stores a negative value.plus<Col>andminus<Col>bind their amounts. Attribute changes are cleared after a write. - PLN-27
update(true)compares the storedupdated_tsvalue that was read, not the current attribute value. - PLN-28 An empty list value returns
EMPTY_INbefore SQL execution.
5. Five-language rules
5.1 Model creation and connection
PLN-29 PHP — Creation:
new Product; Connected model:(new Product)->connect($slave1); Connected loaded row:$row->connect($master)PLN-30 Go — Creation:
model.Product(); Connected model:model.Product().Connect(slave1); Connected loaded row:row.Connect(master)PLN-31 Rust — Creation:
Product::new(); Connected model:Product::new().connect(&slave1); Connected loaded row:row.connect(&master)PLN-32 TypeScript — Creation:
new Product(); Connected model:new Product().connect(slave1); Connected loaded row:row.connect(master)PLN-33 Python — Creation:
Product(); Connected model:Product().connect(slave1); Connected loaded row:row.connect(master)PLN-34 Creation uses each language form. Go uses a function in the generated
modelpackage and returns*model.ProductModel.PLN-35
connectis the only connection method.onis reserved for join conditions.PLN-36 PHP also accepts
(new Product)($slave1)and$row($master)as the short form ofconnect.
5.2 Conditions
- PLN-37 The first condition has no prefix. Each following condition uses
and<Chain>,or<Chain>, or a connectorand()oror()followed by<Chain>. A missing connector, a connector at the start of a group, or a connector without a following condition returnsCONFIG. - PLN-38
<Chain>accepts any column combination withAnd,Or, operator prefixes, and<ColA>With<ColB>. Arguments follow the keys in order. The same chain is valid aftergetBy,getsBy,getCountBy, andgetsCountBy. - PLN-39 Groups use
and(fn)andor(fn), and negated groupsnot(fn),andNot(fn)andorNot(fn). The callback receives an empty model of the same type and accepts condition methods only; a terminal or write inside the callback returnsCONFIG. - PLN-40 PHP and Python resolve chains at call time, while Go, Rust, and TypeScript generate the chain methods that the scanned source code calls: the generator reads that source, parses each chain, checks it against the schema, and writes only those methods.
rows, err := model.Product().Connect(slave1).
GetsByServiceSeqAndIsCloseAndLtSaleStartDt(serviceSeq, 0, now)
rows, err = model.Product().Connect(slave1).
ServiceSeq(serviceSeq).
And(func(q *model.ProductModel) {
q.IsSaleAndLtSaleStartDt(1, now).Or().IsSale(2)
}).
Gets()$rows = (new Product)->connect($slave1)
->serviceSeq($serviceSeq)
->and(fn (Product $q) => $q->isSaleAndLtSaleStartDt(1, $now)->or()->isSale(2))
->gets();5.3 Relations and joins
- PLN-41 A relation or join child without
connectuses the parent connection. - PLN-42 A relation child with
connectruns its separate query on that connection. Inside a transaction, that query uses the connection's active transaction in the same flow, if one exists. - PLN-43 A join child with
connectreturnsCONFIGbecause a join is part of one statement.
6. Additional capabilities
- PLN-44 Operations: AES key rotation and status, row locks, savepoints, transaction options (isolation, read-only, timeout), bulk insert, SQL output, schema-checked query forms that reject raw SQL fragments, schema installation and migration, large
INlist splitting
7. Work order
- Write the DSL, common interface, guide, and README with Korean pages from sections 3 to 6.
- Update
contracts/interfaces.json,contracts/features.json, andcontracts/rules.jsonto the syntax in this plan. - Add failing conformance vectors for list with relations, upsert, optimistic update, recursive delete, multi-write transaction, relation on another connection, section 4.2 defects, and section 4.1 transaction rules.
- Update generators and runtimes in the order Go, PHP, Rust, TypeScript, Python, and regenerate clients.
- Run
make orm-check, SQLite, PostgreSQL, and MySQL conformance, the public symbol audit, repeated generation, andmake docs-check. - Regenerate the Platform, SDK, and module models and update their callers.
8. Completion criteria
- Documents, manifests, generated clients, and tests describe the same syntax.
- These conformance vectors pass in five languages and three databases.
- Every section 4.2 regression test passes, and the public symbols match the common interface.
- A task that does not meet these criteria is not recorded as complete.