@aws-blocks/bb-distributed-data 0.1.2 → 0.1.5
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 +2 -0
- package/README.md +2 -0
- package/dist/e2e-mock.test.js +7 -0
- package/dist/index.aws.d.ts.map +1 -1
- package/dist/index.aws.js +2 -1
- package/dist/index.cdk.d.ts +7 -0
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +10 -1
- package/dist/index.mock.d.ts.map +1 -1
- package/dist/index.mock.js +2 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +56 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +8 -0
- package/dist/validation.test.js +53 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/package.json +14 -4
package/DESIGN.md
CHANGED
|
@@ -74,6 +74,8 @@ 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) |
|
|
78
|
+
| `ALTER TABLE ... DROP [COLUMN]` | Not in DSQL's supported ALTER TABLE subset (`DROP CONSTRAINT` and `ALTER COLUMN ... DROP DEFAULT/NOT NULL/EXPRESSION/IDENTITY` are supported) |
|
|
77
79
|
|
|
78
80
|
### Transaction Tracking
|
|
79
81
|
|
package/README.md
CHANGED
|
@@ -108,6 +108,8 @@ 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) |
|
|
112
|
+
| `ALTER TABLE DROP [COLUMN]` | Leave the column in place and stop referencing it; or rebuild the table (create new → `INSERT INTO ... SELECT` → `DROP` → `RENAME TO`, one migration file per step) |
|
|
111
113
|
|
|
112
114
|
### Transaction Constraints
|
|
113
115
|
|
package/dist/e2e-mock.test.js
CHANGED
|
@@ -154,6 +154,13 @@ 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
|
+
});
|
|
160
|
+
it('rejects ALTER TABLE DROP COLUMN', async () => {
|
|
161
|
+
await engine.withDdl(() => db.execute(sql `CREATE TABLE legacy (id TEXT PRIMARY KEY, obsolete TEXT)`));
|
|
162
|
+
await assert.rejects(() => engine.withDdl(() => db.execute(sql `ALTER TABLE legacy DROP COLUMN obsolete`)), /DROP COLUMN/i);
|
|
163
|
+
});
|
|
157
164
|
it('does not strip ASYNC outside of CREATE INDEX (column named "async")', async () => {
|
|
158
165
|
await engine.withDdl(() => db.execute(sql `CREATE TABLE jobs (id TEXT PRIMARY KEY, async BOOLEAN)`));
|
|
159
166
|
await db.execute(sql `INSERT INTO jobs (id, async) VALUES (${'j1'}, ${true})`);
|
package/dist/index.aws.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.aws.d.ts","sourceRoot":"","sources":["../src/index.aws.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.aws.d.ts","sourceRoot":"","sources":["../src/index.aws.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,OAAO,CAAC,KAAK,CAA6B;IAE1C,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,0BAA0B;IAQjF,OAAO,KAAK,IAAI,GAiBf;IAED,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvD;;;;;OAKG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAInG,gBAAgB;IAChB,SAAS;CACV;AAED,OAAO,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.aws.js
CHANGED
|
@@ -11,12 +11,13 @@ import { DsqlEngine } from './engines/dsql-engine.js';
|
|
|
11
11
|
import { transactionWithRetry } from './transaction.js';
|
|
12
12
|
import { ENV_SANITIZE, sanitizeDbRoleName } from './constants.js';
|
|
13
13
|
import { Logger } from '@aws-blocks/bb-logger';
|
|
14
|
+
import { BB_NAME, BB_VERSION } from './version.js';
|
|
14
15
|
export class DistributedDatabase extends Scope {
|
|
15
16
|
_base = null;
|
|
16
17
|
/** @internal Logger for internal operations. Defaults to error-level when not provided. */
|
|
17
18
|
log;
|
|
18
19
|
constructor(scope, id, _options) {
|
|
19
|
-
super(id, { parent: scope });
|
|
20
|
+
super(id, { parent: scope, bbName: BB_NAME, bbVersion: BB_VERSION });
|
|
20
21
|
this.log = _options?.logger ?? new Logger(this, 'logger', { level: 'error' });
|
|
21
22
|
const envName = this.fullId.replace(ENV_SANITIZE, '_');
|
|
22
23
|
const clusterEndpoint = process.env[`BLOCKS_${envName}_ENDPOINT`] ?? '';
|
package/dist/index.cdk.d.ts
CHANGED
|
@@ -8,6 +8,13 @@ import type { ScopeParent } from '@aws-blocks/core';
|
|
|
8
8
|
import type { DistributedDatabaseOptions } from './types.js';
|
|
9
9
|
export declare class DistributedDatabase extends Scope {
|
|
10
10
|
constructor(scope: ScopeParent, id: string, options?: DistributedDatabaseOptions);
|
|
11
|
+
/**
|
|
12
|
+
* Runtime-only. This is the CDK (synth) build: it defines infrastructure and
|
|
13
|
+
* has no engine — queries run in the app Lambda against the deployed cluster.
|
|
14
|
+
* `createKyselyAdapter()` no longer calls this eagerly, so reaching it means a
|
|
15
|
+
* query ran at synth time (e.g. at module scope).
|
|
16
|
+
*/
|
|
17
|
+
getEngine(): never;
|
|
11
18
|
}
|
|
12
19
|
export { sql, createKyselyAdapter } from '@aws-blocks/data-common';
|
|
13
20
|
export type { SqlQuery, Transaction } from '@aws-blocks/data-common';
|
package/dist/index.cdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAoC,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAG7D,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B;IAyFhF;;;;;OAKG;IACH,SAAS,IAAI,KAAK;CAGnB;AAaD,OAAO,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.cdk.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provisions Aurora DSQL cluster via CloudFormation.
|
|
6
6
|
* Optionally runs migrations via a CustomResource Lambda.
|
|
7
7
|
*/
|
|
8
|
-
import { Scope, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
|
|
8
|
+
import { Scope, DEFAULT_NODE_RUNTIME, synthGuard } from '@aws-blocks/core/cdk';
|
|
9
9
|
import * as cdk from 'aws-cdk-lib';
|
|
10
10
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
11
11
|
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
@@ -88,6 +88,15 @@ export class DistributedDatabase extends Scope {
|
|
|
88
88
|
// Ensure migrations run after cluster is created
|
|
89
89
|
migrationCR.node.addDependency(cluster);
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Runtime-only. This is the CDK (synth) build: it defines infrastructure and
|
|
93
|
+
* has no engine — queries run in the app Lambda against the deployed cluster.
|
|
94
|
+
* `createKyselyAdapter()` no longer calls this eagerly, so reaching it means a
|
|
95
|
+
* query ran at synth time (e.g. at module scope).
|
|
96
|
+
*/
|
|
97
|
+
getEngine() {
|
|
98
|
+
return synthGuard('DistributedDatabase', 'getEngine');
|
|
99
|
+
}
|
|
91
100
|
}
|
|
92
101
|
/** Hash all .sql files in a directory to detect changes. */
|
|
93
102
|
function hashMigrationsDir(dir) {
|
package/dist/index.mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,aAAa,CAA8B;IAEnD,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B;YAelE,KAAK;IAEnB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvD;;;;;OAKG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAKnG,yDAAyD;IACzD,gBAAgB,IAAI,IAAI;IAExB,gBAAgB;IAChB,SAAS;CACV;AAED,OAAO,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.mock.js
CHANGED
|
@@ -10,6 +10,7 @@ import { DsqlMockEngine } from './engines/dsql-mock-engine.js';
|
|
|
10
10
|
import { runMigrations, loadMigrationsFromDir } from './migrations.js';
|
|
11
11
|
import { transactionWithRetry } from './transaction.js';
|
|
12
12
|
import { Logger } from '@aws-blocks/bb-logger';
|
|
13
|
+
import { BB_NAME, BB_VERSION } from './version.js';
|
|
13
14
|
export class DistributedDatabase extends Scope {
|
|
14
15
|
base;
|
|
15
16
|
mockEngine;
|
|
@@ -17,7 +18,7 @@ export class DistributedDatabase extends Scope {
|
|
|
17
18
|
/** @internal Logger for internal operations. Defaults to error-level when not provided. */
|
|
18
19
|
log;
|
|
19
20
|
constructor(scope, id, options) {
|
|
20
|
-
super(id, { parent: scope });
|
|
21
|
+
super(id, { parent: scope, bbName: BB_NAME, bbVersion: BB_VERSION });
|
|
21
22
|
this.log = options?.logger ?? new Logger(this, 'logger', { level: 'error' });
|
|
22
23
|
this.mockEngine = new DsqlMockEngine(`.bb-data/${this.fullId}`);
|
|
23
24
|
this.base = new DatabaseBase(this.mockEngine);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Telemetry-registration tests for DistributedDatabase.
|
|
5
|
+
*
|
|
6
|
+
* `Scope.getRegisteredBlocks()` only names a block whose `bbName` is in
|
|
7
|
+
* OFFICIAL_BB_NAMES, and that set is generated from the umbrella's
|
|
8
|
+
* `aws-blocks.vendorize` map. These tests pin the three coupled artifacts to
|
|
9
|
+
* each other: the block's generated BB_NAME, its vendorize entry, and the
|
|
10
|
+
* generated name set. A block that omits `bbMeta` still constructs fine and
|
|
11
|
+
* every other test still passes, so that gap is only visible here.
|
|
12
|
+
*
|
|
13
|
+
* The package ships a distinct mock implementation (the default entry does not
|
|
14
|
+
* re-export the AWS class), so both `index.aws.ts` and `index.mock.ts` carry
|
|
15
|
+
* the `super()` change. Registration happens in `super()`, identical for both;
|
|
16
|
+
* these tests exercise the AWS class because its constructor is side-effect
|
|
17
|
+
* free (the mock eagerly spins up PGlite on disk), keeping the suite fast and
|
|
18
|
+
* deterministic.
|
|
19
|
+
*/
|
|
20
|
+
import { test, describe, beforeEach } from 'node:test';
|
|
21
|
+
import assert from 'node:assert';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { dirname, join } from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { Scope } from '@aws-blocks/core';
|
|
26
|
+
import { DistributedDatabase } from './index.aws.js';
|
|
27
|
+
import { BB_NAME, BB_VERSION } from './version.js';
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
let counter = 0;
|
|
30
|
+
function makeDb() {
|
|
31
|
+
const scope = new Scope(`distdata-telemetry-${++counter}`);
|
|
32
|
+
return new DistributedDatabase(scope, 'db');
|
|
33
|
+
}
|
|
34
|
+
describe('DistributedDatabase telemetry registration', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
Scope._resetRegistry();
|
|
37
|
+
});
|
|
38
|
+
test('BB_NAME is the name the vendorize map and OFFICIAL_BB_NAMES carry', () => {
|
|
39
|
+
assert.strictEqual(BB_NAME, 'DistributedDatabase');
|
|
40
|
+
});
|
|
41
|
+
test('BB_VERSION tracks the package version', () => {
|
|
42
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
43
|
+
assert.strictEqual(BB_VERSION, pkg.version);
|
|
44
|
+
});
|
|
45
|
+
test('an instance carries bbName and bbVersion', () => {
|
|
46
|
+
const db = makeDb();
|
|
47
|
+
assert.strictEqual(db.bbName, BB_NAME);
|
|
48
|
+
assert.strictEqual(db.bbVersion, BB_VERSION);
|
|
49
|
+
});
|
|
50
|
+
test('registers as an official block, so telemetry is allowed to name it', () => {
|
|
51
|
+
makeDb();
|
|
52
|
+
const { blocks, customBlocksCount } = Scope.getRegisteredBlocks();
|
|
53
|
+
assert.deepStrictEqual(blocks.filter(b => b.name === BB_NAME), [{ name: BB_NAME, version: BB_VERSION }]);
|
|
54
|
+
assert.strictEqual(customBlocksCount, 0, 'must not be filtered out as an unnamed custom block');
|
|
55
|
+
});
|
|
56
|
+
});
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AA8BD,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"}
|
package/dist/validation.js
CHANGED
|
@@ -73,6 +73,7 @@ const RULES = [
|
|
|
73
73
|
{ pattern: /\b(LISTEN|NOTIFY)\b/i, message: 'DSQL does not support LISTEN/NOTIFY.', severity: 'error' },
|
|
74
74
|
{ pattern: /\bCREATE\s+EXTENSION\b/i, message: 'DSQL does not support extensions.', severity: 'error' },
|
|
75
75
|
{ pattern: /\bALTER\s+TABLE\b[\s\S]*\bADD\s+COLUMN\b[\s\S]*\bDEFAULT\b/i, message: 'DSQL does not support ADD COLUMN with DEFAULT.', severity: 'error' },
|
|
76
|
+
{ pattern: /\bALTER\s+TABLE\b[^;]*\bDROP\s+(?:COLUMN\b|IF\s+EXISTS\b|(?!(?:DEFAULT|NOT|EXPRESSION|IDENTITY|CONSTRAINT)\b)[\w"])/i, message: 'DSQL does not support ALTER TABLE DROP COLUMN. Leave the column in place and stop referencing it, or rebuild the table (create a new table → INSERT INTO ... SELECT → DROP → RENAME TO). (DSQL: "unsupported ALTER TABLE DROP COLUMN statement")', severity: 'error' },
|
|
76
77
|
{ pattern: /\bALTER\s+DEFAULT\s+PRIVILEGES\b/i, message: 'DSQL does not support ALTER DEFAULT PRIVILEGES.', severity: 'error' },
|
|
77
78
|
{ pattern: /\b(CREATE\s+POLICY|ENABLE\s+ROW\s+LEVEL\s+SECURITY)\b/i, message: 'DSQL does not support Row Level Security.', severity: 'error' },
|
|
78
79
|
{ pattern: /\bCREATE\s+(TEMP|TEMPORARY)\s+TABLE\b/i, message: 'DSQL does not support temporary tables.', severity: 'error' },
|
|
@@ -80,6 +81,13 @@ const RULES = [
|
|
|
80
81
|
{ pattern: /\bCOLLATE\b/i, message: 'DSQL only supports C collation.', severity: 'error' },
|
|
81
82
|
{ 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
83
|
{ pattern: /(@>|<@|\?\||\?&)/, message: 'JSONB operators lack GIN index acceleration in DSQL.', severity: 'warn' },
|
|
84
|
+
// DSQL rejects a sort direction (ASC/DESC) on index keys — it isn't in the
|
|
85
|
+
// CREATE INDEX ASYNC grammar (only `NULLS FIRST|LAST`, which IS supported, is).
|
|
86
|
+
// `[^;]*` keeps the match inside a single statement; ASC/DESC cannot otherwise
|
|
87
|
+
// appear in a CREATE INDEX. Caveat: stripLiteralsAndComments doesn't strip
|
|
88
|
+
// double-quoted identifiers, so a column literally named "desc"/"asc" would
|
|
89
|
+
// false-positive — niche, and shared with other single-keyword rules here.
|
|
90
|
+
{ 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
91
|
];
|
|
84
92
|
/** Validate a SQL statement for DSQL compatibility. Throws on unsupported features. */
|
|
85
93
|
export function validateStatement(sql) {
|
package/dist/validation.test.js
CHANGED
|
@@ -45,12 +45,38 @@ describe('validateStatement', () => {
|
|
|
45
45
|
['ISOLATION LEVEL', 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'],
|
|
46
46
|
// DSQL only supports C collation — locale-aware sorting is not available
|
|
47
47
|
['COLLATE', 'SELECT * FROM t ORDER BY name COLLATE "en_US"'],
|
|
48
|
+
// DROP COLUMN is not in DSQL's supported ALTER TABLE subset — rebuild the table instead
|
|
49
|
+
['DROP COLUMN', 'ALTER TABLE t DROP COLUMN x'],
|
|
50
|
+
// Postgres allows omitting the COLUMN keyword — same DROP COLUMN action, same rejection
|
|
51
|
+
['DROP COLUMN shorthand', 'ALTER TABLE t DROP x'],
|
|
52
|
+
['DROP COLUMN IF EXISTS shorthand', 'ALTER TABLE t DROP IF EXISTS x'],
|
|
53
|
+
['DROP COLUMN quoted shorthand', 'ALTER TABLE t DROP "identity"'],
|
|
48
54
|
];
|
|
49
55
|
for (const [label, sql] of rejects) {
|
|
50
56
|
it(`rejects ${label}`, () => {
|
|
51
57
|
assert.throws(() => validateStatement(sql), { name: 'DsqlValidationError' });
|
|
52
58
|
});
|
|
53
59
|
}
|
|
60
|
+
it('allows supported ALTER TABLE forms that contain DROP', () => {
|
|
61
|
+
// Per the DSQL ALTER TABLE grammar, the ALTER COLUMN ... DROP actions and
|
|
62
|
+
// DROP CONSTRAINT are supported — must not be confused with the
|
|
63
|
+
// unsupported DROP [COLUMN].
|
|
64
|
+
// https://docs.aws.amazon.com/aurora-dsql/latest/userguide/alter-table-syntax-support.html
|
|
65
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t ALTER COLUMN c DROP IDENTITY'));
|
|
66
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t ALTER COLUMN c DROP DEFAULT'));
|
|
67
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t ALTER COLUMN c DROP NOT NULL'));
|
|
68
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t ALTER COLUMN c DROP EXPRESSION'));
|
|
69
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t DROP CONSTRAINT c'));
|
|
70
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t DROP CONSTRAINT IF EXISTS c CASCADE'));
|
|
71
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t RENAME TO t2'));
|
|
72
|
+
assert.doesNotThrow(() => validateStatement('DROP TABLE t'));
|
|
73
|
+
});
|
|
74
|
+
it('allows a supported ALTER TABLE followed by an unrelated statement in one batch', () => {
|
|
75
|
+
// The DROP COLUMN rule must not match across a statement boundary: the
|
|
76
|
+
// ALTER TABLE here is a supported DROP DEFAULT, and the DROP TABLE that
|
|
77
|
+
// follows the semicolon belongs to a different statement.
|
|
78
|
+
assert.doesNotThrow(() => validateStatement('ALTER TABLE t ALTER COLUMN c DROP DEFAULT; DROP TABLE archived'));
|
|
79
|
+
});
|
|
54
80
|
});
|
|
55
81
|
describe('classifyStatement', () => {
|
|
56
82
|
it('classifies DDL/DML/other', () => {
|
|
@@ -352,3 +378,30 @@ describe('validateStatement — unsupported features after bind parameters', ()
|
|
|
352
378
|
assert.doesNotThrow(() => validateStatement("INSERT INTO audit (id, action) VALUES ($1, 'TRUNCATE')"));
|
|
353
379
|
});
|
|
354
380
|
});
|
|
381
|
+
describe('validateStatement — index key sort order', () => {
|
|
382
|
+
it('rejects DESC on an index key', () => {
|
|
383
|
+
assert.throws(() => validateStatement('CREATE INDEX idx ON persons (user_id, last_encounter_at DESC)'), /sort order/i);
|
|
384
|
+
});
|
|
385
|
+
it('rejects DESC on a CREATE INDEX ASYNC key', () => {
|
|
386
|
+
assert.throws(() => validateStatement('CREATE INDEX ASYNC idx_persons_user_recent ON persons (user_id, last_encounter_at DESC)'), /sort order/i);
|
|
387
|
+
});
|
|
388
|
+
it('rejects an explicit ASC on an index key', () => {
|
|
389
|
+
assert.throws(() => validateStatement('CREATE INDEX idx ON t (col ASC)'), /sort order/i);
|
|
390
|
+
});
|
|
391
|
+
it('allows NULLS FIRST / NULLS LAST on an index key (supported by DSQL)', () => {
|
|
392
|
+
assert.doesNotThrow(() => validateStatement('CREATE INDEX ASYNC idx ON t (col NULLS FIRST)'));
|
|
393
|
+
assert.doesNotThrow(() => validateStatement('CREATE INDEX ASYNC idx ON t (col NULLS LAST)'));
|
|
394
|
+
});
|
|
395
|
+
it('allows a plain CREATE INDEX without sort order', () => {
|
|
396
|
+
assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON persons (user_id, last_encounter_at)'));
|
|
397
|
+
});
|
|
398
|
+
it('allows a partial index whose WHERE mentions a column like "description"', () => {
|
|
399
|
+
assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON t (name) WHERE description IS NOT NULL'));
|
|
400
|
+
});
|
|
401
|
+
it('allows an expression index without sort order', () => {
|
|
402
|
+
assert.doesNotThrow(() => validateStatement('CREATE INDEX idx ON t ((lower(name)))'));
|
|
403
|
+
});
|
|
404
|
+
it('does not flag ORDER BY ... DESC in a SELECT (no false positive outside CREATE INDEX)', () => {
|
|
405
|
+
assert.doesNotThrow(() => validateStatement('SELECT * FROM persons ORDER BY last_encounter_at DESC'));
|
|
406
|
+
});
|
|
407
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,wBAAwB,CAAC;AAC7C,eAAO,MAAM,UAAU,UAAU,CAAC"}
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-blocks/bb-distributed-data",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/aws-devtools-labs/aws-blocks.git",
|
|
7
|
+
"directory": "packages/bb-distributed-data"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/aws-devtools-labs/aws-blocks/tree/main/packages/bb-distributed-data#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/aws-devtools-labs/aws-blocks/issues"
|
|
12
|
+
},
|
|
4
13
|
"author": "Amazon Web Services",
|
|
5
14
|
"license": "Apache-2.0",
|
|
6
15
|
"type": "module",
|
|
@@ -23,13 +32,14 @@
|
|
|
23
32
|
}
|
|
24
33
|
},
|
|
25
34
|
"scripts": {
|
|
35
|
+
"prebuild": "node ../../scripts/generate-version.mjs DistributedDatabase",
|
|
26
36
|
"build": "tsc --build",
|
|
27
37
|
"test": "node --test 'dist/**/*.test.js'"
|
|
28
38
|
},
|
|
29
39
|
"dependencies": {
|
|
30
|
-
"@aws-blocks/core": "^0.1.
|
|
31
|
-
"@aws-blocks/data-common": "^0.1.
|
|
32
|
-
"@aws-blocks/bb-logger": "^0.1.
|
|
40
|
+
"@aws-blocks/core": "^0.1.17",
|
|
41
|
+
"@aws-blocks/data-common": "^0.1.3",
|
|
42
|
+
"@aws-blocks/bb-logger": "^0.1.3",
|
|
33
43
|
"@aws-sdk/dsql-signer": "^3.700.0",
|
|
34
44
|
"@electric-sql/pglite": "^0.2.0",
|
|
35
45
|
"pg": "^8.13.0"
|