Skip to content
Aabha AI Academy

Stage 4 · L20

Keep SQL queries subordinate to one atomic business operation

Core · original Session 4

Native checkpoint 04

Download starter · Download solution

Use the starter for this stage's focused examples. The cumulative transfer and solution belong at the stage-end capstone. Baseline checks pass; transfer checks initially fail. Downloads contain the matching native starter and solution for this stage.

An atomic operation either commits all of its intended stored changes or leaves none. This is broader than running several valid SQL statements. A service defines which changes belong together and opens one transaction around them. Queries may flush but must not commit independently.

Read the stage-02 customer/rental transfer again, then compare the stage-04 create/patch/delete services. The parent flush obtains a key but does not make the row visible to an observer. A child constraint failure raises out of the context and rolls back the parent too. A service that caught the child error and returned success could accidentally commit only the parent.

Atomicity does not automatically protect a business read-then-write race. Two transactions can both read old capacity and then each insert a rental. Stage 05 coordinates that decision through an equipment row lock shared by every reservation path. Transaction scope and concurrency policy must be chosen together.

Output validation is inside the write context. A public response that cannot be constructed should fail before commit. The transaction's successful exit finishes the write; only then does the service return its DTO. A fresh observer session is essential evidence, because the same session's identity map can display an object that was never made durable.

SQL and ORM expressions describe the same stored selection when their predicates match. query_examples.py compares a parameterized SELECT by name with select(Equipment).where(Equipment.name == name). Both reject an injection-shaped literal as a nonmatching name, because it is data rather than concatenated SQL. A stored state update plus an AuditEvent must also commit together; stage09 implements and deliberately fails that second write to prove this early atomicity principle.

Begin one transaction; Flush the first write; Validate/flush second write; alternative outcomes: Success / Commit both / together or Any failure / Rollback both / together;
Begin one transaction; Flush the first write; Validate/flush second write; alternative outcomes: Success / Commit both / together or Any failure / Rollback both / together;

Follow the running code

Focused lesson example; see the end-of-stage capstone for the cumulative app · stage 04

SQL: SELECT id FROM equipment WHERE name = :name
ORM: select(Equipment.id).where(Equipment.name == name)

One business transaction → intended writes and valid public output
                         ↙                       ↘
                 exception: rollback       success: commit

Predict and observe this focused example using the concepts explained above. Its boundary is stated in the focused answer.

Guided lab

  1. Read the explanation and predict the focused example’s outcome.
  2. Compare an injection-shaped literal name through parameterized SQL and ORM. Explain why a child failure must leave no parent row.
  3. Compare the observed outcome with the focused answer and state its boundary.

Expected: Both compare the submitted value as data and return no matching row; neither concatenates it into executable SQL. One outer operation owns all intended changes. A child exception must escape that context so the parent rolls back too. Public output must validate before successful exit. This answer uses transactions taught in L15; it does not implement later rental locking.

  • A flushed identity is not durable evidence; failure after the first write must roll back the whole operation.

Focused exercise and answer

Complete this focused exercise before reading its answer. The full native transfer is introduced only at the end of the stage.

Your transfer task: Compare an injection-shaped literal name through parameterized SQL and ORM. Explain why a child failure must leave no parent row.

  1. Compare an injection-shaped literal name through parameterized SQL and ORM. Explain why a child failure must leave no parent row.
Inspect the matching answer

This answer addresses the focused exercise above; the cumulative implementation is shown only after the stage prerequisites.

Both compare the submitted value as data and return no matching row; neither concatenates it into executable SQL. One outer operation owns all intended changes. A child exception must escape that context so the parent rolls back too. Public output must validate before successful exit. This answer uses transactions taught in L15; it does not implement later rental locking.

Stage 04 capstone — after these prerequisites

Implement the PATCH service preserving omission, rejecting explicit null/empty/unknown fields and proving stored effects.

Use the downloaded starter after completing this stage's focused exercises. The cumulative implementation below is a stage transfer answer, not an answer to an earlier lesson.

python run_checks.py --stage 04 --role starter --prepare
python run_checks.py --stage 04 --role starter
python run_checks.py --stage 04 --role starter --transfer

From this extracted starter: preparation and baseline pass; transfer initially fails only at the named unfinished target. After implementing it, rerun the same starter --transfer command and expect success.

Optional comparison in a separate solution directory

Optional comparison: download and extract this stage’s solution ZIP into a separate directory. Change your terminal into that extracted solution root (beside checkpoint.json and run_checks.py) before running the following commands. Your starter remains a starter even after you implement its task.

python run_checks.py --stage 04 --role solution --transfer
Inspect the cumulative capstone implementation
def patch_equipment(session, identifier, payload):
    changes = payload.model_dump(exclude_unset=True)
    if not changes:
        raise DomainError(422, "empty_patch", "Supply at least one changed field")
    if any(value is None for value in changes.values()):
        raise DomainError(422, "null_patch", "These catalogue fields cannot be null")
    with session.begin():
        row = equipment_by_id(session, identifier)
        if row is None:
            raise DomainError(404, "equipment_missing", "Equipment not found")
        for key, value in changes.items():
            setattr(row, key, value)
        session.flush()
        output = EquipmentOut.model_validate(row)
    return output

Check your reasoning

Can committing the first write separately preserve one atomic two-write operation?

Show the explanation

No. If the second write fails after the first was separately committed, the first fact remains. One owning transaction commits both on success or rolls back both on failure.

Reading progress

54 lessons remain open to guests. Marking a lesson read records reading only; it does not award assessment credit or a certificate.

Device reading marks require browser storage. Reading is always available.

Sign in or create an account to save separate account progress. Your current page is kept.