@aws-blocks/bb-distributed-data 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md CHANGED
@@ -74,6 +74,7 @@ The core insight: PGlite supports everything DSQL doesn't. Without validation, c
74
74
  | `CREATE TEMP TABLE` | Temporary tables |
75
75
  | `SET TRANSACTION ISOLATION LEVEL` | Fixed Repeatable Read |
76
76
  | `COLLATE` | C collation only |
77
+ | `CREATE INDEX ... ASC/DESC` | Sort direction on index keys (NULLS FIRST/LAST is allowed) |
77
78
 
78
79
  ### Transaction Tracking
79
80
 
package/README.md CHANGED
@@ -108,6 +108,7 @@ DSQL is a subset of PostgreSQL. The local mock enforces these restrictions so co
108
108
  | LISTEN / NOTIFY | AppSync Events, EventBridge, or polling |
109
109
  | Extensions | Not available |
110
110
  | 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) |
111
112
 
112
113
  ### Transaction Constraints
113
114
 
@@ -154,6 +154,9 @@ describe('DsqlMockEngine — CREATE INDEX ASYNC parity', () => {
154
154
  it('still supports a plain CREATE INDEX (no ASYNC)', async () => {
155
155
  await assert.doesNotReject(() => engine.withDdl(() => db.execute(sql `CREATE INDEX idx_users_plain ON users(id)`)));
156
156
  });
157
+ it('rejects CREATE INDEX ASYNC with a DESC sort order on a key', async () => {
158
+ await assert.rejects(() => engine.withDdl(() => db.execute(sql `CREATE INDEX ASYNC idx_users_email_desc ON users (email DESC)`)), /sort order/i);
159
+ });
157
160
  it('does not strip ASYNC outside of CREATE INDEX (column named "async")', async () => {
158
161
  await engine.withDdl(() => db.execute(sql `CREATE TABLE jobs (id TEXT PRIMARY KEY, async BOOLEAN)`));
159
162
  await db.execute(sql `INSERT INTO jobs (id, async) VALUES (${'j1'}, ${true})`);
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAuB,MAAM,yBAAyB,CAAC;AAU/E,mEAAmE;AACnE,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA6C5D;AAsBD,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAenD;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAKtE;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAK;IAErB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAalC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAUnC,KAAK,IAAI,IAAI;CACd;AAID,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAiB3E;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAuB,MAAM,yBAAyB,CAAC;AAU/E,mEAAmE;AACnE,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA6C5D;AA6BD,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAenD;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAKtE;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAK;IAErB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAalC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAUnC,KAAK,IAAI,IAAI;CACd;AAID,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAiB3E;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -80,6 +80,13 @@ const RULES = [
80
80
  { pattern: /\bCOLLATE\b/i, message: 'DSQL only supports C collation.', severity: 'error' },
81
81
  { 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' },
82
82
  { pattern: /(@>|<@|\?\||\?&)/, message: 'JSONB operators lack GIN index acceleration in DSQL.', severity: 'warn' },
83
+ // DSQL rejects a sort direction (ASC/DESC) on index keys — it isn't in the
84
+ // CREATE INDEX ASYNC grammar (only `NULLS FIRST|LAST`, which IS supported, is).
85
+ // `[^;]*` keeps the match inside a single statement; ASC/DESC cannot otherwise
86
+ // appear in a CREATE INDEX. Caveat: stripLiteralsAndComments doesn't strip
87
+ // double-quoted identifiers, so a column literally named "desc"/"asc" would
88
+ // false-positive — niche, and shared with other single-keyword rules here.
89
+ { 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' },
83
90
  ];
84
91
  /** Validate a SQL statement for DSQL compatibility. Throws on unsupported features. */
85
92
  export function validateStatement(sql) {
@@ -352,3 +352,30 @@ describe('validateStatement — unsupported features after bind parameters', ()
352
352
  assert.doesNotThrow(() => validateStatement("INSERT INTO audit (id, action) VALUES ($1, 'TRUNCATE')"));
353
353
  });
354
354
  });
355
+ describe('validateStatement — index key sort order', () => {
356
+ it('rejects DESC on an index key', () => {
357
+ assert.throws(() => validateStatement('CREATE INDEX idx ON persons (user_id, last_encounter_at DESC)'), /sort order/i);
358
+ });
359
+ it('rejects DESC on a CREATE INDEX ASYNC key', () => {
360
+ assert.throws(() => validateStatement('CREATE INDEX ASYNC idx_persons_user_recent ON persons (user_id, last_encounter_at DESC)'), /sort order/i);
361
+ });
362
+ it('rejects an explicit ASC on an index key', () => {
363
+ assert.throws(() => validateStatement('CREATE INDEX idx ON t (col ASC)'), /sort order/i);
364
+ });
365
+ it('allows NULLS FIRST / NULLS LAST on an index key (supported by DSQL)', () => {
366
+ assert.doesNotThrow(() => validateStatement('CREATE INDEX ASYNC idx ON t (col NULLS FIRST)'));
367
+ assert.doesNotThrow(() => validateStatement('CREATE INDEX ASYNC idx ON t (col NULLS LAST)'));
368
+ });
369
+ it('allows a plain CREATE INDEX without sort order', () => {
370
+ assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON persons (user_id, last_encounter_at)'));
371
+ });
372
+ it('allows a partial index whose WHERE mentions a column like "description"', () => {
373
+ assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON t (name) WHERE description IS NOT NULL'));
374
+ });
375
+ it('allows an expression index without sort order', () => {
376
+ assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON t ((lower(name)))'));
377
+ });
378
+ it('does not flag ORDER BY ... DESC in a SELECT (no false positive outside CREATE INDEX)', () => {
379
+ assert.doesNotThrow(() => validateStatement('SELECT * FROM persons ORDER BY last_encounter_at DESC'));
380
+ });
381
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-blocks/bb-distributed-data",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "author": "Amazon Web Services",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",