Skip to content

Commit dae3940

Browse files
kriszypclaude
andcommitted
fix(downgrade): store is_hash_attribute in __dbis__ for harperdb@4.x compat
harperdb@4.7.x reads is_hash_attribute from the __dbis__ primary-key entry to compute LMDB DBI open flags: DBIDefinition(!is_hash_attribute, is_hash_attribute). harper@5 only stored isPrimaryKey, so harperdb@4 opened hdb_deployment with dup_sort=true/useVersions=false instead of the dup_sort=false/useVersions=true it was created with, causing LMDB to throw MDB_INCOMPATIBLE. This left databases.system.hdb_info unpopulated and checkIfInstallIsSupported fatal. Fix: set is_hash_attribute=true alongside isPrimaryKey in databases.ts so new tables (including those created by the 5.1.0 directive) carry the backward-compat field. The 5.1.0 directive also now patches the __dbis__ entry on existing installs that ran the directive before this fix. Re-enable the downgrade test. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent c4cc54e commit dae3940

3 files changed

Lines changed: 32 additions & 13 deletions

File tree

integrationTests/upgrade/4.x-upgrade.test.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,7 @@ suite(
105105
ok(response.length > 10);
106106
});
107107

108-
test(
109-
'downgrade and start',
110-
{
111-
// The 5.1.0 upgrade directive adds the hdb_deployment table as a named DBI within
112-
// schema/system.mdb (the shared LMDB env used by both harper@5 in legacy mode and
113-
// harperdb@4). harperdb@4's initStores iterates __dbis__ alphabetically and fails
114-
// when it encounters hdb_deployment (which precedes hdb_info alphabetically), so
115-
// databases.system.hdb_info is never populated and checkIfInstallIsSupported throws.
116-
// Downgrade from harper 5.1.x back to harperdb 4.x is therefore not supported after
117-
// an upgrade that runs the 5.1.0 directive.
118-
skip: 'downgrade from harper 5.1.x to harperdb 4.x is not supported after 5.1.0 upgrade directive runs (hdb_deployment DBI incompatible with harperdb@4)',
119-
},
120-
async () => {
108+
test('downgrade and start', async () => {
121109
// can we downgrade?
122110
await killHarper(ctx); // kill 5.x harper
123111
await startHarper(ctx, {

resources/databases.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,7 @@ export function table<TableResourceType>(tableDefinition: TableDefinition): Tabl
970970
primaryKeyAttribute = attributes.find((attribute) => attribute.isPrimaryKey) || {};
971971
primaryKey = primaryKeyAttribute.name;
972972
primaryKeyAttribute.isPrimaryKey = true;
973+
primaryKeyAttribute.is_hash_attribute = true; // backward-compat: harperdb@4.x reads this field to open the DBI with correct flags
973974
primaryKeyAttribute.schemaDefined = schemaDefined;
974975
// can't change compression after the fact (except threshold), so save only when we create the table
975976
primaryKeyAttribute.compression = getDefaultCompression();

upgrade/directives/5-1-0.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const DEPLOYMENT_TABLE = terms.SYSTEM_TABLE_NAMES.DEPLOYMENT_TABLE_NAME;
2626
async function createHdbDeploymentIfMissing() {
2727
if (databases.system?.[DEPLOYMENT_TABLE]) {
2828
hdbLogger.info(`system.${DEPLOYMENT_TABLE} already exists; skipping create.`);
29+
// Still run the patch — installs that created hdb_deployment before the is_hash_attribute
30+
// fix was introduced have an __dbis__ entry without that field. harperdb@4.x uses
31+
// is_hash_attribute to determine LMDB DBI open flags; without it the DBI is opened with
32+
// wrong flags (DUPSORT set) and LMDB throws MDB_INCOMPATIBLE, breaking downgrade.
33+
await patchHdbDeploymentIsHashAttribute();
2934
return;
3035
}
3136

@@ -48,6 +53,31 @@ async function createHdbDeploymentIfMissing() {
4853
await bridge.createTable(DEPLOYMENT_TABLE, createTable);
4954
}
5055

56+
/**
57+
* Patch the hdb_deployment __dbis__ primary-key entry to include is_hash_attribute: true.
58+
*
59+
* harperdb@4.x reads is_hash_attribute from __dbis__ to derive the LMDB DBI open flags
60+
* (DBIDefinition(dup_sort, is_hash_attribute)). If the field is absent it opens the DBI
61+
* with dup_sort=true / useVersions=false — the opposite of how harper@5 created it — which
62+
* causes LMDB to throw MDB_INCOMPATIBLE and prevents harperdb@4 from starting after a
63+
* 5.1.x downgrade.
64+
*
65+
* This is safe to call when the table already exists: it is a no-op if is_hash_attribute is
66+
* already set, and writing it back is idempotent (lmdb upsert).
67+
*/
68+
async function patchHdbDeploymentIsHashAttribute() {
69+
const systemTable = (databases as any).system?.[DEPLOYMENT_TABLE];
70+
if (!systemTable?.dbisDB) return;
71+
72+
const dbiName = `${DEPLOYMENT_TABLE}/`;
73+
const primaryAttr = systemTable.dbisDB.getSync(dbiName);
74+
if (!primaryAttr || primaryAttr.is_hash_attribute) return; // already correct
75+
76+
primaryAttr.is_hash_attribute = true;
77+
await systemTable.dbisDB.put(dbiName, primaryAttr);
78+
hdbLogger.info(`Patched system.${DEPLOYMENT_TABLE} __dbis__ entry with is_hash_attribute=true for harperdb@4.x downgrade compatibility.`);
79+
}
80+
5181
const directive510 = {
5282
version: '5.1.0',
5383
sync_functions: [] as Array<() => unknown>,

0 commit comments

Comments
 (0)