@onivoro/server-aws-redshift 24.38.2 → 25.0.2
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/README.md
CHANGED
|
@@ -1,233 +1,130 @@
|
|
|
1
1
|
# @onivoro/server-aws-redshift
|
|
2
2
|
|
|
3
|
-
AWS Redshift Data API integration for NestJS applications.
|
|
3
|
+
AWS Redshift Data API integration for NestJS applications, aimed at **Redshift Serverless** workgroups.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @onivoro/server-aws-redshift
|
|
8
|
+
npm install @onivoro/server-aws-redshift @aws-sdk/client-redshift @aws-sdk/client-redshift-data @aws-sdk/client-redshift-serverless
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
This library provides AWS Redshift Data API integration for NestJS applications, offering basic database operations, user management, and schema permissions.
|
|
11
|
+
`@nestjs/common` is also a peer dependency.
|
|
14
12
|
|
|
15
13
|
## Module Setup
|
|
16
14
|
|
|
17
15
|
```typescript
|
|
18
16
|
import { Module } from '@nestjs/common';
|
|
19
|
-
import {
|
|
17
|
+
import { ServerAwsRedshiftDataModule } from '@onivoro/server-aws-redshift';
|
|
20
18
|
|
|
21
19
|
@Module({
|
|
22
20
|
imports: [
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
ServerAwsRedshiftDataModule.configure({
|
|
22
|
+
AWS_REGION: process.env.AWS_REGION!,
|
|
23
|
+
AWS_PROFILE: process.env.AWS_PROFILE, // optional
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
25
26
|
})
|
|
26
27
|
export class AppModule {}
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
## Configuration
|
|
30
31
|
|
|
31
|
-
The module uses environment-based configuration:
|
|
32
|
-
|
|
33
32
|
```typescript
|
|
34
|
-
export class
|
|
33
|
+
export class ServerAwsRedshiftDataConfig {
|
|
34
|
+
AWS_PROFILE?: string;
|
|
35
35
|
AWS_REGION: string;
|
|
36
|
-
AWS_PROFILE?: string; // Optional AWS profile
|
|
37
|
-
AWS_REDSHIFT_CLUSTER_IDENTIFIER: string;
|
|
38
|
-
AWS_REDSHIFT_DATABASE: string;
|
|
39
|
-
AWS_REDSHIFT_USER: string;
|
|
40
|
-
AWS_REDSHIFT_WORKGROUP?: string;
|
|
41
|
-
AWS_S3_BUCKET?: string; // For data operations
|
|
42
36
|
}
|
|
43
37
|
```
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
Credentials come from [`@onivoro/server-aws-credential-providers`](../aws-credential-providers/): when `AWS_PROFILE` is set the named profile is used, otherwise the AWS SDK default credential chain applies.
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
The module provides and exports:
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
- `RedshiftDataService`
|
|
44
|
+
- `RedshiftDataClient` (`@aws-sdk/client-redshift-data`)
|
|
45
|
+
- `RedshiftServerlessClient` (`@aws-sdk/client-redshift-serverless`)
|
|
46
|
+
- `RedshiftClient` (`@aws-sdk/client-redshift`)
|
|
47
|
+
- `ServerAwsRedshiftDataConfig`
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
import { Injectable } from '@nestjs/common';
|
|
53
|
-
import { RedshiftDataService } from '@onivoro/server-aws-redshift';
|
|
49
|
+
Each client is created once per process (the first `configure()` call wins) and is constructed with `logger: console`, so the SDK logs to the console.
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
export class DataWarehouseService {
|
|
57
|
-
constructor(private readonly redshiftService: RedshiftDataService) {}
|
|
51
|
+
## RedshiftDataService
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
async executeQuery(sql: string) {
|
|
61
|
-
const result = await this.redshiftService.query(sql);
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
53
|
+
Every method that runs SQL takes a target of `{ database: string; workgroupName: string }` and sends `ExecuteStatementCommand` with `WorkgroupName`, so these methods target Redshift Serverless (provisioned clusters via `ClusterIdentifier` are not supported).
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
55
|
+
```typescript
|
|
56
|
+
import { Injectable } from '@nestjs/common';
|
|
57
|
+
import { RedshiftDataService } from '@onivoro/server-aws-redshift';
|
|
69
58
|
|
|
70
|
-
|
|
71
|
-
async createAnalystGroup(groupName: string) {
|
|
72
|
-
await this.redshiftService.createGroup(groupName);
|
|
73
|
-
}
|
|
59
|
+
const target = { database: 'analytics', workgroupName: 'analytics-wg' };
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
61
|
+
@Injectable()
|
|
62
|
+
export class ReportService {
|
|
63
|
+
constructor(private readonly redshift: RedshiftDataService) {}
|
|
79
64
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
65
|
+
async ordersSince(since: string) {
|
|
66
|
+
// Named parameters use :name placeholders (Data API syntax)
|
|
67
|
+
return this.redshift.query(target, 'SELECT id, total FROM sales.orders WHERE created_at >= :since', { since });
|
|
83
68
|
}
|
|
84
69
|
}
|
|
85
70
|
```
|
|
86
71
|
|
|
87
|
-
|
|
72
|
+
### Queries
|
|
88
73
|
|
|
89
|
-
|
|
90
|
-
-
|
|
74
|
+
- **`query(target, sql, parameters?)`**: executes the statement, polls `DescribeStatement` every second until it is `FINISHED` (throws on `FAILED` or `ABORTED`), then fetches every page of `GetStatementResult` (following `NextToken`) and returns the rows as `Array<Array<string | number | boolean | undefined>>`. Each field is mapped to `stringValue ?? longValue ?? doubleValue ?? booleanValue`, so `0`, `false` and `''` are preserved; SQL `NULL` and `blobValue` fields come back as `undefined`. If fetching results fails (for example, the statement has no result set), it returns `[]`.
|
|
75
|
+
- **`queryV1(target, sql, parameters?)`**: executes the statement and waits with `waitForStatement`, returning the statement's `ResultRows` count (not the rows).
|
|
76
|
+
- **`waitForStatement(statementId, maxAttempts = 10, delay = 1000)`**: polls `DescribeStatement`; returns `ResultRows` on `FINISHED`; throws on `FAILED` or `ABORTED` (with the statement's `Error`, or `SQL statement was aborted`), or after `maxAttempts`.
|
|
91
77
|
|
|
92
|
-
|
|
93
|
-
- **createUser(username: string, password: string)** - Create a new Redshift user
|
|
94
|
-
- **createGroup(groupName: string)** - Create a new user group
|
|
95
|
-
- **addUserToGroup(username: string, groupName: string)** - Add user to a group
|
|
96
|
-
- **grantSchemaPermissions(groupName: string, schemaName: string)** - Grant schema access to a group
|
|
78
|
+
`parameters` is either an `SqlParameter[]` passed through unchanged, or a `RedshiftQueryParameters` object (`Record<string, string | number | boolean | null | undefined>`) converted to `{ name, value: String(value) }` entries (`null`/`undefined` become an undefined value).
|
|
97
79
|
|
|
98
|
-
###
|
|
99
|
-
- **getWorkgroupEndpoint(workgroupName: string)** - Get the endpoint for a workgroup (used internally)
|
|
80
|
+
### User and Group Management
|
|
100
81
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
82
|
+
- **`createDbGroupFromIamGroupIfNotExists({ iamGroup, database, workgroupName })`**: creates the group with `CREATE GROUP` if `pg_group` has no group with that name.
|
|
83
|
+
- **`createDatabaseUser({ database, workgroupName, user })`**: looks the user up in `pg_user` by name (`usename`, bound as a parameter); if absent, runs `CREATE USER` with a random placeholder password (`IAM_<uuid>`) and returns that password, otherwise returns `''`. Errors are logged and rethrown.
|
|
84
|
+
- **`addIamUserToDatabaseGroup({ database, workgroupName, user, group })`**: runs `ALTER GROUP ... ADD USER`. Errors are logged with `console.warn` and not rethrown.
|
|
85
|
+
- **`grantUsageOnSchema({ database, workgroupName, schema, group })`**: grants `USAGE` on the schema, `SELECT` on all its tables, and default `SELECT` on future tables to the group. A failing statement is logged and the rest still run.
|
|
86
|
+
- **`getAssociatedIAmRolesByWorkgroup({ database, workgroupName })`**: lists every group in the target database with its members from `pg_group`/`pg_user` via `query`, one row per group/member: `[role_name, member_name, role_id, owner_id, is_member]` (`member_name` is `undefined` for a group with no members).
|
|
104
87
|
|
|
105
88
|
```typescript
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
GetStatementResultCommand
|
|
111
|
-
} from '@aws-sdk/client-redshift-data';
|
|
89
|
+
await redshift.createDbGroupFromIamGroupIfNotExists({ ...target, iamGroup: 'analysts' });
|
|
90
|
+
await redshift.grantUsageOnSchema({ ...target, schema: 'reporting', group: 'analysts' });
|
|
91
|
+
await redshift.addIamUserToDatabaseGroup({ ...target, user: 'IAMR:analyst', group: 'analysts' });
|
|
92
|
+
```
|
|
112
93
|
|
|
113
|
-
|
|
114
|
-
export class AdvancedRedshiftService {
|
|
115
|
-
constructor(private readonly redshiftService: RedshiftDataService) {}
|
|
116
|
-
|
|
117
|
-
// List all databases
|
|
118
|
-
async listDatabases() {
|
|
119
|
-
const command = new ListDatabasesCommand({
|
|
120
|
-
ClusterIdentifier: process.env.AWS_REDSHIFT_CLUSTER_IDENTIFIER,
|
|
121
|
-
Database: process.env.AWS_REDSHIFT_DATABASE,
|
|
122
|
-
DbUser: process.env.AWS_REDSHIFT_USER
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
return await this.redshiftService.redshiftDataApiClient.send(command);
|
|
126
|
-
}
|
|
94
|
+
Identifiers (user, group, schema names) are interpolated directly into SQL without quoting or escaping, so never pass untrusted input to these methods.
|
|
127
95
|
|
|
128
|
-
|
|
129
|
-
async listTables(schemaName: string) {
|
|
130
|
-
const command = new ListTablesCommand({
|
|
131
|
-
ClusterIdentifier: process.env.AWS_REDSHIFT_CLUSTER_IDENTIFIER,
|
|
132
|
-
Database: process.env.AWS_REDSHIFT_DATABASE,
|
|
133
|
-
DbUser: process.env.AWS_REDSHIFT_USER,
|
|
134
|
-
SchemaPattern: schemaName
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
return await this.redshiftService.redshiftDataApiClient.send(command);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
```
|
|
96
|
+
### Workgroups
|
|
141
97
|
|
|
142
|
-
|
|
98
|
+
- **`verifyEndpointAccess(workgroupName)`**: returns the `GetWorkgroupCommand` response, or `undefined` (after logging an error) if the workgroup has no endpoint address.
|
|
143
99
|
|
|
144
|
-
|
|
145
|
-
import { Module, Injectable } from '@nestjs/common';
|
|
146
|
-
import { ServerAwsRedshiftModule, RedshiftDataService } from '@onivoro/server-aws-redshift';
|
|
100
|
+
## Direct Client Access
|
|
147
101
|
|
|
148
|
-
|
|
149
|
-
imports: [ServerAwsRedshiftModule.configure()],
|
|
150
|
-
providers: [AnalyticsService]
|
|
151
|
-
})
|
|
152
|
-
export class AnalyticsModule {}
|
|
102
|
+
For anything the service does not cover, inject the SDK clients directly:
|
|
153
103
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
async setupAnalyticsUser(email: string) {
|
|
159
|
-
const username = email.split('@')[0].replace(/[^a-zA-Z0-9]/g, '_');
|
|
160
|
-
const tempPassword = `Temp123!${Math.random().toString(36).slice(-4)}`;
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
// Create user
|
|
164
|
-
await this.redshiftService.createUser(username, tempPassword);
|
|
165
|
-
|
|
166
|
-
// Create or use existing analyst group
|
|
167
|
-
const groupName = 'analysts';
|
|
168
|
-
await this.redshiftService.createGroup(groupName);
|
|
169
|
-
|
|
170
|
-
// Add user to group
|
|
171
|
-
await this.redshiftService.addUserToGroup(username, groupName);
|
|
172
|
-
|
|
173
|
-
// Grant permissions to analytics schema
|
|
174
|
-
await this.redshiftService.grantSchemaPermissions(groupName, 'analytics');
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
username,
|
|
178
|
-
tempPassword,
|
|
179
|
-
message: 'User created successfully. Please change password on first login.'
|
|
180
|
-
};
|
|
181
|
-
} catch (error) {
|
|
182
|
-
console.error('Failed to setup user:', error);
|
|
183
|
-
throw error;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
104
|
+
```typescript
|
|
105
|
+
import { Injectable } from '@nestjs/common';
|
|
106
|
+
import { ListTablesCommand, RedshiftDataClient } from '@aws-sdk/client-redshift-data';
|
|
186
107
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
108
|
+
@Injectable()
|
|
109
|
+
export class CatalogService {
|
|
110
|
+
constructor(private readonly dataClient: RedshiftDataClient) {}
|
|
111
|
+
|
|
112
|
+
listTables(schemaPattern: string) {
|
|
113
|
+
return this.dataClient.send(
|
|
114
|
+
new ListTablesCommand({
|
|
115
|
+
Database: 'analytics',
|
|
116
|
+
WorkgroupName: 'analytics-wg',
|
|
117
|
+
SchemaPattern: schemaPattern,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
196
120
|
}
|
|
197
121
|
}
|
|
198
122
|
```
|
|
199
123
|
|
|
200
|
-
##
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
# Required
|
|
204
|
-
AWS_REGION=us-east-1
|
|
205
|
-
AWS_REDSHIFT_CLUSTER_IDENTIFIER=my-redshift-cluster
|
|
206
|
-
AWS_REDSHIFT_DATABASE=mydb
|
|
207
|
-
AWS_REDSHIFT_USER=admin
|
|
208
|
-
|
|
209
|
-
# Optional
|
|
210
|
-
AWS_PROFILE=my-profile
|
|
211
|
-
AWS_REDSHIFT_WORKGROUP=my-workgroup
|
|
212
|
-
AWS_S3_BUCKET=my-data-bucket
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
## Limitations
|
|
216
|
-
|
|
217
|
-
- Basic query execution only (no advanced features like prepared statements)
|
|
218
|
-
- Limited user management capabilities
|
|
219
|
-
- No built-in connection pooling or query optimization
|
|
220
|
-
- No support for advanced Redshift features (materialized views, stored procedures)
|
|
221
|
-
- For advanced operations, use the exposed `redshiftDataApiClient` directly
|
|
222
|
-
|
|
223
|
-
## Best Practices
|
|
124
|
+
## Known Limitations
|
|
224
125
|
|
|
225
|
-
|
|
226
|
-
2. **Query Validation**: Always validate user input before executing queries
|
|
227
|
-
3. **Error Handling**: Implement proper error handling for database operations
|
|
228
|
-
4. **Performance**: Use appropriate cluster sizing and distribution keys
|
|
229
|
-
5. **Monitoring**: Monitor query performance using Redshift console
|
|
126
|
+
- `query` has no timeout; it polls until the statement is `FINISHED`, `FAILED` or `ABORTED`.
|
|
230
127
|
|
|
231
128
|
## License
|
|
232
129
|
|
|
233
|
-
MIT
|
|
130
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onivoro/server-aws-redshift",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "25.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"url": "https://github.com/onivoro/monorepo.git"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@onivoro/server-aws-credential-providers": "
|
|
14
|
-
"@onivoro/server-common": "
|
|
13
|
+
"@onivoro/server-aws-credential-providers": "25.0.2",
|
|
14
|
+
"@onivoro/server-common": "25.0.2",
|
|
15
15
|
"tslib": "^2.3.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RedshiftDataClient, SqlParameter } from
|
|
1
|
+
import { RedshiftDataClient, SqlParameter } from '@aws-sdk/client-redshift-data';
|
|
2
2
|
import { RedshiftServerlessClient } from '@aws-sdk/client-redshift-serverless';
|
|
3
3
|
export type RedshiftQueryParameters = Record<string, string | number | boolean | null | undefined>;
|
|
4
4
|
export declare class RedshiftDataService {
|
|
@@ -36,11 +36,11 @@ export declare class RedshiftDataService {
|
|
|
36
36
|
query(_: {
|
|
37
37
|
database: string;
|
|
38
38
|
workgroupName: string;
|
|
39
|
-
}, Sql: string, parameters?: RedshiftQueryParameters | SqlParameter[]): Promise<(string | number | undefined)[][]>;
|
|
39
|
+
}, Sql: string, parameters?: RedshiftQueryParameters | SqlParameter[]): Promise<(string | number | boolean | undefined)[][]>;
|
|
40
40
|
getAssociatedIAmRolesByWorkgroup(_: {
|
|
41
41
|
database: string;
|
|
42
42
|
workgroupName: string;
|
|
43
|
-
}): Promise<(string | number | undefined)[][]>;
|
|
43
|
+
}): Promise<(string | number | boolean | undefined)[][]>;
|
|
44
44
|
private upsert;
|
|
45
45
|
}
|
|
46
46
|
//# sourceMappingURL=redshift.service.d.ts.map
|
|
@@ -27,7 +27,7 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
27
27
|
let attempts = 0;
|
|
28
28
|
while (attempts < maxAttempts) {
|
|
29
29
|
const describeStatementCommand = new client_redshift_data_1.DescribeStatementCommand({
|
|
30
|
-
Id: statementId
|
|
30
|
+
Id: statementId,
|
|
31
31
|
});
|
|
32
32
|
const status = await this.redshiftDataClient.send(describeStatementCommand);
|
|
33
33
|
if (status.Status === 'FINISHED') {
|
|
@@ -36,8 +36,11 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
36
36
|
if (status.Status === 'FAILED') {
|
|
37
37
|
throw new Error(`SQL statement failed: ${status.Error}`);
|
|
38
38
|
}
|
|
39
|
+
if (status.Status === 'ABORTED') {
|
|
40
|
+
throw new Error(status.Error || 'SQL statement was aborted');
|
|
41
|
+
}
|
|
39
42
|
attempts++;
|
|
40
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
41
44
|
}
|
|
42
45
|
throw new Error('Timeout waiting for SQL statement to complete');
|
|
43
46
|
}
|
|
@@ -54,7 +57,7 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
54
57
|
const groupResult = await this.redshiftDataClient.send(new client_redshift_data_1.ExecuteStatementCommand({
|
|
55
58
|
Database: _.database,
|
|
56
59
|
Sql: `ALTER GROUP ${_.group} ADD USER "${_.user}";`,
|
|
57
|
-
WorkgroupName: _.workgroupName
|
|
60
|
+
WorkgroupName: _.workgroupName,
|
|
58
61
|
}));
|
|
59
62
|
await this.waitForStatement(groupResult.Id);
|
|
60
63
|
console.log(`Added user "${_.user}" to group "${_.group}`);
|
|
@@ -67,18 +70,21 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
67
70
|
try {
|
|
68
71
|
const checkResult = await this.redshiftDataClient.send(new client_redshift_data_1.ExecuteStatementCommand({
|
|
69
72
|
Database: _.database,
|
|
70
|
-
Sql: `SELECT u.usename as username FROM pg_user u;`,
|
|
71
|
-
WorkgroupName: _.workgroupName
|
|
73
|
+
Sql: `SELECT u.usename as username FROM pg_user u WHERE u.usename = :user;`,
|
|
74
|
+
WorkgroupName: _.workgroupName,
|
|
75
|
+
Parameters: [{ name: 'user', value: _.user }],
|
|
72
76
|
}));
|
|
73
77
|
await this.waitForStatement(checkResult.Id);
|
|
74
|
-
const describeCommand = new client_redshift_data_1.DescribeStatementCommand({
|
|
78
|
+
const describeCommand = new client_redshift_data_1.DescribeStatementCommand({
|
|
79
|
+
Id: checkResult.Id,
|
|
80
|
+
});
|
|
75
81
|
const checkStatus = await this.redshiftDataClient.send(describeCommand);
|
|
76
82
|
if (!checkStatus.ResultRows) {
|
|
77
83
|
const placeholderPassword = `IAM_${(0, crypto_1.randomUUID)().replace(/-/g, '_')}`;
|
|
78
84
|
const createUserCommand = new client_redshift_data_1.ExecuteStatementCommand({
|
|
79
85
|
Database: _.database,
|
|
80
86
|
Sql: `CREATE USER ${_.user} PASSWORD '${placeholderPassword}';`,
|
|
81
|
-
WorkgroupName: _.workgroupName
|
|
87
|
+
WorkgroupName: _.workgroupName,
|
|
82
88
|
});
|
|
83
89
|
const createResult = await this.redshiftDataClient.send(createUserCommand);
|
|
84
90
|
await this.waitForStatement(createResult.Id);
|
|
@@ -106,7 +112,7 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
106
112
|
const executeStatementCommand = new client_redshift_data_1.ExecuteStatementCommand({
|
|
107
113
|
Database: _.database,
|
|
108
114
|
Sql,
|
|
109
|
-
WorkgroupName: _.workgroupName
|
|
115
|
+
WorkgroupName: _.workgroupName,
|
|
110
116
|
});
|
|
111
117
|
try {
|
|
112
118
|
const result = await this.redshiftDataClient.send(executeStatementCommand);
|
|
@@ -127,16 +133,18 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
127
133
|
const checkResult = await this.redshiftDataClient.send(new client_redshift_data_1.ExecuteStatementCommand({
|
|
128
134
|
Database: _.database,
|
|
129
135
|
Sql: `SELECT 1 FROM pg_group WHERE groname = '${_.iamGroup}';`,
|
|
130
|
-
WorkgroupName: _.workgroupName
|
|
136
|
+
WorkgroupName: _.workgroupName,
|
|
131
137
|
}));
|
|
132
138
|
await this.waitForStatement(checkResult.Id);
|
|
133
|
-
const describeCommand = new client_redshift_data_1.DescribeStatementCommand({
|
|
139
|
+
const describeCommand = new client_redshift_data_1.DescribeStatementCommand({
|
|
140
|
+
Id: checkResult.Id,
|
|
141
|
+
});
|
|
134
142
|
const checkStatus = await this.redshiftDataClient.send(describeCommand);
|
|
135
143
|
if (!checkStatus.ResultRows) {
|
|
136
144
|
const createGroupCommand = new client_redshift_data_1.ExecuteStatementCommand({
|
|
137
145
|
Database: _.database,
|
|
138
146
|
Sql: `CREATE GROUP ${_.iamGroup};`,
|
|
139
|
-
WorkgroupName: _.workgroupName
|
|
147
|
+
WorkgroupName: _.workgroupName,
|
|
140
148
|
});
|
|
141
149
|
const createResult = await this.redshiftDataClient.send(createGroupCommand);
|
|
142
150
|
await this.waitForStatement(createResult.Id);
|
|
@@ -170,26 +178,40 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
170
178
|
});
|
|
171
179
|
const { Id } = await this.redshiftDataClient.send(executeCommand);
|
|
172
180
|
if (!Id) {
|
|
173
|
-
throw new Error(
|
|
181
|
+
throw new Error('Failed to get statement ID');
|
|
174
182
|
}
|
|
175
|
-
let status =
|
|
183
|
+
let status = '';
|
|
176
184
|
do {
|
|
177
185
|
const describeCommand = new client_redshift_data_1.DescribeStatementCommand({ Id });
|
|
178
186
|
const { Status, Error: err } = await this.redshiftDataClient.send(describeCommand);
|
|
179
|
-
status = Status ||
|
|
180
|
-
if (status ===
|
|
187
|
+
status = Status || '';
|
|
188
|
+
if (status === 'FAILED') {
|
|
181
189
|
throw new Error(err);
|
|
182
190
|
}
|
|
183
|
-
if (status
|
|
184
|
-
|
|
191
|
+
if (status === 'ABORTED') {
|
|
192
|
+
throw new Error(err || 'SQL statement was aborted');
|
|
193
|
+
}
|
|
194
|
+
if (status !== 'FINISHED') {
|
|
195
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
185
196
|
}
|
|
186
|
-
} while (status !==
|
|
197
|
+
} while (status !== 'FINISHED');
|
|
187
198
|
try {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
const records = [];
|
|
200
|
+
let NextToken;
|
|
201
|
+
do {
|
|
202
|
+
const getResultsCommand = new client_redshift_data_1.GetStatementResultCommand({
|
|
203
|
+
Id,
|
|
204
|
+
NextToken,
|
|
205
|
+
});
|
|
206
|
+
const results = await this.redshiftDataClient.send(getResultsCommand);
|
|
207
|
+
records.push(...(results.Records || []));
|
|
208
|
+
NextToken = results.NextToken;
|
|
209
|
+
} while (NextToken);
|
|
210
|
+
return records.map((record) => {
|
|
211
|
+
return record.map((field) => field.stringValue ??
|
|
212
|
+
field.longValue ??
|
|
213
|
+
field.doubleValue ??
|
|
214
|
+
field.booleanValue);
|
|
193
215
|
});
|
|
194
216
|
}
|
|
195
217
|
catch (error) {
|
|
@@ -197,7 +219,7 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
197
219
|
}
|
|
198
220
|
}
|
|
199
221
|
catch (error) {
|
|
200
|
-
console.error({ detail:
|
|
222
|
+
console.error({ detail: 'Error executing query:', error });
|
|
201
223
|
throw error;
|
|
202
224
|
}
|
|
203
225
|
}
|
|
@@ -212,12 +234,6 @@ let RedshiftDataService = class RedshiftDataService {
|
|
|
212
234
|
CASE WHEN pg_has_role(u.usename, g.groname, 'MEMBER') THEN true ELSE false END as is_member
|
|
213
235
|
FROM pg_group g
|
|
214
236
|
LEFT JOIN pg_user u ON u.usesysid = ANY(g.grolist)
|
|
215
|
-
WHERE EXISTS (
|
|
216
|
-
SELECT 1
|
|
217
|
-
FROM svv_redshift_databases d
|
|
218
|
-
WHERE d.database_name = current_database()
|
|
219
|
-
AND d.workgroup_name = $1
|
|
220
|
-
)
|
|
221
237
|
ORDER BY g.groname, u.usename;
|
|
222
238
|
`;
|
|
223
239
|
return await this.query(_, query);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redshift.service.js","sourceRoot":"","sources":["../../../../../../../libs/server/aws-redshift/src/lib/services/redshift.service.ts"],"names":[],"mappings":";;;;AAAA,2CAA4C;AAC5C,
|
|
1
|
+
{"version":3,"file":"redshift.service.js","sourceRoot":"","sources":["../../../../../../../libs/server/aws-redshift/src/lib/services/redshift.service.ts"],"names":[],"mappings":"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"}
|