Skip to content

[Candidate Identifier (Rework)] Database Schema and Configuration Migration - #11209

Open
HachemJ wants to merge 23 commits into
aces:mainfrom
HachemJ:ReworkIdentifierStructure
Open

HachemJ wants to merge 23 commits into
aces:mainfrom
HachemJ:ReworkIdentifierStructure

Conversation

@HachemJ

@HachemJ HachemJ commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Brief summary of changes

This PR introduces the database structure for the candidate identifier rework discussed in #9036.

It adds three tables:

  • candidate_identifier_types — stores identifier type configuration.
  • candidate_identifier_types_project_rel — associates identifier types with projects.
  • candidate_identifiers — stores identifier values assigned to candidates.
Design decisions and database schema

Candidate identifiers are split into two parts: the identifier types that define how an identifier behaves, and the identifier values assigned to candidates.

Identifier types

candidate_identifier_types stores the configuration for each identifier type.

Field Description
CandidateIdentifierTypeID Identifier type ID
Name Identifier name, e.g. PSCID or ExternalID
Format Template used to generate or validate the identifier
IsRequired Whether candidates using this identifier type must have at least one value
IsUnique Whether identifier values must be unique across candidates
AutoGenerate Whether LORIS generates the value automatically from the format
AllowMultiple Whether a candidate can have more than one value of this type
permID Optional permission required to view this identifier type

For example, PSCID could be configured as required, unique, automatically generated, and limited to one value per candidate. Other identifier types could be optional, manually entered, or allow multiple values.

Projects and permissions

candidate_identifier_types_project_rel associates identifier types with the projects they apply to. An identifier type can be used by multiple projects.

Visibility uses the existing LORIS permission system rather than introducing a separate Visible setting.

The optional permission is stored directly on the identifier type through permID. An identifier type can have at most one permission, while the same permission can be used by multiple identifier types.

The intended behavior is that users must have the associated permission to view an identifier type. If no permission is configured, anyone with access to the candidate should be able to view it. Permission enforcement will be implemented during application integration.

Candidate identifiers

candidate_identifiers stores the actual identifier values assigned to candidates.

Field Description
CandidateIdentifierID Identifier value ID
CandidateID Candidate the value belongs to
CandidateIdentifierTypeID Identifier type
Value Stored identifier value

CandidateIdentifierID is a separate primary key because a candidate may have multiple values of the same type when AllowMultiple is enabled.

AllowMultiple and IsUnique will be enforced by the application when identifier values are added or updated. Both depend on the configuration of the identifier type, so they cannot be represented by a single constraint that applies to every row.

For example, making (CandidateID, CandidateIdentifierTypeID) unique would enforce AllowMultiple = false, but would also prevent multiple values for types where AllowMultiple is enabled.

The current limitation is that the database does not enforce these rules on its own. Direct inserts into candidate_identifiers could bypass them, so application code needs to check the identifier type configuration.

Deletion behavior

Identifier values are deleted when their candidate is deleted since they belong to that candidate.

I decided not to cascade when an identifier type is deleted. Although its values would no longer make sense without the type, cascading here could make a configuration mistake destructive. Accidentally deleting the PSCID type, for example, could also delete every stored PSCID.

Instead, the foreign key prevents an identifier type from being deleted while identifier values still reference it. Removing a type therefore requires explicitly handling its existing values first.

Project relationships cascade when their related configuration is deleted since those rows only store associations. If a permission is deleted, permID is set to NULL rather than deleting the identifier type.

It also includes a migration script that reads the existing PSCID and ExternalID configuration from config.xml and migrates it into candidate_identifier_types.

Scope

This is the first PR in the candidate identifier rework, covering the database structure and configuration migration.

The application implementation, including identifier generation and validation, is handled in #11212, while the REST API changes are handled in #11253.

Existing identifier values and config.xml remain unchanged.

Identifier format

Identifier formats are now stored as template strings in the Format column, replacing the legacy configuration that defines structure and generation separately.

Templates describe the identifier structure and, when AutoGenerate is enabled, the generation method.

The implementation that interprets these templates is handled in #11212.

Identifier template syntax

Templates combine literal text with placeholders enclosed in curly braces.

For example:

{SITE:ALIAS}-{PROJECT:ALIAS}-{SEQUENCE:4,FORMAT:numeric}

Supported placeholders include:

Placeholder Description
{SITE:ALIAS} Site alias.
{PROJECT:ALIAS} Project alias.
{SEQUENCE:N} Sequentially generated value of length N.
{RANDOM:N} Randomly generated value of length N.
{NUMERIC:N} Validates exactly N digits.
{ALPHA:N} Validates exactly N letters.
{ALPHANUMERIC:N} Validates exactly N alphanumeric characters.

SEQUENCE and RANDOM support the following options:

  • FORMAT — numeric, alpha, or alphanumeric (defaults to numeric).
  • PADDING — character used for left-padding (defaults to 0 or A, depending on the alphabet).
  • MIN — minimum generator value.
  • MAX — maximum generator value.

A generated identifier uses one SEQUENCE or RANDOM placeholder, optionally preceded by literal text, site aliases, or project aliases.

When AutoGenerate is disabled, identifiers are entered manually and validated against the template. The NUMERIC, ALPHA, and ALPHANUMERIC placeholders are validation-only.

Testing instructions (if applicable)

  1. Apply the SQL patch and verify that the three new tables are created.

  2. Run the migration script:

    php tools/single_use/migrate_candidate_identifiers.php
  3. Check that PSCID and ExternalID are added to candidate_identifier_types with the correct formats and settings.

  4. Verify that config.xml and the existing PSCID and ExternalID values in the candidate table remain unchanged.

@github-actions github-actions Bot added the Language: PHP PR or issue that update PHP code label Sep 17, 2026
@HachemJ HachemJ added the State: Blocking PR should be prioritized because it is blocking the progress of another task label Oct 7, 2026
@github-actions github-actions Bot added the Language: SQL PR or issue that update SQL code label Oct 12, 2026
@HachemJ HachemJ changed the title [libraries] Refactor identifier configuration to use template strings Add candidate identifier tables and configuration migration Oct 13, 2026
HachemJ added a commit to HachemJ/Loris that referenced this pull request Oct 13, 2026
@HachemJ HachemJ changed the title Add candidate identifier tables and configuration migration [Candidate Identifier rework] Database Schema and Configuration Migration Oct 14, 2026
@HachemJ HachemJ changed the title [Candidate Identifier rework] Database Schema and Configuration Migration [Candidate Identifier Rework] Database Schema and Configuration Migration Oct 16, 2026
@HachemJ HachemJ changed the title [Candidate Identifier Rework] Database Schema and Configuration Migration [Candidate Identifier (Rework)] Database Schema and Configuration Migration Oct 16, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Language: PHP PR or issue that update PHP code Language: SQL PR or issue that update SQL code State: Blocking PR should be prioritized because it is blocking the progress of another task

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants