Skip to content

Commit 46cadd4

Browse files
authored
feat(redshift): Tables can include comments (#23847)
Adds comments to tables. closes #22682 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 5bde92c commit 46cadd4

File tree

22 files changed

+245
-142
lines changed

22 files changed

+245
-142
lines changed

packages/@aws-cdk/aws-redshift/README.md

+28-5
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ import * as ec2 from '@aws-cdk/aws-ec2';
5454
import * as s3 from '@aws-cdk/aws-s3';
5555

5656
const vpc = new ec2.Vpc(this, 'Vpc');
57-
const bucket = s3.Bucket.fromBucketName(stack, 'bucket', 'logging-bucket');
57+
const bucket = s3.Bucket.fromBucketName(this, 'bucket', 'logging-bucket');
5858

5959
const cluster = new Cluster(this, 'Redshift', {
6060
masterUser: {
6161
masterUsername: 'admin',
6262
},
6363
vpc,
6464
loggingProperties: {
65-
loggingBucket = bucket,
65+
loggingBucket: bucket,
6666
loggingKeyPrefix: 'prefix',
6767
}
6868
});
@@ -200,6 +200,20 @@ new Table(this, 'Table', {
200200
});
201201
```
202202

203+
Tables can also be configured with a comment:
204+
205+
```ts fixture=cluster
206+
new Table(this, 'Table', {
207+
tableColumns: [
208+
{ name: 'col1', dataType: 'varchar(4)' },
209+
{ name: 'col2', dataType: 'float' }
210+
],
211+
cluster: cluster,
212+
databaseName: 'databaseName',
213+
comment: 'This is a comment',
214+
});
215+
```
216+
203217
### Granting Privileges
204218

205219
You can give a user privileges to perform certain actions on a table by using the
@@ -305,7 +319,9 @@ cluster.addRotationMultiUser('MultiUserRotation', {
305319
You can add a parameter to a parameter group with`ClusterParameterGroup.addParameter()`.
306320

307321
```ts
308-
const params = new ClusterParameterGroup(stack, 'Params', {
322+
import { ClusterParameterGroup } from '@aws-cdk/aws-redshift';
323+
324+
const params = new ClusterParameterGroup(this, 'Params', {
309325
description: 'desc',
310326
parameters: {
311327
require_ssl: 'true',
@@ -318,6 +334,8 @@ params.addParameter('enable_user_activity_logging', 'true');
318334
Additionally, you can add a parameter to the cluster's associated parameter group with `Cluster.addToParameterGroup()`. If the cluster does not have an associated parameter group, a new parameter group is created.
319335

320336
```ts
337+
import * as ec2 from '@aws-cdk/aws-ec2';
338+
import * as cdk from '@aws-cdk/core';
321339
declare const vpc: ec2.Vpc;
322340

323341
const cluster = new Cluster(this, 'Cluster', {
@@ -336,9 +354,11 @@ cluster.addToParameterGroup('enable_user_activity_logging', 'true');
336354
If you configure your cluster to be publicly accessible, you can optionally select an *elastic IP address* to use for the external IP address. An elastic IP address is a static IP address that is associated with your AWS account. You can use an elastic IP address to connect to your cluster from outside the VPC. An elastic IP address gives you the ability to change your underlying configuration without affecting the IP address that clients use to connect to your cluster. This approach can be helpful for situations such as recovery after a failure.
337355

338356
```ts
357+
import * as ec2 from '@aws-cdk/aws-ec2';
358+
import * as cdk from '@aws-cdk/core';
339359
declare const vpc: ec2.Vpc;
340360

341-
new Cluster(stack, 'Redshift', {
361+
new Cluster(this, 'Redshift', {
342362
masterUser: {
343363
masterUsername: 'admin',
344364
masterPassword: cdk.SecretValue.unsafePlainText('tooshort'),
@@ -352,6 +372,7 @@ new Cluster(stack, 'Redshift', {
352372
If the Cluster is in a VPC and you want to connect to it using the private IP address from within the cluster, it is important to enable *DNS resolution* and *DNS hostnames* in the VPC config. If these parameters would not be set, connections from within the VPC would connect to the elastic IP address and not the private IP address.
353373

354374
```ts
375+
import * as ec2 from '@aws-cdk/aws-ec2';
355376
const vpc = new ec2.Vpc(this, 'VPC', {
356377
enableDnsSupport: true,
357378
enableDnsHostnames: true,
@@ -373,9 +394,11 @@ In some cases, you might want to associate the cluster with an elastic IP addres
373394
When you use Amazon Redshift enhanced VPC routing, Amazon Redshift forces all COPY and UNLOAD traffic between your cluster and your data repositories through your virtual private cloud (VPC) based on the Amazon VPC service. By using enhanced VPC routing, you can use standard VPC features, such as VPC security groups, network access control lists (ACLs), VPC endpoints, VPC endpoint policies, internet gateways, and Domain Name System (DNS) servers, as described in the Amazon VPC User Guide. You use these features to tightly manage the flow of data between your Amazon Redshift cluster and other resources. When you use enhanced VPC routing to route traffic through your VPC, you can also use VPC flow logs to monitor COPY and UNLOAD traffic.
374395

375396
```ts
397+
import * as ec2 from '@aws-cdk/aws-ec2';
398+
import * as cdk from '@aws-cdk/core';
376399
declare const vpc: ec2.Vpc;
377400

378-
new Cluster(stack, 'Redshift', {
401+
new Cluster(this, 'Redshift', {
379402
masterUser: {
380403
masterUsername: 'admin',
381404
masterPassword: cdk.SecretValue.unsafePlainText('tooshort'),

packages/@aws-cdk/aws-redshift/lib/private/database-query-provider/table.ts

+11
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ async function createTable(
6060
}
6161

6262
await executeStatement(statement, tableAndClusterProps);
63+
64+
if (tableAndClusterProps.tableComment) {
65+
await executeStatement(`COMMENT ON TABLE ${tableName} IS '${tableAndClusterProps.tableComment}'`, tableAndClusterProps);
66+
}
67+
6368
return tableName;
6469
}
6570

@@ -143,6 +148,12 @@ async function updateTable(
143148
}
144149
}
145150

151+
const oldComment = oldResourceProperties.tableComment;
152+
const newComment = tableAndClusterProps.tableComment;
153+
if (oldComment !== newComment) {
154+
alterationStatements.push(`COMMENT ON TABLE ${tableName} IS ${newComment ? `'${newComment}'` : 'NULL'}`);
155+
}
156+
146157
await Promise.all(alterationStatements.map(statement => executeStatement(statement, tableAndClusterProps)));
147158

148159
return tableName;

packages/@aws-cdk/aws-redshift/lib/private/handler-props.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface TableHandlerProps {
2020
readonly tableColumns: Column[];
2121
readonly distStyle?: TableDistStyle;
2222
readonly sortStyle: TableSortStyle;
23+
readonly tableComment?: string;
2324
}
2425

2526
export interface TablePrivilege {

packages/@aws-cdk/aws-redshift/lib/table.ts

+8
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ export interface TableProps extends DatabaseOptions {
117117
* @default cdk.RemovalPolicy.Retain
118118
*/
119119
readonly removalPolicy?: cdk.RemovalPolicy;
120+
121+
/**
122+
* A comment to attach to the table.
123+
*
124+
* @default - no comment
125+
*/
126+
readonly tableComment?: string;
120127
}
121128

122129
/**
@@ -234,6 +241,7 @@ export class Table extends TableBase {
234241
tableColumns: this.tableColumns,
235242
distStyle: props.distStyle,
236243
sortStyle: props.sortStyle ?? this.getDefaultSortStyle(props.tableColumns),
244+
tableComment: props.tableComment,
237245
},
238246
});
239247

packages/@aws-cdk/aws-redshift/test/database-query-provider/table.test.ts

+50
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,20 @@ describe('create', () => {
134134
Sql: `CREATE TABLE ${tableNamePrefix}${requestIdTruncated} (col1 varchar(4),col2 float,col3 float) DISTSTYLE KEY DISTKEY(col1) COMPOUND SORTKEY(col2,col3)`,
135135
}));
136136
});
137+
138+
test('serializes table comment in statement', async () => {
139+
const event = baseEvent;
140+
const newResourceProperties: ResourcePropertiesType = {
141+
...resourceProperties,
142+
tableComment: 'table comment',
143+
};
144+
145+
await manageTable(newResourceProperties, event);
146+
147+
expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({
148+
Sql: `COMMENT ON TABLE ${tableNamePrefix}${requestIdTruncated} IS 'table comment'`,
149+
}));
150+
});
137151
});
138152

139153
describe('delete', () => {
@@ -502,4 +516,40 @@ describe('update', () => {
502516
});
503517
});
504518

519+
describe('table comment', () => {
520+
test('does not replace if comment added on table', async () => {
521+
const newComment = 'newComment';
522+
const newResourceProperties = {
523+
...resourceProperties,
524+
tableComment: newComment,
525+
};
526+
527+
await expect(manageTable(newResourceProperties, event)).resolves.toMatchObject({
528+
PhysicalResourceId: physicalResourceId,
529+
});
530+
expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({
531+
Sql: `COMMENT ON TABLE ${physicalResourceId} IS '${newComment}'`,
532+
}));
533+
});
534+
535+
test('does not replace if comment removed on table', async () => {
536+
const newEvent = {
537+
...event,
538+
OldResourceProperties: {
539+
...event.OldResourceProperties,
540+
tableComment: 'oldComment',
541+
},
542+
};
543+
const newResourceProperties = {
544+
...resourceProperties,
545+
};
546+
547+
await expect(manageTable(newResourceProperties, newEvent)).resolves.toMatchObject({
548+
PhysicalResourceId: physicalResourceId,
549+
});
550+
expect(mockExecuteStatement).toHaveBeenCalledWith(expect.objectContaining({
551+
Sql: `COMMENT ON TABLE ${physicalResourceId} IS NULL`,
552+
}));
553+
});
554+
});
505555
});

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/asset.8359857aa9b7c3fbc8bba9a505282ba848915383c4549f21b9f93f9f35b56415/table.js

-117
This file was deleted.

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/asset.ab58b1384030fef0fc8663c06f6fd62196fb3ae8807ab82e4559967d3b885b08/table.js

+125
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/aws-cdk-redshift-cluster-database.assets.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "22.0.0",
2+
"version": "29.0.0",
33
"files": {
4-
"8359857aa9b7c3fbc8bba9a505282ba848915383c4549f21b9f93f9f35b56415": {
4+
"ab58b1384030fef0fc8663c06f6fd62196fb3ae8807ab82e4559967d3b885b08": {
55
"source": {
6-
"path": "asset.8359857aa9b7c3fbc8bba9a505282ba848915383c4549f21b9f93f9f35b56415",
6+
"path": "asset.ab58b1384030fef0fc8663c06f6fd62196fb3ae8807ab82e4559967d3b885b08",
77
"packaging": "zip"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "8359857aa9b7c3fbc8bba9a505282ba848915383c4549f21b9f93f9f35b56415.zip",
12+
"objectKey": "ab58b1384030fef0fc8663c06f6fd62196fb3ae8807ab82e4559967d3b885b08.zip",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}
@@ -27,15 +27,15 @@
2727
}
2828
}
2929
},
30-
"fadca82b7c081a8b6de68f952878e92ba5610dce63ccbead865e1b854073bff0": {
30+
"fd9bc22f4d8ca7fabbbe054d374117616ffa6a2393152ecb529d2b385432d259": {
3131
"source": {
3232
"path": "aws-cdk-redshift-cluster-database.template.json",
3333
"packaging": "file"
3434
},
3535
"destinations": {
3636
"current_account-current_region": {
3737
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
38-
"objectKey": "fadca82b7c081a8b6de68f952878e92ba5610dce63ccbead865e1b854073bff0.json",
38+
"objectKey": "fd9bc22f4d8ca7fabbbe054d374117616ffa6a2393152ecb529d2b385432d259.json",
3939
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
4040
}
4141
}

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/aws-cdk-redshift-cluster-database.template.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@
10041004
"S3Bucket": {
10051005
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
10061006
},
1007-
"S3Key": "8359857aa9b7c3fbc8bba9a505282ba848915383c4549f21b9f93f9f35b56415.zip"
1007+
"S3Key": "ab58b1384030fef0fc8663c06f6fd62196fb3ae8807ab82e4559967d3b885b08.zip"
10081008
},
10091009
"Role": {
10101010
"Fn::GetAtt": [
@@ -1176,7 +1176,8 @@
11761176
}
11771177
],
11781178
"distStyle": "KEY",
1179-
"sortStyle": "INTERLEAVED"
1179+
"sortStyle": "INTERLEAVED",
1180+
"tableComment": "A test table"
11801181
},
11811182
"UpdateReplacePolicy": "Delete",
11821183
"DeletionPolicy": "Delete"
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":"22.0.0"}
1+
{"version":"29.0.0"}

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/integ.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "22.0.0",
2+
"version": "29.0.0",
33
"testCases": {
44
"redshift-cluster-database-integ/DefaultTest": {
55
"stacks": [

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/manifest.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "22.0.0",
2+
"version": "29.0.0",
33
"artifacts": {
44
"aws-cdk-redshift-cluster-database.assets": {
55
"type": "cdk:asset-manifest",
@@ -17,7 +17,7 @@
1717
"validateOnSynth": false,
1818
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
1919
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
20-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fadca82b7c081a8b6de68f952878e92ba5610dce63ccbead865e1b854073bff0.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fd9bc22f4d8ca7fabbbe054d374117616ffa6a2393152ecb529d2b385432d259.json",
2121
"requiresBootstrapStackVersion": 6,
2222
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2323
"additionalDependencies": [

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/redshiftclusterdatabaseintegDefaultTestDeployAssert4339FB48.assets.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "22.0.0",
2+
"version": "29.0.0",
33
"files": {
44
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
55
"source": {

packages/@aws-cdk/aws-redshift/test/integ.database.js.snapshot/tree.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,7 @@
12281228
},
12291229
"constructInfo": {
12301230
"fqn": "constructs.Construct",
1231-
"version": "10.1.182"
1231+
"version": "10.1.209"
12321232
}
12331233
},
12341234
"TablePrivileges": {
@@ -1470,13 +1470,13 @@
14701470
},
14711471
"constructInfo": {
14721472
"fqn": "constructs.Construct",
1473-
"version": "10.1.182"
1473+
"version": "10.1.209"
14741474
}
14751475
}
14761476
},
14771477
"constructInfo": {
14781478
"fqn": "constructs.Construct",
1479-
"version": "10.1.182"
1479+
"version": "10.1.209"
14801480
}
14811481
}
14821482
},
@@ -1639,7 +1639,7 @@
16391639
"s3Bucket": {
16401640
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
16411641
},
1642-
"s3Key": "8359857aa9b7c3fbc8bba9a505282ba848915383c4549f21b9f93f9f35b56415.zip"
1642+
"s3Key": "ab58b1384030fef0fc8663c06f6fd62196fb3ae8807ab82e4559967d3b885b08.zip"
16431643
},
16441644
"role": {
16451645
"Fn::GetAtt": [
@@ -1902,7 +1902,7 @@
19021902
},
19031903
"constructInfo": {
19041904
"fqn": "constructs.Construct",
1905-
"version": "10.1.182"
1905+
"version": "10.1.209"
19061906
}
19071907
}
19081908
},
@@ -1946,7 +1946,7 @@
19461946
"path": "redshift-cluster-database-integ/DefaultTest/Default",
19471947
"constructInfo": {
19481948
"fqn": "constructs.Construct",
1949-
"version": "10.1.182"
1949+
"version": "10.1.209"
19501950
}
19511951
},
19521952
"DeployAssert": {
@@ -1992,7 +1992,7 @@
19921992
"path": "Tree",
19931993
"constructInfo": {
19941994
"fqn": "constructs.Construct",
1995-
"version": "10.1.182"
1995+
"version": "10.1.209"
19961996
}
19971997
}
19981998
},

packages/@aws-cdk/aws-redshift/test/integ.database.ts

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const table = new redshift.Table(stack, 'Table', {
4949
],
5050
distStyle: redshift.TableDistStyle.KEY,
5151
sortStyle: redshift.TableSortStyle.INTERLEAVED,
52+
tableComment: 'A test table',
5253
});
5354
table.grant(user, redshift.TableAction.INSERT, redshift.TableAction.DELETE);
5455

0 commit comments

Comments
 (0)