Skip to content

Commit

Permalink
add glue view support
Browse files Browse the repository at this point in the history
  • Loading branch information
JeromeGuyon committed Nov 4, 2022
1 parent aa19ec0 commit e14108d
Show file tree
Hide file tree
Showing 11 changed files with 730 additions and 14 deletions.
20 changes: 20 additions & 0 deletions packages/@aws-cdk/aws-glue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,26 @@ new glue.Table(this, 'MyTable', {

By default, an S3 bucket will be created to store the table's data and stored in the bucket root. You can also manually pass the `bucket` and `s3Prefix`:

### View

A Glue view is a special glue Table that reference other existing glue tables or views.

```ts
declare const myDatabase: glue.Database;
new glue.View(this, 'MyView', {
database: myDatabase,
tableName: 'my_view',
columns: [{
name: 'col',
type: glue.Schema.STRING,
}],
sql: 'select "1" as col',
});
```

As the Glue view is nothing more than a special glue table it needs a `tableName` property.
In order to make the view working, ensure `sql` property validity, and constitency with view `columns`.

### Partition Keys

To improve query performance, a table can specify `partitionKeys` on which data is stored and queried separately. For example, you might partition a table by `year` and `month` to optimize queries based on a time window:
Expand Down
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-glue/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export interface Type {
* Glue InputString for this type.
*/
readonly inputString: string;

/**
* Glue type converted in Presto type for View usage
*/
readonly prestoInputString: string;
}

/**
Expand All @@ -42,11 +47,13 @@ export class Schema {
public static readonly BOOLEAN: Type = {
isPrimitive: true,
inputString: 'boolean',
prestoInputString: 'boolean',
};

public static readonly BINARY: Type = {
isPrimitive: true,
inputString: 'binary',
prestoInputString: 'varbinary',
};

/**
Expand All @@ -55,16 +62,19 @@ export class Schema {
public static readonly BIG_INT: Type = {
isPrimitive: true,
inputString: 'bigint',
prestoInputString: 'bigint',
};

public static readonly DOUBLE: Type = {
isPrimitive: true,
inputString: 'double',
prestoInputString: 'double',
};

public static readonly FLOAT: Type = {
isPrimitive: true,
inputString: 'float',
prestoInputString: 'real',
};

/**
Expand All @@ -73,6 +83,7 @@ export class Schema {
public static readonly INTEGER: Type = {
isPrimitive: true,
inputString: 'int',
prestoInputString: 'integer',
};

/**
Expand All @@ -81,6 +92,7 @@ export class Schema {
public static readonly SMALL_INT: Type = {
isPrimitive: true,
inputString: 'smallint',
prestoInputString: 'smallint',
};

/**
Expand All @@ -89,6 +101,7 @@ export class Schema {
public static readonly TINY_INT: Type = {
isPrimitive: true,
inputString: 'tinyint',
prestoInputString: 'tinyint',
};

/**
Expand All @@ -97,6 +110,7 @@ export class Schema {
public static readonly DATE: Type = {
isPrimitive: true,
inputString: 'date',
prestoInputString: 'date',
};

/**
Expand All @@ -105,6 +119,7 @@ export class Schema {
public static readonly TIMESTAMP: Type = {
isPrimitive: true,
inputString: 'timestamp',
prestoInputString: 'timestamp',
};

/**
Expand All @@ -113,6 +128,7 @@ export class Schema {
public static readonly STRING: Type = {
isPrimitive: true,
inputString: 'string',
prestoInputString: 'varchar',
};

/**
Expand All @@ -127,6 +143,7 @@ export class Schema {
return {
isPrimitive: true,
inputString: scale !== undefined ? `decimal(${precision},${scale})` : `decimal(${precision})`,
prestoInputString: scale !== undefined ? `decimal(${precision},${scale})` : `decimal(${precision})`,
};
}

Expand All @@ -145,6 +162,7 @@ export class Schema {
return {
isPrimitive: true,
inputString: `char(${length})`,
prestoInputString: `char(${length})`,
};
}

Expand All @@ -163,6 +181,7 @@ export class Schema {
return {
isPrimitive: true,
inputString: `varchar(${length})`,
prestoInputString: `varchar(${length})`,
};
}

Expand All @@ -175,6 +194,7 @@ export class Schema {
return {
isPrimitive: false,
inputString: `array<${itemType.inputString}>`,
prestoInputString: `array(${itemType.inputString})`,
};
}

Expand All @@ -191,6 +211,7 @@ export class Schema {
return {
isPrimitive: false,
inputString: `map<${keyType.inputString},${valueType.inputString}>`,
prestoInputString: `map(${keyType.inputString},${valueType.inputString})`,
};
}

Expand All @@ -209,6 +230,7 @@ export class Schema {
return `${column.name}:${column.type.inputString} COMMENT '${column.comment}'`;
}
}).join(',')}>`,
prestoInputString: `struct(${columns.map(column => `"${column.name}" ${column.type.inputString}`).join(',')}>`,
};
}
}
148 changes: 146 additions & 2 deletions packages/@aws-cdk/aws-glue/lib/table.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import * as s3 from '@aws-cdk/aws-s3';
import { ArnFormat, Fn, IResource, Names, Resource, Stack } from '@aws-cdk/core';
import { ArnFormat, Fn, IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core';
import * as cr from '@aws-cdk/custom-resources';
import { AwsCustomResource } from '@aws-cdk/custom-resources';
import { Construct } from 'constructs';
Expand Down Expand Up @@ -185,6 +185,8 @@ export interface TableProps {

/**
* A Glue table.
*
* @resource AWS::Glue::Table
*/
export class Table extends Resource implements ITable {

Expand Down Expand Up @@ -558,7 +560,7 @@ const writePermissions = [
'glue:UpdatePartition',
];

function renderColumns(columns?: Array<Column | Column>) {
function renderColumns(columns?: Array<Column>) {
if (columns === undefined) {
return undefined;
}
Expand All @@ -570,3 +572,145 @@ function renderColumns(columns?: Array<Column | Column>) {
};
});
}

function renderPrestoColumns(columns: Array<Column>): string {
return JSON.stringify(columns.map(col => ({
name: col.name,
type: col.type.prestoInputString,
})));
}

/**
* View Properties
*/
export interface ViewProps {
/**
* Name of the view
*/
readonly tableName: string;

/**
* Description of the table.
*
* @default generated
*/
readonly description?: string;

/**
* Database in which to store the view.
*/
readonly database: IDatabase;

/**
* Columns of the view.
*/
readonly columns: Column[];

/**
* View SQL.
*/
readonly sql: string;
}

/**
* A Glue View
*
* @resource AWS::Glue::Table
*/
export class View extends Resource implements ITable {

/**
* Database this table belongs to.
*/
public readonly database: IDatabase;

/**
* Name of the underlying glue table.
*/
public readonly tableName: string;

/**
* ARN of the underlying glue table.
*/
public readonly tableArn: string;

/**
* This table's columns.
*/
public readonly columns: Column[];

constructor(scope: Construct, id: string, props: ViewProps) {
super(scope, id, {
physicalName: props.tableName,
});

this.database = props.database;
this.columns = props.columns;

const tableResource = new CfnTable(this, 'View', {
catalogId: props.database.catalogId,

databaseName: props.database.databaseName,

tableInput: {
name: this.physicalName,
description: props.description || `${props.tableName} generated by CDK`,

partitionKeys: [],

parameters: {
comment: 'Presto View',
presto_view: 'true',
},

storageDescriptor: {
location: '',
compressed: false,
columns: renderColumns(props.columns),
serdeInfo: {},
},
tableType: 'VIRTUAL_VIEW',
viewExpandedText: '/* Presto View */',
viewOriginalText: Lazy.string({
produce: () =>
Fn.sub('/* Presto View: ${PrestoBase64} */', {
PrestoBase64: Fn.base64(
Fn.sub('{"originalSql":${OriginalSql},"catalog":"${Catalog}","schema":"${Schema}","columns":${Columns}}', {
Catalog: 'awsdatacatalog',
Schema: props.database.databaseName,
OriginalSql: JSON.stringify(props.sql),
Columns: renderPrestoColumns(props.columns),
}),
),
}),
}),
},
});

this.tableName = this.getResourceNameAttribute(tableResource.ref);
this.tableArn = this.stack.formatArn({
service: 'glue',
resource: 'table',
resourceName: `${this.database.databaseName}/${this.tableName}`,
});
}

/**
* Grant read permissions ti the View to an IAM principal
* @param grantee
*/
public grantRead(grantee: iam.IGrantable): iam.Grant {
return this.grant(grantee, readPermissions);
}

/**
* Grant the given identity custom permissions.
*/
public grant(grantee: iam.IGrantable, actions: string[]) {
return iam.Grant.addToPrincipal({
grantee,
resourceArns: [this.tableArn],
actions,
});
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"20.0.0"}
{"version":"21.0.0"}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "20.0.0",
"version": "21.0.0",
"files": {
"eef5abdc0f1ee16e5be447f60688757df6726f3c2d1d06c136e9bbdb99d96e1f": {
"349027dd5d4861050937f4ea890519b645bd3fee009105c0af11a2afeacc8b83": {
"source": {
"path": "aws-cdk-glue.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "eef5abdc0f1ee16e5be447f60688757df6726f3c2d1d06c136e9bbdb99d96e1f.json",
"objectKey": "349027dd5d4861050937f4ea890519b645bd3fee009105c0af11a2afeacc8b83.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Loading

0 comments on commit e14108d

Please sign in to comment.