Skip to content

Commit c7f1e7c

Browse files
authored
fix(bb-distributed-data): reject index key sort order (ASC/DESC) for DSQL (#148)
1 parent 288762d commit c7f1e7c

6 files changed

Lines changed: 62 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@aws-blocks/bb-distributed-data": patch
3+
---
4+
5+
Reject index key sort direction (`ASC`/`DESC`) in `CREATE INDEX` at dev time. DSQL does not allow a sort direction on index keys ("specifying sort order not supported for index keys"), but the PGlite-based local mock previously accepted it, so the error only surfaced on deploy. Migration and mock validation now fail locally instead. (`NULLS FIRST/LAST` is supported by DSQL and is not rejected.)

packages/bb-distributed-data/DESIGN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ The core insight: PGlite supports everything DSQL doesn't. Without validation, c
7474
| `CREATE TEMP TABLE` | Temporary tables |
7575
| `SET TRANSACTION ISOLATION LEVEL` | Fixed Repeatable Read |
7676
| `COLLATE` | C collation only |
77+
| `CREATE INDEX ... ASC/DESC` | Sort direction on index keys (NULLS FIRST/LAST is allowed) |
7778

7879
### Transaction Tracking
7980

packages/bb-distributed-data/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ DSQL is a subset of PostgreSQL. The local mock enforces these restrictions so co
108108
| LISTEN / NOTIFY | AppSync Events, EventBridge, or polling |
109109
| Extensions | Not available |
110110
| ADD COLUMN with DEFAULT | Add column without default, handle nulls in app |
111+
| Index key sort direction (`ASC`/`DESC`) | Omit it; enforce ordering with `ORDER BY` in queries (`NULLS FIRST/LAST` is supported) |
111112

112113
### Transaction Constraints
113114

packages/bb-distributed-data/src/e2e-mock.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ describe('DsqlMockEngine — CREATE INDEX ASYNC parity', () => {
207207
);
208208
});
209209

210+
it('rejects CREATE INDEX ASYNC with a DESC sort order on a key', async () => {
211+
await assert.rejects(
212+
() => engine.withDdl(() => db.execute(sql`CREATE INDEX ASYNC idx_users_email_desc ON users (email DESC)`)),
213+
/sort order/i,
214+
);
215+
});
216+
210217
it('does not strip ASYNC outside of CREATE INDEX (column named "async")', async () => {
211218
await engine.withDdl(() => db.execute(sql`CREATE TABLE jobs (id TEXT PRIMARY KEY, async BOOLEAN)`));
212219
await db.execute(sql`INSERT INTO jobs (id, async) VALUES (${'j1'}, ${true})`);

packages/bb-distributed-data/src/validation.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,44 @@ describe('validateStatement — unsupported features after bind parameters', ()
500500
assert.doesNotThrow(() => validateStatement("INSERT INTO audit (id, action) VALUES ($1, 'TRUNCATE')"));
501501
});
502502
});
503+
504+
describe('validateStatement — index key sort order', () => {
505+
it('rejects DESC on an index key', () => {
506+
assert.throws(
507+
() => validateStatement('CREATE INDEX idx ON persons (user_id, last_encounter_at DESC)'),
508+
/sort order/i,
509+
);
510+
});
511+
512+
it('rejects DESC on a CREATE INDEX ASYNC key', () => {
513+
assert.throws(
514+
() => validateStatement('CREATE INDEX ASYNC idx_persons_user_recent ON persons (user_id, last_encounter_at DESC)'),
515+
/sort order/i,
516+
);
517+
});
518+
519+
it('rejects an explicit ASC on an index key', () => {
520+
assert.throws(() => validateStatement('CREATE INDEX idx ON t (col ASC)'), /sort order/i);
521+
});
522+
523+
it('allows NULLS FIRST / NULLS LAST on an index key (supported by DSQL)', () => {
524+
assert.doesNotThrow(() => validateStatement('CREATE INDEX ASYNC idx ON t (col NULLS FIRST)'));
525+
assert.doesNotThrow(() => validateStatement('CREATE INDEX ASYNC idx ON t (col NULLS LAST)'));
526+
});
527+
528+
it('allows a plain CREATE INDEX without sort order', () => {
529+
assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON persons (user_id, last_encounter_at)'));
530+
});
531+
532+
it('allows a partial index whose WHERE mentions a column like "description"', () => {
533+
assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON t (name) WHERE description IS NOT NULL'));
534+
});
535+
536+
it('allows an expression index without sort order', () => {
537+
assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON t ((lower(name)))'));
538+
});
539+
540+
it('does not flag ORDER BY ... DESC in a SELECT (no false positive outside CREATE INDEX)', () => {
541+
assert.doesNotThrow(() => validateStatement('SELECT * FROM persons ORDER BY last_encounter_at DESC'));
542+
});
543+
});

packages/bb-distributed-data/src/validation.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ const RULES: ValidationRule[] = [
8585
{ pattern: /\bCOLLATE\b/i, message: 'DSQL only supports C collation.', severity: 'error' },
8686
{ pattern: /(?<!::)\bJSONB\b/i, message: 'DSQL does not support JSONB columns. Use JSON instead (JSONB is available as a query runtime cast via ::jsonb).', severity: 'error' },
8787
{ pattern: /(@>|<@|\?\||\?&)/, message: 'JSONB operators lack GIN index acceleration in DSQL.', severity: 'warn' },
88+
// DSQL rejects a sort direction (ASC/DESC) on index keys — it isn't in the
89+
// CREATE INDEX ASYNC grammar (only `NULLS FIRST|LAST`, which IS supported, is).
90+
// `[^;]*` keeps the match inside a single statement; ASC/DESC cannot otherwise
91+
// appear in a CREATE INDEX. Caveat: stripLiteralsAndComments doesn't strip
92+
// double-quoted identifiers, so a column literally named "desc"/"asc" would
93+
// false-positive — niche, and shared with other single-keyword rules here.
94+
{ pattern: /\bCREATE\s+(?:UNIQUE\s+)?INDEX\b[^;]*\b(?:ASC|DESC)\b/i, message: 'DSQL does not support sort order (ASC/DESC) on index keys. Remove it — ordering is enforced by ORDER BY in queries (NULLS FIRST/LAST is allowed). (DSQL: "specifying sort order not supported for index keys")', severity: 'error' },
8895
];
8996

9097
/** Validate a SQL statement for DSQL compatibility. Throws on unsupported features. */

0 commit comments

Comments
 (0)