Files
bstore-j/README.md

6.7 KiB

BStore-j

Explicit-first successor line for the Java BStore data access layer.

bstore-j starts as a clean copy of bstore-java, but evolves independently:

  • build and publication identity are separate
  • the package surface remains familiar
  • explicit model definition becomes first-class
  • reflection remains available only as optional sugar
  • GraalVM-friendlier operation is a design goal

What It Is

bstore-j has two main pieces:

  • com.reliancy.rec
    • lightweight structured data types
    • JSON-like records, headers, slots, arrays/objects
  • com.reliancy.dbo
    • storage-oriented entity/field/record model
    • SQL-inspired actions and filters
    • SQL backend via com.reliancy.dbo.sql

The API is inspired by SQL, but the design goal is not to reproduce SQLAlchemy or raw SQL access. The goal is a small, consistent data layer that can later be ported across languages and backed by other systems.

Current Direction

The Java codebase is the reference implementation for:

  • CRUD over entities and fields
  • backend-neutral dbo contracts
  • SQL as the first reference backend
  • metadata/migration support through dbo.meta

The current meta model is intentionally lean:

  • ChangeEvent is the canonical persisted history
  • DataOriginator is a module/plugin registry snapshot
  • EntityDefinition and FieldDefinition are utility/transient planning models

Core Features

  • explicit and reflective entity definition paths
  • CRUD with Terminal, Action, Check, and Ordering
  • joined inheritance support
  • SQL backends for PostgreSQL/MySQL/SQL Server/Oracle/H2
  • streaming loads and batched writes
  • migration/change discovery through MetaTerminal
  • replay-safe structural change application
  • startup-oriented migration flow

Status

The library is being revised so that:

  • explicit metadata becomes the canonical runtime path
  • DBO remains the runtime record type
  • typed non-DBO models can use adapters
  • reflection and annotation-driven publication remain optional convenience

The active implementation plan lives in:

Explicit-First Example

import com.reliancy.dbo.DBO;
import com.reliancy.dbo.Entity;
import com.reliancy.dbo.Field;
import com.reliancy.dbo.sql.SQLTerminal;

Entity person = Entity.define("public.person")
    .setId("person")
    .field(
        Field.Int("id").setPk(true).setAutoIncrement(true).nullable(false),
        Field.Str("name").nullable(false),
        Field.Int("age")
    )
    .publish();

SQLTerminal db = new SQLTerminal("postgres://user:pass@localhost:5432/appdb");
db.meta().migrate("core", "person-v1", person);

DBO alice = DBO.of(person);
alice.set(person.getField("name"), "Alice");
alice.set(person.getField("age"), 30);
db.save(alice);

DBO loaded = db.load(person, alice.get(person.getField("id")));

Optional Sugar Example

import com.reliancy.dbo.DBO;
import com.reliancy.dbo.Entity;
import com.reliancy.dbo.Field;
import com.reliancy.dbo.sugar.BStoreRegistry;
import com.reliancy.dbo.sql.SQLTerminal;

@Entity.Info(name="public.person")
public class Person extends DBO {
    public static final Field ID = Field.Int("id").setPk(true).setAutoIncrement(true);
    public static final Field NAME = Field.Str("name");
}

SQLTerminal db = new SQLTerminal("postgres://user:pass@localhost:5432/appdb");
BStoreRegistry registry = BStoreRegistry.builder().register(Person.class).build();

registry.publishAll();
db.meta().migrate("core", "person-v1", registry.entity(Person.class));

Person loaded = db.load(registry.adapter(Person.class), 1);

Quick Start

import com.reliancy.dbo.DBO;
import com.reliancy.dbo.Entity;
import com.reliancy.dbo.Field;
import com.reliancy.dbo.sql.SQLTerminal;

@Entity.Info(name="public.person")
public class Person extends DBO {
    public static final Field ID = Field.Int("id").setPk(true).setAutoIncrement(true);
    public static final Field NAME = Field.Str("name").setTypeParams("255");
    public static final Field AGE = Field.Int("age");
}

SQLTerminal db = new SQLTerminal("postgres://user:pass@localhost:5432/appdb");

Person person = new Person();
person.set(Person.NAME, "Alice");
person.set(Person.AGE, 30);
db.save(person);

Person loaded = db.load(Person.class, person.get(Person.ID));

Query Example

import com.reliancy.dbo.Action;
import com.reliancy.dbo.DBO;

try (Action action = db.begin()
        .load(Person.class)
        .filterBy(Person.AGE.gte(18))
        .orderBy(Person.NAME.asc())
        .limit(100)
        .execute()) {
    for (DBO row : action) {
        System.out.println(row.get(Person.NAME));
    }
}

Startup Migration Example

import com.reliancy.dbo.Entity;
import com.reliancy.dbo.meta.MetaTerminal;

MetaTerminal meta = db.meta(Entity.recall(Person.class));

meta.migrate(
    "core-module",
    "release-2026-03-17",
    Entity.recall(Person.class)
);

Current behavior:

  • ensures the required bstore.change_event history table exists
  • discovers expected entity structure from code
  • discovers actual structure from the backend
  • computes ordered structural changes
  • applies unapplied changes
  • records applied changes in the change log

Module Registry Example

import com.reliancy.dbo.meta.DataOriginator;

DataOriginator module = new DataOriginator();
module.set(DataOriginator.ID, "core-module");
module.set(DataOriginator.ORIGINATOR_ID, "core-module");
module.set(DataOriginator.ORIGINATOR_VERSION, "1.0.0");
module.set(DataOriginator.INSTALLED_VERSION, "1.0.0");
module.set(DataOriginator.DESCRIPTION, "Core application module");

db.save(module);

DataOriginator is meant for module/plugin registry data, not arbitrary module state and not detailed migration history.

What Is Supported Today

  • install-style schema creation
  • additive schema upgrades
  • safe table rename within the same schema
  • replay-safe change application
  • schema downgrade by replaying a contraction plan

What Is Intentionally Not Broad Yet

  • rich relationship/foreign-key navigation
  • generic rollback synthesis
  • plugin settings/state storage in meta records
  • non-SQL backends in Java

Testing

The curated test suite uses a Postgres database via DB_URL.

export DB_URL="postgres://user:pass@localhost:5432/testdb"
./gradlew test

There are focused integration tests for:

  • CRUD and SQL backend behavior
  • change discovery ordering
  • change replay/idempotency
  • startup migration flow
  • install/upgrade/downgrade lifecycle at the schema level

Docs

License

GNU Lesser General Public License, Version 3.0