Skip to content

Add rowId to provisioned dataclass table#7439

Open
XingY wants to merge 3 commits intodevelopfrom
fb_sourceRowId
Open

Add rowId to provisioned dataclass table#7439
XingY wants to merge 3 commits intodevelopfrom
fb_sourceRowId

Conversation

@XingY
Copy link
Contributor

@XingY XingY commented Feb 20, 2026

Rationale

In preparation for work to consolidate dataclass update methods (from the current single _update vs DIB based batch update), we need to make some changes to the current dataclass schema.

  1. provisioned currently has lsid, but not rowId. The plan is to support update using rowId as key only. This requires rowId to be available on provisioned table.
  2. there is a classid on provisioned, which provides no value since it's static. This should be removed.

This PR does not drop LSID from provisioned, nor does it deprecate support of update using LSID.

Related Pull Requests

Changes

  • drop classId from provisioned dataclass table since it's not needed
  • add rowId to provisioned dataclass table
  • migration script to handle dataclass domain field, indexes updates
  • switch exp.data to join on rowId instead of lsid, in preparation of dropping provisioned.lsid in the future
  • always reselectId for dataclass during insert/merge. This likely won't have much perf impact since we are already doing it when audit is turned on, which is the default behavior.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prepares the DataClass schema for future consolidation of update methods by adding rowId and removing classId from provisioned DataClass tables. The changes maintain backward compatibility with LSID-based updates while establishing rowId as the primary join key between provisioned tables and exp.data.

Changes:

  • Removed classId column from DataClass provisioned table schema (no longer needed as it's static)
  • Added rowId column to DataClass provisioned tables with FK to exp.Data(RowId)
  • Updated all joins between exp.data and provisioned tables to use rowId instead of lsid
  • Added migration code to upgrade existing provisioned tables
  • Updated tests to verify update operations work with both rowId and lsid keys

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
experiment/src/org/labkey/experiment/api/DataClassDomainKind.java Updated base properties, foreign keys, and indexes for provisioned tables; removed classId, added rowId, kept lsid for compatibility
experiment/src/org/labkey/experiment/ExperimentUpgradeCode.java Added migration logic to drop classId and add rowId to existing provisioned DataClass tables
experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java Changed joins from lsid to rowId; updated deletion logic to use rowId
experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java Updated update operations to use rowId; added rowId to alternative update keys; removed classId filtering logic; simplified configureDataIteratorContext
experiment/src/org/labkey/experiment/ExperimentModule.java Bumped schema version to 26.003
experiment/src/client/test/integration/utils.ts Added helper functions for field name generation compatible with import operations
experiment/src/client/test/integration/DataClassCrud.ispec.ts Added comprehensive test for update operations using different key fields (rowId, lsid, name)
experiment/resources/schemas/dbscripts/postgresql/exp-26.002-26.003.sql PostgreSQL migration script to invoke Java upgrade code
experiment/resources/schemas/dbscripts/sqlserver/exp-26.002-26.003.sql SQL Server migration script to invoke Java upgrade code

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +642 to +650
SQLFragment update = new SQLFragment()
.append("UPDATE expdataclass.").append(tableName).append("\n")
.append("SET rowId = i.rowid\n")
.append("FROM (\n")
.append(" SELECT d.lsid, d.RowId\n")
.append(" FROM exp.data d\n")
.append(" WHERE d.cpasType = ?\n").add(domain.getTypeURI())
.append(") AS i\n")
.append("WHERE i.lsid = ").append(tableName).append(".lsid");
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UPDATE...FROM syntax used here is PostgreSQL-specific and will fail on SQL Server. SQL Server requires the table alias to be used in the UPDATE clause. The UPDATE statement should be database-specific. For SQL Server, use: UPDATE t SET t.rowId = i.rowid FROM expdataclass.tableName t INNER JOIN ..., while PostgreSQL uses: UPDATE expdataclass.tableName SET rowId = i.rowid FROM ... WHERE ....

Suggested change
SQLFragment update = new SQLFragment()
.append("UPDATE expdataclass.").append(tableName).append("\n")
.append("SET rowId = i.rowid\n")
.append("FROM (\n")
.append(" SELECT d.lsid, d.RowId\n")
.append(" FROM exp.data d\n")
.append(" WHERE d.cpasType = ?\n").add(domain.getTypeURI())
.append(") AS i\n")
.append("WHERE i.lsid = ").append(tableName).append(".lsid");
SQLFragment update = new SQLFragment();
if (scope.getSqlDialect().isSqlServer())
{
// SQL Server requires the table alias in the UPDATE clause
update.append("UPDATE t\n")
.append("SET t.rowId = i.RowId\n")
.append("FROM expdataclass.").append(tableName).append(" t\n")
.append("JOIN (\n")
.append(" SELECT d.lsid, d.RowId\n")
.append(" FROM exp.data d\n")
.append(" WHERE d.cpasType = ?\n").add(domain.getTypeURI())
.append(") AS i ON i.lsid = t.lsid");
}
else
{
// PostgreSQL-style UPDATE ... FROM syntax
update.append("UPDATE expdataclass.").append(tableName).append("\n")
.append("SET rowId = i.RowId\n")
.append("FROM (\n")
.append(" SELECT d.lsid, d.RowId\n")
.append(" FROM exp.data d\n")
.append(" WHERE d.cpasType = ?\n").add(domain.getTypeURI())
.append(") AS i\n")
.append("WHERE i.lsid = ").append(tableName).append(".lsid");
}

Copilot uses AI. Check for mistakes.
String tableName = domain.getStorageTableName();
SQLFragment update = new SQLFragment()
.append("UPDATE expdataclass.").append(tableName).append("\n")
.append("SET rowId = i.rowid\n")
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a case inconsistency in the column reference. Line 644 sets rowId = i.rowid (lowercase), but line 646 selects d.RowId (mixed case). This should be consistent. Use d.RowId on line 644 to match the column name used in the SELECT.

Suggested change
.append("SET rowId = i.rowid\n")
.append("SET rowId = i.RowId\n")

Copilot uses AI. Check for mistakes.
Comment on lines +632 to +638
String fkName = "FK_" + domain.getStorageTableName() + "_rowId";
executor.execute(new SQLFragment("ALTER TABLE expdataclass.").append(domain.getStorageTableName())
.append(" ADD CONSTRAINT ").append(fkName)
.append(" FOREIGN KEY (rowId) REFERENCES exp.Data(RowId)"));
LOG.info("DataClass '{}' ({}) added FK constraint on 'rowId'", dc.getName(), dc.getRowId());
}

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FK constraint name uses the table name without any length limitation. If the domain's storage table name is very long, this could exceed database identifier length limits (typically 63 characters for PostgreSQL, 128 for SQL Server). Consider truncating or hashing the table name when constructing the FK name.

Suggested change
String fkName = "FK_" + domain.getStorageTableName() + "_rowId";
executor.execute(new SQLFragment("ALTER TABLE expdataclass.").append(domain.getStorageTableName())
.append(" ADD CONSTRAINT ").append(fkName)
.append(" FOREIGN KEY (rowId) REFERENCES exp.Data(RowId)"));
LOG.info("DataClass '{}' ({}) added FK constraint on 'rowId'", dc.getName(), dc.getRowId());
}
String fkName = createRowIdFkName(domain.getStorageTableName());
executor.execute(new SQLFragment("ALTER TABLE expdataclass.").append(domain.getStorageTableName())
.append(" ADD CONSTRAINT ").append(fkName)
.append(" FOREIGN KEY (rowId) REFERENCES exp.Data(RowId)"));
LOG.info("DataClass '{}' ({}) added FK constraint on 'rowId'", dc.getName(), dc.getRowId());
}
private static final int MAX_FK_NAME_LENGTH = 63;
/**
* Create a foreign key name for the rowId column that respects common database identifier length limits.
* The pattern is "FK_" + tableName + "_rowId", possibly truncated with a hash if necessary.
*/
private static String createRowIdFkName(String tableName)
{
String suffix = "_rowId";
String prefix = "FK_" + tableName;
String fullName = prefix + suffix;
if (fullName.length() <= MAX_FK_NAME_LENGTH)
{
return fullName;
}
// Use a short hex hash of the table name to help preserve uniqueness when truncating
String hash = Integer.toHexString(tableName.hashCode());
if (hash.length() > 8)
{
hash = hash.substring(0, 8);
}
// Leave room for "_" + hash + suffix
int availableForPrefix = MAX_FK_NAME_LENGTH - suffix.length() - 1 - hash.length();
if (availableForPrefix < 0)
{
availableForPrefix = 0;
}
String truncatedPrefix = prefix.substring(0, Math.min(prefix.length(), availableForPrefix));
return truncatedPrefix + "_" + hash + suffix;
}

Copilot uses AI. Check for mistakes.
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

Comments