@@ -40,21 +40,36 @@ import { describe, it, expect, afterEach } from 'vitest';
4040import { SqlDriver } from '../src/index.js' ;
4141import { isHashShadowColumn } from './schema-drift.js' ;
4242import { MYSQL_CELL , declareDialectCell } from './live-dialect-matrix.testkit.js' ;
43+ import { asPre18TenantColumn } from './pre-18-tenant-column.testkit.js' ;
4344
4445/**
4546 * An object with a tenant column and one long text field carrying an
46- * ORG-SCOPED unique. `maxLength: 1024` exceeds the 768-char keyable ceiling, so
47- * MySQL refuses the direct (functional-key-part) index and the sync takes the
48- * shadow route — the same route the live members
47+ * ORG-SCOPED unique in its NULL-safe (18.0) form. `maxLength: 1024` exceeds the
48+ * 768-char keyable ceiling, so MySQL refuses the direct (functional-key-part)
49+ * index and the sync takes the shadow route — the same route the live members
4950 * (`sys_notification_preference` / `sys_notification_subscription`) take.
51+ *
52+ * ADR-0131 D1: a `unique: 'organization'` declaration now keys the PLAIN
53+ * tenant column, so the NULL-safe key part this file pins reaches the shadow
54+ * only from an input that already carries `nullSafeColumns` (honoured
55+ * verbatim) — the shape a database built before 18.x holds. Every test that
56+ * writes NULL-organization rows also returns the table to that store's
57+ * nullable tenant column (`asPre18TenantColumn`).
5058 */
5159const orgUniqueOn = ( name : string ) => ( {
5260 name,
5361 fields : {
5462 organization_id : { type : 'string' } ,
5563 v : { type : 'text' , maxLength : 1024 } ,
5664 } ,
57- indexes : [ { fields : [ 'v' ] , unique : 'organization' as const , name : `uniq_${ name } _org_v` } ] ,
65+ indexes : [
66+ {
67+ fields : [ 'organization_id' , 'v' ] ,
68+ unique : true as const ,
69+ nullSafeColumns : [ 'organization_id' ] ,
70+ name : `uniq_${ name } _org_v` ,
71+ } ,
72+ ] ,
5873} ) ;
5974
6075declareDialectCell ( MYSQL_CELL , 'hash-shadow NULL-safe key (#12998)' , ( cell ) => {
@@ -100,6 +115,7 @@ declareDialectCell(MYSQL_CELL, 'hash-shadow NULL-safe key (#12998)', (cell) => {
100115 it ( 'collides two NULL-organization rows under an org-scoped shadow unique' , async ( ) => {
101116 driver = new SqlDriver ( cell . config ( ) ) ;
102117 await driver . initObjects ( [ orgUniqueOn ( 'os12998_org' ) ] ) ;
118+ await asPre18TenantColumn ( driver , 'os12998_org' ) ;
103119
104120 const { cols, idx } = await catalog ( 'os12998_org' ) ;
105121 const shadow = cols . find ( ( c : any ) => isHashShadowColumn ( c . COLUMN_NAME ) ) ;
@@ -184,6 +200,7 @@ declareDialectCell(MYSQL_CELL, 'hash-shadow NULL-safe key (#12998)', (cell) => {
184200 const bare = orgUniqueOn ( 'os12998_dirty' ) ;
185201 const withoutIndex = { ...bare , indexes : [ ] } ;
186202 await driver . initObjects ( [ withoutIndex ] ) ;
203+ await asPre18TenantColumn ( driver , 'os12998_dirty' ) ;
187204 const knex = ( driver as any ) . knex ;
188205 const V = 'd' . repeat ( 900 ) ;
189206 await knex ( 'os12998_dirty' ) . insert ( [
@@ -220,6 +237,7 @@ declareDialectCell(MYSQL_CELL, 'hash-shadow NULL-safe key (#12998)', (cell) => {
220237 it ( 'names a NULL-organization duplicate in declared terms, never as a collision' , async ( ) => {
221238 driver = new SqlDriver ( cell . config ( ) ) ;
222239 await driver . initObjects ( [ orgUniqueOn ( 'os12998_msg' ) ] ) ;
240+ await asPre18TenantColumn ( driver , 'os12998_msg' ) ;
223241 const V = 'm' . repeat ( 900 ) ;
224242 await driver . create ( 'os12998_msg' , { v : V } ) ;
225243 const err : unknown = await driver . create ( 'os12998_msg' , { v : V } ) . then (
@@ -266,6 +284,7 @@ declareDialectCell(MYSQL_CELL, 'hash-shadow NULL-safe key (#12998)', (cell) => {
266284 // rejected by tsc for a key the driver reads regardless (the gap commit b72226f48 closed).
267285 const withoutIndex = { ...bare , indexes : [ ] } ;
268286 await driver . initObjects ( [ withoutIndex ] ) ;
287+ await asPre18TenantColumn ( driver , 'os12998_tail' ) ;
269288 const knex = ( driver as any ) . knex ;
270289 // Six DISTINCT values, each doubled under a NULL organization: six
271290 // conflicting groups once the declared key folds NULL into the global
0 commit comments