Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions __tests__/transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ test.only('Simple Transaction', async () => {
expect(results).toBe('Transaction Committed');

const endInfo = await rds.query(`SELECT COUNT(*) AS cn FROM ${TABLE}`);
const startCount = startInfo.data[0].cn.number;
const endCount = endInfo.data[0].cn.number;
const startCount = startInfo.data[0].cn.number || 0;
const endCount = endInfo.data[0].cn.number || 0;

expect(results).toBe('Transaction Committed');
expect(startCount).toBe(endCount! - 3);
expect(startCount).toBe(endCount - 3);
});

test('Rollback Transaction', async () => {
Expand Down
41 changes: 41 additions & 0 deletions src/ColumnValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { RDSDataService } from "aws-sdk";

class ColumnValue {
public field: RDSDataService.Field;

constructor(field: RDSDataService.Field) {
this.field = field;
}

get isNull(): boolean {
return !!this.field.isNull;
}

get date(): Date | null {
if (this.isNull) return null;
return new Date(this.field.stringValue!);
}

get string(): string | null {
if (this.isNull) return null;
return this.field.stringValue || null;
}

get number(): number | null {
if (this.isNull) return null;
return this.field.longValue || null;
}

get buffer(): Buffer | null {
if (this.isNull) return null;
return Buffer.from((this.field.blobValue || '').toString());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should do we call .toString() on a value that I'd presume to be a string?

Copy link
Collaborator

@MarkHerhold MarkHerhold Sep 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think this logic is wrong for binary type.

The AWS docs say:

blobValue - A value of BLOB data type. Type: Base64-encoded binary data object

So I think we would need to do Buffer.from(this.field.blobValue, 'base64'). I don't see a test that explicitly tests this logic and I haven't verified my assumption with the real RDS Data API.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disregard my prior comment - that's the API documentation, not the JS SDK docs.

It does look like blobValue is already a Buffer type so this value manipulation would be unnecessary, both in this PR and on master.

image

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MarkHerhold - looks like the blobValue can return as Buffer|Uint8Array|Blob|string possible answer I am committing now:

return Buffer.isBuffer(this.field.blobValue) ? this.field.blobValue : Buffer.from(this.field.blobValue as Uint8Array);

}

get boolean(): boolean | null {
if (this.isNull) return null;
return this.field.booleanValue || null;
}

}

export default ColumnValue;
88 changes: 18 additions & 70 deletions src/RDSData.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RDSDataService } from 'aws-sdk';
import { SqlParametersList } from 'aws-sdk/clients/rdsdataservice';
import ColumnValue from './ColumnValue';

export interface RDSDataOptions {
secretArn: string;
Expand All @@ -9,8 +10,7 @@ export interface RDSDataOptions {
rdsConfig?: RDSDataService.Types.ClientConfiguration;
}

// COLUMNS & PARAMETERS -------------------------
export interface RDSDataColumn {
export interface DataColumn {
name: string;
tableName: string;
type: string;
Expand All @@ -24,29 +24,19 @@ export interface RDSDataType {
}
export type RDSDataTypes = 'stringValue' | 'booleanValue' | 'longValue' | 'isNull' | 'blobValue' | undefined;

export type RDSDataParameterValue = string | Buffer | boolean | number | null | undefined;
export type ParameterValue = string | Buffer | boolean | number | null | undefined;
export interface RDSDataParameters {
[key: string]: RDSDataParameterValue;
[key: string]: ParameterValue;
}
export type RDSDataRow = { [key: string]: RDSDataResponseValue };
export type Row = { [key: string]: ColumnValue };

// RESPONSE TYPES -------------------------------
export interface RDSDataResponseValue {
isNull: boolean;
string?: string;
date?: Date;
boolean?: boolean;
buffer?: Buffer;
number?: number;
export interface ResponseData {
[key: string]: ColumnValue;
}

export interface RDSDataResponseData {
[key: string]: RDSDataResponseValue;
}

export interface RDSDataResponse {
columns: RDSDataColumn[];
data: RDSDataResponseData[];
export interface Response {
columns: DataColumn[];
data: ResponseData[];
numberOfRecordsUpdated: number;
insertId: number | undefined;
}
Expand Down Expand Up @@ -80,7 +70,7 @@ export class RDSData {
return this.rds;
}

public async query(sql: string, params?: RDSDataParameters, transactionId?: string): Promise<RDSDataResponse> {
public async query(sql: string, params?: RDSDataParameters, transactionId?: string): Promise<Response> {
const parameters = RDSData.formatParameters(params);
return new Promise((resolve, reject) => {
let queryParameters: RDSDataService.Types.ExecuteStatementRequest = {
Expand Down Expand Up @@ -113,7 +103,7 @@ export class RDSData {
return [];
}

const getType = (val: RDSDataParameterValue): RDSDataTypes => {
const getType = (val: ParameterValue): RDSDataTypes => {
const t = typeof val;
if (t === 'string') {
return 'stringValue';
Expand All @@ -133,7 +123,7 @@ export class RDSData {
return undefined;
};

const formatType = (name: string, value: RDSDataParameterValue, type: RDSDataTypes): RDSDataType => {
const formatType = (name: string, value: ParameterValue, type: RDSDataTypes): RDSDataType => {
if (!type) {
throw new Error(`Invalid Type for name: ${name} value: ${value} type: ${type} typeof: ${typeof value}`);
}
Expand Down Expand Up @@ -163,11 +153,11 @@ export class RDSData {
return parameters;
}

private static resultFormat(response: RDSDataService.Types.ExecuteStatementResponse): RDSDataResponse {
private static resultFormat(response: RDSDataService.Types.ExecuteStatementResponse): Response {
const insertId =
response.generatedFields && response.generatedFields.length > 0 ? response.generatedFields[0].longValue : 0;
const columns: RDSDataColumn[] = [];
const data: { [key: string]: RDSDataResponseValue }[] = [];
const columns: DataColumn[] = [];
const data: { [key: string]: ColumnValue }[] = [];
const numberOfRecordsUpdated = response.numberOfRecordsUpdated ?? 0;

if (response && response.columnMetadata && response.records) {
Expand All @@ -180,51 +170,9 @@ export class RDSData {
});

response.records.forEach((record) => {
const row: RDSDataRow = {};
const row: Row = {};
for (let c = 0; c < record.length; c += 1) {
/* tslint:disable:no-string-literal */
const isNull = record[c].isNull ?? false;
const v: RDSDataResponseValue = { isNull };
switch (columns[c].type) {
case 'BINARY':
v.buffer = isNull ? undefined : Buffer.from((record[c].blobValue || '').toString());
v.string = isNull ? undefined : v.buffer?.toString('base64');
break;
case 'BOOL':
case 'BIT':
v.boolean = isNull ? undefined : record[c].booleanValue;
v.number = v.boolean ? 1 : 0;
break;
case 'TIMESTAMP':
case 'DATETIME':
case 'DATE':
v.date = isNull ? undefined : new Date(record[c].stringValue ?? '');
v.string = isNull ? undefined : record[c].stringValue;
v.number = v.date ? v.date.getTime() : 0;
break;
case 'INTEGER':
case 'INTEGER UNSIGNED':
case 'INT':
case 'INT4':
case 'INT8':
case 'INT UNSIGNED':
case 'BIGINT':
case 'BIGINT UNSIGNED':
case 'SERIAL':
v.number = isNull ? undefined : record[c].longValue;
break;
case 'UUID':
case 'TEXT':
case 'CHAR':
case 'BPCHAR':
case 'VARCHAR':
v.string = isNull ? undefined : record[c].stringValue;
break;
default:
throw new Error(`Missing type: ${columns[c].type}`);
}
/* tslint:enable:no-string-literal */
row[columns[c].name] = v;
row[columns[c].name] = new ColumnValue(record[c]);
}
data.push(row);
});
Expand Down