DrivineMetamodelVersionStore

class DrivineMetamodelVersionStore(persistenceManager: <Error class: unknown class>, clock: Clock = Clock.systemUTC())

Drivine/Neo4j implementation of SweptBaselineStore: a MetamodelVersionStore that also keeps a durable pointer at the declaration a sweep last finished reconciling. Every schema stamp is a (:MetamodelVersion) node.

The write MERGEs on the natural key (schemaName, contentHash), so a retry or a re-stamp of an unchanged schema updates the node that's already there. That is race-free only under a uniqueness constraint on the same pair of properties: without one, concurrent MERGEs all miss, all take the CREATE branch, and history fills with copies of one version.

Every statement is parameterized; nothing user-derived is interpolated into Cypher. Ordering and the keyed lookup both run in the database.

Ordered reads sort on a per-schema counter. "Most recent" in the contract means logical write order, which a wall clock can't express: two saves can land in the same millisecond, and an NTP correction or a failover to a differently-skewed node can make the clock run backwards between them. Each schema owns a (:MetamodelSchemaCounter) node, and a version takes the next value off it when its node is first created. savedAt and savedAtEpochMillis are informational; nothing sorts on them.

The counter is bumped in the same statement, and so the same transaction, as the MERGE that creates the version, so a version node always carries its place in the order. A re-save of an existing version leaves the counter and the sequence alone, which keeps the write idempotent and holds an old stamp at its original position. The in-memory reference implementation behaves the same way.

Three uniqueness constraints are required, and the host declares them in a SchemaCatalog bean (the module's TestApplication shows the shape):

  • UniquenessConstraintSpec("MetamodelVersion", listOf("schemaName", "contentHash")) makes the version MERGE race-free, as above.

  • UniquenessConstraintSpec("MetamodelSchemaCounter", "schemaName") makes the counter MERGE race-free, so one schema can only have one counter handing out numbers.

  • UniquenessConstraintSpec("MetamodelVersion", listOf("schemaName", "sequence")) makes two versions sharing a position in the order unstorable, so a lost counter update fails with a constraint violation the caller can retry.

Retrying a failed save. If the counter's read-modify-write ever does lose an update, the second writer fails with a uniqueness-constraint violation on (schemaName, sequence). saveVersion just lets that exception propagate; it doesn't retry internally, because the failure has already ended the surrounding Neo4j transaction, and a retry needs a new transaction, which only the caller can open. That's safe to do: the write is an idempotent upsert, so retrying a failed save never produces a duplicate or a wrong result. The drift check re-stamps its schema on every pass anyway, so for that caller the next pass already is the retry.

The reconciled baseline sweptVersion answers is tracked apart from that write-order history, the same way com.embabel.dice.metamodel.InMemoryMetamodelVersionStore keeps a separate swept map: as sweptContentHash, a property on the schema's own (:MetamodelSchemaCounter) node, moved only by markSwept. Nothing about an ordinary saveVersion touches it, which is what makes a dry run, a scoped run, or a crash mid-sweep leave the baseline exactly where it was. Tracking the pointer durably is what earns this store the right to declare SweptBaselineStore at all: a backend that could only answer the question from write order keeps the plain MetamodelVersionStore contract, and DriftCheckRunner then stays silent about a baseline nobody tracks. See SweptBaselineStore.sweptVersion for what write order gets wrong once a declaration cycles back to a stamp it already used.

Parameters

persistenceManager

Drivine's handle on the neo datasource.

clock

supplies the instant a version is stamped as saved at. Injectable so a test can pin the instants of two saves.

Constructors

Link copied to clipboard
constructor(persistenceManager: <Error class: unknown class>, clock: Clock = Clock.systemUTC())

Functions

Link copied to clipboard
open fun findVersion(schemaName: String, contentHash: String): <Error class: unknown class>?

Overridden to resolve a recorded hash with a single keyed MATCH; the interface default reads the schema's whole history and filters it in memory. Both halves of the natural key are in the pattern, which is what the uniqueness constraint indexes.

Link copied to clipboard
open fun latestVersion(schemaName: String): <Error class: unknown class>?
Link copied to clipboard
open fun markSwept(version: <Error class: unknown class>)

Moves the reconciled baseline to version, and saves the stamp into the ordinary history on the way, so a caller that only ever calls this for a brand-new declaration still gets it stored. Both writes run in the one transaction, so a reader never observes the pointer moved without the stamp it names being resolvable.

Link copied to clipboard
open fun saveVersion(version: <Error class: unknown class>)
Link copied to clipboard
open fun sweptVersion(schemaName: String): <Error class: unknown class>?

Reads markSwept's own pointer and resolves the hash it holds back into the stamp it names. A schema no sweep has ever completed for carries no such pointer, so this answers null; see this class's own doc for why write order cannot stand in for it.

Link copied to clipboard
open fun versionHistory(schemaName: String): List<<Error class: unknown class>>