Skip to content

feat(database): add Compatible indexes for Mongo and SQL dialects - #1613

Open
ChrisPdgn wants to merge 9 commits into
mainfrom
feat/database-dialect-indexes
Open

ChrisPdgn wants to merge 9 commits into
mainfrom
feat/database-dialect-indexes

Conversation

@ChrisPdgn

@ChrisPdgn ChrisPdgn commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Description

Rewrite of the approved #643 index work from current origin/main (not a cherry-pick/rebase of the stale indexes branch).

Schema indexes are now first-class on MongoDB, PostgreSQL, MySQL, MariaDB, and SQLite. Platform models (authorization + chat) declare CompatibleIndexType Ascending/Descending so the same indexes exist on SQL too.

What this does

  • Adds CompatibleIndexType (Ascending / Descending) plus optional unique. Maps to Mongo 1/-1 and SQL BTREE ASC/DESC.
  • Index identity is fields in order + unique. Name is a label: adopt the live engine name when that identity already exists, otherwise generate a deterministic cnd_idx_* / cnd_uidx_* name.
  • Register (platform and custom-module gRPC) binds declared indexes to live Mongo/SQL names before create/sync(), so upgrades do not throw Mongo 86 or create a second SQL btree.
  • Admin extras in _DeclaredSchema survive module re-register. Stale generated names for an identity now bound to a live name are dropped. Leftover live indexes are not dropped on register; use deleteIndexes.
  • Admin create / get / delete / import / export persist via _DeclaredSchema. No full schema rebuild/sync() for Admin index create/delete.
  • Create/delete persist only what the engine actually applied (including identity-bound skips). Unique-data / options-conflict do not persist. Later failure still persists the applied prefix, publishes, then throws.
  • Persist re-reads _DeclaredSchema from primary. SQL same-name conflicts (42P07 / 1061) reuse only when fields+unique match; same name with different fields throws.
  • getIndexes uses the live engine as source of truth and overlays declared types by name, then by identity.
  • After persist (and after register bind/save), publishSchema notifies replicas. registeredSchemas is snapshotted after bind/save so replica catch-up sends live names. Replicas use instanceSync=true and do not create indexes.
  • SQL adapters use originalSchema.collectionName everywhere (no hardcoded cnd_ prefix).
  • Admin create is privileged for unique indexes. Import is not privileged and respects schema owner.
  • Export/import are Admin-only and paginated (skip/limit). Same-name indexes are skipped on import; create still binds by identity so it cannot duplicate a live index on the same fields.
  • validateModelOptions accepts indexes and still allows conduit.readPreference.
  • Mongoose dropIndex is awaited. autoIndex is off.
  • On schema recover, foreign-dialect leftover types are warned and skipped. Admin-bound invalid types throw.

SQL scalar Relation indexes
Sequelize stores a scalar Relation such as ChatMessage room as roomId. Indexes now follow that mapping instead of skipping every Relation:

  • Declared field room ↔ engine column roomId for SQL bind / create / get. Live roomId compounds adopt the declared room identity.
  • Index names are generated from declared fields only (room, not roomId), so Mongo and SQL keep the same cnd_idx_* labels.
  • Authz String *Id fields (resourceId, subjectId, …) are not rewritten. Only actual scalar Relation fields get the Id suffix.
  • Array / extracted Relations stay unindexed on the parent table. ChatRoom participants compounds are still skipped.
  • MySQL JSON Compatible btree stays rejected (e.g. ObjectIndex inheritanceTree).
  • Field-level scalar Relation indexes are lifted to modelOptions.indexes before extractRelations. Field-level array Relations are still skipped.
  • Admin create/import canonicalize inbound roomIdroom when room is a scalar Relation. getIndexes / export return declared names.
  • Create remains primary-only. instanceSync is unchanged (replicas do not create indexes).

Related SQL register fixes on this branch

  • Chat module register is Promise.all again.
  • SQL ChatRoom / ChatParticipantsLog register no longer deadlocks waiting for the related model to sync() first.

Intentionally not changed / not ported

  • getDatabaseType() still returns 'PostgreSQL' (no 'postgres' rename).
  • No Client API, no BullMQ, no masterkey.
  • Does not conflate with ReBAC ActorIndex / ObjectIndex documents — only their schema index declarations changed to Compatible.
  • Did not port old feat(database)!: add index support for all sql dialects & index refactoring #643 bugs: case 'mysql' || 'mariadb', metadata-only getIndexes, createSchemaFromAdapter rebuild for indexes, unbounded export, required index names, import always treating unique as database-owned, ReverseMongoIndexTypeMap display-name round-trip.
  • Out of scope: cross-dialect export/import, HASH-on-MySQL as a product goal, renaming leftover live indexes, restoring field-level fields.*.index from module source after Admin deleted that field index, indexing array/extracted Relations on the parent table, MySQL JSON Compatible btree.

Fixes #643 (implementation rewrite; this PR does not close or merge #643 by itself).

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Unit tests in modules/database/src/__tests__/indexes/ (bind, unique-collision vs name-exists, extras union, partial persist, overlay-by-identity, primary persist read, SQL same-name/different-fields, publish, declared room ↔ engine roomId, Authz String *Id safety, ChatRoom participants skip, Admin canonicalize):

cd modules/database
NODE_OPTIONS=--experimental-vm-modules ./node_modules/.bin/jest --testPathPatterns=src/__tests__/indexes --forceExit

Result: 5 suites, 92 passed. tsc --noEmit in modules/database is clean (after proto codegen).

Live-engine integration tests were not run in this environment (no Mongo/SQL). Recommended follow-up smoke:

  • Compatible unique + descending compound on Mongo, PostgreSQL, MySQL/MariaDB, and SQLite, including re-register of a schema that already has engine-default names
  • ChatMessage room / roomId compounds on PostgreSQL and MySQL
  • ChatRoom participants still skipped
  • Authz String *Id unchanged (resource stays resource)

Test Configuration:

  • Unit tests via Jest + ts-jest (ESM)
  • Node 24
  • Workspace packages @conduitplatform/database, @conduitplatform/grpc-sdk

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Remaining risks

  • showIndex row shapes differ by SQL dialect; unusual engine-specific indexes may normalize oddly.
  • Compound Compatible DESC uses Sequelize { name, order } field objects — worth a live MySQL/MariaDB/SQLite check.
  • Identity is fields + unique, not sort direction. Opposite-direction indexes on the same fields are treated as the same index.
  • Admin create persist merges by name; stale generated metadata for an identity already live is cleaned on the next module re-register.
  • Live SQL stores roomId while Admin/export now speak declared room. Older exports that used roomId are canonicalized on import when room is a scalar Relation.
  • A declared Relation already named *Id would become *IdId (Sequelize relation + 'Id'). None of the platform models do that.
Open in Web Open in Cursor 

@ChrisPdgn
ChrisPdgn marked this pull request as ready for review September 17, 2026 09:14
cursoragent and others added 9 commits September 17, 2026 09:20
Make schema indexes first-class on Mongo, PostgreSQL, MySQL, MariaDB, and SQLite. Platform models use Compatible Ascending/Descending so the same declarations create live indexes on every dialect. Admin create/get/delete/import/export persist into _DeclaredSchema without a full schema rebuild.
Repair the ModelOptionsIndexTypes union that failed tsc, stop stuffing
Sequelize field objects into Conduit index types, and move the index
suites into src/__tests__/indexes.
Adopt engine names by fields+unique so upgrades and custom modules do not
create duplicate indexes, keep Admin extras across re-register, persist
only applied create/delete subsets, and publish bound names to replicas.
Read _DeclaredSchema from primary before index persist, reuse SQL
name-exists errors only when fields+unique match, and freeze
registeredSchemas after bind/save so replica catch-up gets live names.
…ySQL

Fail-soft Mongo GET/export on missing collections. Qualify generated
index names by table and bind live names without rename. Skip MySQL
JSON btree and SQL extracted-relation indexes on register; reject them
from Admin. Lift Mongo array Compatible indexes to createIndexes.
Register Database, Authz, and Chat schemas sequentially.
Keep Chat and Authorization registerSchemas on Promise.all, matching
other Conduit modules. Sequential register stays in Database onServerStart.
Publish the Sequelize adapter into models before resolving relations
and wait for the related adapter to exist instead of .synced so
Promise.all Chat register cannot deadlock on that cycle.
Map ChatMessage.room to engine roomId for SQL bind/create/get while
keeping index names on declared fields. Array/extracted Relations
(ChatRoom.participants) and MySQL JSON btree stay skipped. Authz
String *Id fields are left unchanged.
Compatible-index createIndexes binds against live Mongo indexes, so the
vector lifecycle mock must expose collection.indexes() for both features
to share the same adapter path after the rebase onto main.

Co-authored-by: Christina Papadogianni <ChrisPdgn@users.noreply.github.com>
@cursor
cursor Bot force-pushed the feat/database-dialect-indexes branch from 4bcc20b to a7fe0f4 Compare September 17, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants