Rebuilding the ORM for AssegaiPHP 0.9.0

How AssegaiPHP 0.9.0 turns the ORM into a more honest SQL-family foundation, with real dialect support, safer query paths, and cleaner entity metadata.

Rebuilding the ORM for AssegaiPHP 0.9.0

AssegaiPHP 0.9.0 is the ORM stability release.

That sounds tidy, but the work underneath it was not a tidy little feature pass. It was a long look at the first-party data story and a decision to make it more honest.

Before this milestone, the ORM had useful pieces, but the shape was uneven. MySQL carried too much of the mental model. SQLite and PostgreSQL existed, but not with the level of confidence we wanted. MSSQL was not a true sibling yet. Some shared SQL builders knew too much. Some entity attributes looked like neutral metadata while quietly carrying SQL-specific assumptions.

0.9.0 is the release where we stopped letting those things stay blurry.

The goal was honesty

The goal was not to make every database feature perfect in one release.

The goal was to make the ORM tell the truth about what it supports, where dialect behavior lives, and how application code should describe persistence intent.

That meant working through several layers:

  • query builder roots
  • SQL statement builders
  • datasource setup
  • schema generation
  • entity metadata
  • docs and examples
  • real test lanes for the databases we claim to support

The result is a much stronger SQL-family foundation.

The query builder is now dialect-aware

One of the biggest changes in 0.9.0 is that SQL generation no longer leans on one generic path and hopes the differences can be patched later.

The ORM now has clearer dialect roots for the main statement families:

  • select
  • insert
  • update
  • delete
  • create and drop
  • alter
  • rename
  • describe
  • truncate

That matters because SQL dialects are not just small spelling differences. PostgreSQL, SQLite, MySQL, MariaDB, and SQL Server each have places where they need real ownership of behavior.

For example, SQL Server has its own identifier quoting, paging syntax, database guard syntax, table description queries, and rename behavior. PostgreSQL has its own returning behavior, schema qualification, sequence handling, and metadata queries. SQLite has its own table rebuild constraints and lightweight local-development strengths.

The 0.9.0 rewrite moves those differences into the right layers.

That makes the code easier to extend, but more importantly, it makes the generated SQL easier to trust.

MSSQL became real

MSSQL support is one of the most visible changes in this release.

It is not just a few rendering methods.

0.9.0 adds a real SQL Server path with:

  • an MSSQL query-builder surface
  • SQL Server identifier quoting
  • SQL Server column rendering
  • SQL Server create and drop behavior
  • SQL Server describe and rename behavior
  • datasource support through pdo_sqlsrv
  • a dedicated composer test:mssql lane
  • live integration coverage against a running SQL Server instance

That last point matters.

Rendering SQL strings is useful, but it is not the same as proving the runtime path can connect, create tables, inspect schema, persist records, rename tables, truncate rows, and survive the small surprises SQL Server brings with it.

0.9.0 crosses that line.

SQLite and PostgreSQL are no longer side paths

SQLite is now treated as a real local development and testing target.

That means the ORM has to care about how SQLite actually works, including table rebuilds for schema alteration, relation behavior, insert paths, upserts, and the constraints of a lightweight embedded database.

PostgreSQL also moves from partial support toward a proper dialect path. The work includes stronger SQL rendering, RETURNING support for inserts, schema-aware table qualification, better introspection, and more honest metadata handling.

The practical outcome is simple:

You can now reason about the ORM as a SQL-family tool, not as a MySQL-first tool with several loosely attached alternatives.

Query execution got safer

The rewrite also tightened security-sensitive query paths.

Raw query execution now consistently prefers prepared statements instead of switching behavior based on whether parameters are present. PostgreSQL also avoids relying on emulated prepares where native prepares should be the safer expectation.

This does not mean every possible SQL bug is impossible now. No serious release note should pretend that.

It does mean the default posture is better. The ORM now does less surprising work behind your back, and the tests cover more of the places where a single-driver implementation used to hide problems.

Entity metadata is getting cleaner

0.9.0 also starts a longer transition in the entity and attribute model.

For a long time, the base Entity attribute carried too much mixed meaning. Some options were general persistence metadata, while others only made sense for SQL storage.

In 0.9.0, new code should prefer:

#[Entity(
  table: 'audit_logs',
  dataSource: 'analytics',
  driver: DataSourceType::POSTGRESQL,
)]
#[SqlEntityOptions(schema: 'reporting')]
class AuditLogEntity
{
}

The split is deliberate.

Entity should describe shared persistence intent. SqlEntityOptions should carry SQL-specific storage choices such as:

  • schema qualification
  • engine selection
  • SQLite WITHOUT ROWID

Existing Entity declarations that use database, engine, schema, or withRowId still work in 0.9.0. This is a compatibility transition, not a sudden break.

But the direction is clear: the ORM is moving toward a backend-neutral metadata core with backend-specific concerns expressed in companion layers.

The unsigned identifier rule stays

One decision we kept intentionally: generated primary keys and default relation keys still preserve unsigned semantics where the dialect supports them.

For MySQL and MariaDB, using an unsigned generated bigint for identifiers is sensible. Primary keys do not usually need negative values, and the type should not waste space pretending they do.

For dialects that do not support unsigned integers in the same way, the renderer maps that semantic intent to the closest correct database representation.

That is the kind of portability we want: preserve the intent, then let the dialect layer render honestly.

The docs changed because the model changed

The guide updates are part of the release, not an afterthought.

The ORM docs now talk more consistently about:

  • data sources instead of only databases
  • dataSource on new entity examples
  • SQL-family driver support, including MSSQL
  • SqlEntityOptions for SQL-only storage concerns
  • the query builder as a dialect-aware SQL-family API

This matters because a framework's docs are part of its API.

If the examples teach the old mental model, users will keep writing old-shape code even after the internals improve. 0.9.0 brings the story and the implementation closer together.

What 0.9.0 does not claim

This release makes the ORM much stronger, but it does not pretend the journey is finished.

0.9.0 does not claim:

  • non-SQL backends are ready
  • every dialect edge case is solved
  • every schema workflow is perfect
  • the final metadata model is complete

The release is important because it makes the foundation honest enough for the next steps.

The next larger destination is a cleaner metadata architecture that can support future backend families without forcing everything through SQL-shaped language.

That is why this release starts with a compatibility-friendly split instead of a hard break. We get a better direction now, while leaving room for a deeper redesign later.

Why this release matters

0.9.0 is not flashy in the way a brand-new feature can be flashy.

It is the kind of release that makes the framework more believable.

The ORM now has real dialect ownership. MSSQL is part of the actual runtime story. SQLite and PostgreSQL are treated with more respect. Query execution is safer. Entity metadata is less MySQL-assumptive. The docs are closer to the code.

That is the work that turns "supported" from a label into a responsibility.

For teams building with AssegaiPHP, 0.9.0 should feel like firmer ground under the data layer.

And for the framework itself, it clears the path for the next phase: a broader, cleaner, backend-neutral persistence model built on top of a SQL foundation that is finally sturdy enough to carry it.