Skip to content

Implement Mapper and Session end to end #9

Description

@piraz

Goal

Deliver the first complete SQLok ORM vertical slice with SQLAlchemy-grade developer ergonomics translated into idiomatic Go:

model-oriented query
  -> Session
  -> prepared Core plan
  -> Executor
  -> Mapper
  -> Identity Map
  -> entity

Normal application code must not coordinate StatementCache, PlanRegistry, CompiledStatement, bind layouts, ArgumentBuffer, reflection metadata, row scanning, or Identity Map registration.

Architectural boundary

Mapper owns

  • immutable recursive metadata for structs and embedded fields;
  • sqlok tag parsing, column names, and primary-key field paths;
  • deterministic mapped-column ordering;
  • row scan destinations and row-to-entity population;
  • deterministic primary-key and mapped-value extraction.

Mapper is stateless with respect to execution. It does not own a database handle, Identity Map, pending entities, snapshots, dirty state, transactions, compiled plans, or Flush decisions.

Session owns

  • Identity Map registration and pointer reuse;
  • pending entities;
  • snapshots or field fingerprints for dirty checking;
  • database-backed Load/Query orchestration;
  • explicit Unit-of-Work and transaction coordination;
  • Flush planning for INSERT and UPDATE operations.

Session consumes Mapper metadata and must not repeat primary-key or mapped-field reflection.

Delivery sequence

  1. Lock the Mapper API, error contract, descriptor ownership, and performance budget.
  2. Implement immutable Mapper descriptors, embedded-field traversal, tags, columns, and primary keys.
  3. Implement row scanning and deterministic value extraction.
  4. Benchmark cold descriptor construction and warm per-row mapping against handwritten baselines.
  5. Refactor Session identity handling to consume Mapper metadata.
  6. Implement database-backed Session.Load through prepared SELECT execution.
  7. Prove Identity Map registration and pointer reuse end to end.
  8. Implement Session.Flush for pending INSERTs and dirty persistent UPDATEs.
  9. Benchmark the complete ORM path and document every accepted regression.
  10. Consolidate the public model-oriented API without exposing engine plumbing.

Performance gate

Benchmarks must use -benchmem -benchtime=1s -count=10 and benchstat and must report cold and warm paths separately.

Required comparisons:

  • descriptor construction;
  • cached descriptor lookup;
  • handwritten row assignment versus Mapper scan for 1, 100, and 1,000 rows;
  • primary-key and mapped-value extraction;
  • Session Identity Map hit;
  • prepared Load miss through Executor and Mapper;
  • Flush planning separately from database latency.

Hard requirements:

  • no recursive metadata discovery per mapped row;
  • immutable descriptors are reusable concurrently;
  • deterministic field and column order;
  • race-clean shared metadata access;
  • database, network, and server time stay outside microbenchmarks.

A warm-path result is materially over budget when it is statistically slower than the handwritten baseline by both more than 25% and more than 100 ns per row, or adds more than one allocation per mapped row. A measured miss does not permit hiding or weakening the result.

If a material regression cannot be corrected within the current slice, create linked technical debt before closing the affected task. The debt record must include benchmark evidence, affected path, impact, reason for deferral, optimization direction, and a reproducible acceptance threshold. Update the performance documentation in the same commit.

Acceptance criteria

  • Mapper and Session responsibilities match the documented boundary.
  • Embedded structs and simple, pointer, and composite primary keys are covered.
  • Unknown, duplicate, missing, inaccessible, and incompatible mappings return actionable errors rather than panic.
  • Session.Load returns the already tracked pointer on Identity Map hits.
  • A Load miss executes one prepared query, maps one entity, registers it, and reuses it later.
  • Flush emits INSERT for pending entities and UPDATE for dirty persistent entities through Core and Executor.
  • Core and direct database/sql usage remain independent from Session.
  • Full tests, race detector, vet, formatting/import checks, E2E tests, and performance evidence pass.
  • Changes land as reviewable atomic commits.

Non-goals for this slice

  • relationships, eager/lazy loading, and cascades;
  • automatic prepared-query promotion;
  • DDL and migrations;
  • vendor driver ownership;
  • replacing sqlc for fixed SQL-first hot paths.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions