Skip to content

feat: add an "Indexed" checkbox to the advanced field creation mode - #435

Open
slyk wants to merge 1 commit into
LaWebcapsule:mainfrom
slyk:upstream/field-index-checkbox
Open

slyk wants to merge 1 commit into
LaWebcapsule:mainfrom
slyk:upstream/field-index-checkbox

Conversation

@slyk

@slyk slyk commented Oct 15, 2026

Copy link
Copy Markdown

Change description

Adds an Indexed checkbox next to Unique in the Schema tab of the advanced field creation mode (Data Model → field).
Discussed in #402. As suggested there, this is a first step: it covers plain single-column indexes only, and a full
index editor for a collection (with composite indexes) can be built on top of it later.

Behaviour

  • Checked when creating a field: the column gets a non-unique single-column index (Knex default name
    <table>_<column>_index). Unchecking it on an existing field drops that index, and checking it again adds it back.
  • The state comes from the database, not from directus_fields. The schema inspector reports a new is_indexed
    boolean for every column: true when the column is the only column of a non-unique, non-primary index. Composite indexes
    do not count, so they are never shown as checked and never dropped. Indexes created by hand in SQL show up too.
  • So schema.is_indexed is also in the fields REST API, GraphQL and schema snapshot|diff|apply. A snapshot taken before
    this change has no is_indexed; the diff ignores the property in that case, so old snapshots do not report every
    column as changed.
  • Not shown for alias and primary key fields; disabled for generated columns. Shown checked and disabled for unique
    columns (the unique constraint is already an index) and for foreign key columns that are already indexed (MySQL needs
    that index for the constraint). A foreign key without an index (for example on Postgres) can still be indexed.
  • API: is_indexed: true is ignored for primary key and unique columns. Turning Unique on drops the plain index.
  • No migration and no change to system tables; nothing changes unless the box is used.

Changes

  • @wbce-d9/schema: Column.is_indexed, filled by every dialect (MySQL/MariaDB from INFORMATION_SCHEMA.STATISTICS,
    Postgres and CockroachDB from pg_index, SQLite from PRAGMA index_list / index_info, MSSQL from the existing index
    join, Oracle from USER_IND_COLUMNS).
  • API: FieldsService.addColumnToTable creates/drops the index, same style as is_unique; is_indexed added to the
    snapshot whitelists in sanitize-schema.ts and to the GraphQL schema type; get-snapshot-diff.ts handles old
    snapshots.
  • App: the checkbox in field-detail-advanced-schema.vue, two en-US strings (indexed, value_indexed).
  • Docs: is_indexed in the fields reference.
  • Changeset: @wbce-d9/schema, @wbce-d9/api, @wbce-d9/app minor.

Things a reviewer may want to weigh in on

  1. Column.is_indexed is a new required property of a public type of @wbce-d9/schema. Code that builds Column
    objects by hand has to add it (the test fixtures here needed is_indexed: false).
  2. Unchecking drops the index by its Knex default name. A single-column index created by hand under another name shows as
    checked, but unchecking it fails with a database error (nothing is half-applied). Returning the real index name from
    the inspector would fix that; I left it out to keep this PR small.
  3. MySQL/MariaDB and Postgres need one extra query per columnInfo() call. overview() is not changed.

Testing note

  • MariaDB: used on our instance (create, uncheck, re-check, unique, composite index not counted).
  • SQLite: unit tests in api/src/services/fields.test.ts (index on create, none by default, no extra index next to a unique constraint, composite indexes not counted, add and drop on an existing column).
  • Postgres, CockroachDB, MSSQL, Oracle: the inspector queries are written but were not run against a live
    server
    . Please check them, or I can adjust them if your CI or a test database shows a problem.
  • Index creation on a large table can lock it for a while; the UI shows no warning about that.

Locally on this branch: the four related API test files pass (19 tests), tsc --noEmit passes for api and
packages/schema, and eslint and prettier pass on the changed files. I did not run the full app type check
(vue-tsc).

Type of change

  • Bug fix (fixes an issue)
  • New feature (adds functionality)

Related issues

Discussion #402

Checklists

Development

  • Lint rules pass locally
  • Application changes have been tested thoroughly (MariaDB and SQLite; other dialects see the testing note)
  • Automated tests covering modified code pass

Security

  • Security impact of change has been considered
  • Code follows company security practices and guidelines

Only admins can change the data model, as before. The new inspector queries use bound parameters for the table name.

Network

  • Changes to network configurations have been reviewed
  • Any newly exposed public endpoints or data have gone through security review

No new endpoints; is_indexed is added to the existing field schema object.

Code review

  • Pull request has a descriptive title and context useful to a reviewer. Screenshots or screencasts are attached as
    necessary
  • reviewers assigned
  • Pull request linked to task tracker where applicable

Creates or drops a plain single-column index on the field's column. The state
is read back from the database (new `schema.is_indexed` on the column info of
every dialect), so it is exposed in the fields API, GraphQL and schema
snapshots. Primary key and unique columns are skipped, snapshots without the
property do not produce a diff.

Refs LaWebcapsule#402

Signed-off-by: Kyrylo Kuzmytskyi <slykirill@gmail.com>
slyk added a commit to slyk/d9 that referenced this pull request Oct 15, 2026
Signed-off-by: Kyrylo Kuzmytskyi <slykirill@gmail.com>
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.

1 participant