Changeset 9577c79 for docs/P2-RelationalDesign
- Timestamp:
- 09/16/26 23:37:15 (13 days ago)
- Branches:
- main
- Children:
- 8b447ef
- Parents:
- df05838
- Location:
- docs/P2-RelationalDesign
- Files:
-
- 3 edited
-
P2.zip (modified) ( previous)
-
RelationalDesign.md (modified) (5 diffs)
-
RelationalDesignAIUsage.md (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
docs/P2-RelationalDesign/RelationalDesign.md
rdf05838 r9577c79 11 11 - **Markets**(<u>**id**</u>, *crypto_id*, quote_currency, is_active, created_at) 12 12 - Candidate keys: `{id}`, `{crypto_id, quote_currency}`. `UNIQUE(crypto_id, quote_currency)`. 13 - **Holdings**(<u>**id**</u>, *user_id*, *crypto_id*, quantity, avg_price, created_at, updated_at)13 - **Holdings**(<u>**id**</u>, *user_id*, *crypto_id*, quantity, reserved_quantity, avg_price, created_at, updated_at) 14 14 - Transformation of the M:N relationship `Holds`. Candidate keys: `{id}` and 15 15 `{user_id, crypto_id}` — the latter is the relationship's own key and is … … 17 17 consistency with the other relations. 18 18 - `avg_price` is `NOT NULL DEFAULT 0 CHECK (avg_price >= 0)`. 19 - `reserved_quantity` is `NOT NULL DEFAULT 0 CHECK (reserved_quantity >= 0 20 AND reserved_quantity <= quantity)` — the amount already committed to the 21 user's own open sell orders. `quantity - reserved_quantity` (the amount 22 actually free to sell) is not a stored column; it is computed wherever 23 needed, in `v_portfolio` as `available_quantity` and in the sell path of 24 [UseCase0005](../P3-UseCaseModel/UseCase0005.md). See 25 [ERModel](../P1-ConceptualModel/ERModel.md#holds--users-m--cryptos-n-partial-on-both-sides-with-attributes) 26 for why this mirrors `available_balance`/`invested_balance` on `Users`. 19 27 - **Orders**(<u>**id**</u>, *user_id*, *market_id*, side, type, status, quantity, price, placed_at, executed_at) 20 28 - `side ∈ {buy, sell}`, `type ∈ {market, limit}`, `status ∈ {open, executed, cancelled}`. … … 56 64 ### Normalisation 57 65 66 > **Validated in P5.** [Normalization](../P5-Normalization/Normalization.md) derives this 67 > exact schema independently — starting only from a single de-normalized relation of every 68 > model attribute and its functional dependencies, with no reference to the ER-to-relational 69 > transformation below — and shows it decomposes to **BCNF**, one normal form stronger than 70 > the 3NF claimed here. The two designs agree relation for relation and key for key, so 71 > nothing here changed as a result; see that page's 72 > [discussion](../P5-Normalization/Normalization.md#discussion) for what the one real 73 > difference is (`avg_price`, a stored derived value, not a normalisation issue) and why this 74 > design is still the one used from P5 onward. 75 58 76 All relations are in **3NF**: 59 77 … … 72 90 yields `NULL`, so a nullable average would have silently blanked the 73 91 unrealised-P/L column for an existing position instead of failing loudly. 92 - `holdings.reserved_quantity`, unlike `avg_price`, is **not** derived — it is 93 written directly by the application (`trade.go`) as orders are placed and 94 settled, the same way `quantity` itself is. `quantity - reserved_quantity` 95 ("available") is the derived value here, and it is never stored, only 96 computed where it is needed. 97 98 ### Reservation and the order lifecycle 99 100 `holdings.reserved_quantity` exists so that placing a sell order can be 101 checked against what a user actually has *free* to sell 102 (`quantity - reserved_quantity`), not against the raw `quantity`, which also 103 counts crypto already promised to another order that has not settled yet. 104 `CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)` makes an 105 inconsistent reservation impossible at the database level, regardless of what 106 application code does. The exact statement sequence — lock the row, check the 107 available amount, reserve, then settle — is in 108 [UseCase0005](../P3-UseCaseModel/UseCase0005.md); the same 109 `SELECT … FOR UPDATE` locking that already protected `users.available_balance` 110 on the buy path is what makes two concurrent sell orders against the same 111 holding serialize correctly instead of racing. 74 112 75 113 ## DDL script … … 80 118 - 10 tables with check constraints, primary keys, foreign keys and unique constraints. 81 119 - 5 performance indexes. 82 - 2 views: `v_latest_prices` (latest trade price per market) and `v_portfolio` (per-user holdings valuation with unrealised P/L ).120 - 2 views: `v_latest_prices` (latest trade price per market) and `v_portfolio` (per-user holdings valuation with unrealised P/L, plus `reserved_quantity` and the derived `available_quantity`). 83 121 84 122 ## DML script (sample data) -
docs/P2-RelationalDesign/RelationalDesignAIUsage.md
rdf05838 r9577c79 69 69 > against the faculty database. No AI involvement is possible there — it needs a 70 70 > live connection to your assigned database. 71 72 ### Session 3 — 2026-09-16 73 74 Driven by the same design review logged in full in 75 [ERModelAIUsage](../P1-ConceptualModel/ERModelAIUsage.md#session-3--2026-09-16): 76 a sell order had nothing to check `holdings.quantity` against except itself, 77 so nothing stopped two sell orders from being granted the same units. 78 79 Changes to the P2 artefacts: 80 81 - `holdings` gained `reserved_quantity numeric(20,4) NOT NULL DEFAULT 0 82 CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)` in 83 `schema_creation.sql`. 84 - `v_portfolio` gained `reserved_quantity` and the derived 85 `available_quantity = quantity - reserved_quantity`. 86 - [RelationalDesign](RelationalDesign.md) gained a "Reservation and the order 87 lifecycle" section explaining why the check is enforced at the database 88 level rather than trusted to application code, and why it does not conflict 89 with the existing `SELECT … FOR UPDATE` locking on the sell path. 90 - `data_load.sql` needed no change — `reserved_quantity` defaults to 0, which 91 is correct for every seeded holding. 92 93 Re-run end to end against the live database on `localhost:5433` 94 (`-init` then `-load-data`), and against a manually seeded 2 BTC holding to 95 reproduce the exact scenario that motivated the change — see 96 [UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md) for 97 the transcript. 98 99 **What I decided:** to add the `CHECK` constraint rather than rely on 100 `trade.go` alone to keep the reservation consistent — the same reasoning 101 already applied to `avg_price NOT NULL` in session 2.
Note:
See TracChangeset
for help on using the changeset viewer.
