data-api-client 1.2.0 → 2.0.0-beta.0
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 +136 -110
- package/index.js +334 -298
- package/package.json +6 -5
package/index.js
CHANGED
|
@@ -8,13 +8,20 @@
|
|
|
8
8
|
* https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html
|
|
9
9
|
*
|
|
10
10
|
* @author Jeremy Daly <jeremy@jeremydaly.com>
|
|
11
|
-
* @version
|
|
11
|
+
* @version 2.0.0-beta
|
|
12
12
|
* @license MIT
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// Require the aws-sdk. This is a dev dependency, so if being used
|
|
16
16
|
// outside of a Lambda execution environment, it must be manually installed.
|
|
17
|
-
const
|
|
17
|
+
const {
|
|
18
|
+
RDSDataClient,
|
|
19
|
+
ExecuteStatementCommand,
|
|
20
|
+
BatchExecuteStatementCommand,
|
|
21
|
+
BeginTransactionCommand,
|
|
22
|
+
CommitTransactionCommand,
|
|
23
|
+
RollbackTransactionCommand
|
|
24
|
+
} = require('@aws-sdk/client-rds-data')
|
|
18
25
|
|
|
19
26
|
// Require sqlstring to add additional escaping capabilities
|
|
20
27
|
const sqlString = require('sqlstring')
|
|
@@ -36,103 +43,125 @@ const supportedTypes = [
|
|
|
36
43
|
/********************************************************************/
|
|
37
44
|
|
|
38
45
|
// Simple error function
|
|
39
|
-
const error = (...err) => {
|
|
46
|
+
const error = (...err) => {
|
|
47
|
+
throw Error(...err)
|
|
48
|
+
}
|
|
40
49
|
|
|
41
50
|
// Parse SQL statement from provided arguments
|
|
42
|
-
const parseSQL = args =>
|
|
43
|
-
typeof args[0] === 'string'
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
const parseSQL = (args) =>
|
|
52
|
+
typeof args[0] === 'string'
|
|
53
|
+
? args[0]
|
|
54
|
+
: typeof args[0] === 'object' && typeof args[0].sql === 'string'
|
|
55
|
+
? args[0].sql
|
|
56
|
+
: error(`No 'sql' statement provided.`)
|
|
46
57
|
|
|
47
58
|
// Parse the parameters from provided arguments
|
|
48
|
-
const parseParams = args =>
|
|
49
|
-
Array.isArray(args[0].parameters)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
const parseParams = (args) =>
|
|
60
|
+
Array.isArray(args[0].parameters)
|
|
61
|
+
? args[0].parameters
|
|
62
|
+
: typeof args[0].parameters === 'object'
|
|
63
|
+
? [args[0].parameters]
|
|
64
|
+
: Array.isArray(args[1])
|
|
65
|
+
? args[1]
|
|
66
|
+
: typeof args[1] === 'object'
|
|
67
|
+
? [args[1]]
|
|
68
|
+
: args[0].parameters
|
|
69
|
+
? error(`'parameters' must be an object or array`)
|
|
70
|
+
: args[1]
|
|
71
|
+
? error('Parameters must be an object or array')
|
|
72
|
+
: []
|
|
56
73
|
|
|
57
74
|
// Parse the supplied database, or default to config
|
|
58
|
-
const parseDatabase = (config,args) =>
|
|
59
|
-
config.transactionId
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
const parseDatabase = (config, args) =>
|
|
76
|
+
config.transactionId
|
|
77
|
+
? config.database
|
|
78
|
+
: typeof args[0].database === 'string'
|
|
79
|
+
? args[0].database
|
|
80
|
+
: args[0].database
|
|
81
|
+
? error(`'database' must be a string.`)
|
|
82
|
+
: config.database
|
|
83
|
+
? config.database
|
|
84
|
+
: undefined // removed for #47 - error('No \'database\' provided.')
|
|
64
85
|
|
|
65
86
|
// Parse the supplied hydrateColumnNames command, or default to config
|
|
66
|
-
const parseHydrate = (config,args) =>
|
|
67
|
-
typeof args[0].hydrateColumnNames === 'boolean'
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
const parseHydrate = (config, args) =>
|
|
88
|
+
typeof args[0].hydrateColumnNames === 'boolean'
|
|
89
|
+
? args[0].hydrateColumnNames
|
|
90
|
+
: args[0].hydrateColumnNames
|
|
91
|
+
? error(`'hydrateColumnNames' must be a boolean.`)
|
|
92
|
+
: config.hydrateColumnNames
|
|
70
93
|
|
|
71
94
|
// Parse the supplied format options, or default to config
|
|
72
|
-
const parseFormatOptions = (config,args) =>
|
|
73
|
-
typeof args[0].formatOptions === 'object'
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
const parseFormatOptions = (config, args) =>
|
|
96
|
+
typeof args[0].formatOptions === 'object'
|
|
97
|
+
? {
|
|
98
|
+
deserializeDate:
|
|
99
|
+
typeof args[0].formatOptions.deserializeDate === 'boolean'
|
|
100
|
+
? args[0].formatOptions.deserializeDate
|
|
101
|
+
: args[0].formatOptions.deserializeDate
|
|
102
|
+
? error(`'formatOptions.deserializeDate' must be a boolean.`)
|
|
103
|
+
: config.formatOptions.deserializeDate,
|
|
104
|
+
treatAsLocalDate:
|
|
105
|
+
typeof args[0].formatOptions.treatAsLocalDate == 'boolean'
|
|
106
|
+
? args[0].formatOptions.treatAsLocalDate
|
|
107
|
+
: args[0].formatOptions.treatAsLocalDate
|
|
108
|
+
? error(`'formatOptions.treatAsLocalDate' must be a boolean.`)
|
|
109
|
+
: config.formatOptions.treatAsLocalDate
|
|
110
|
+
}
|
|
111
|
+
: args[0].formatOptions
|
|
112
|
+
? error(`'formatOptions' must be an object.`)
|
|
113
|
+
: config.formatOptions
|
|
83
114
|
|
|
84
115
|
// Prepare method params w/ supplied inputs if an object is passed
|
|
85
|
-
const prepareParams = ({ secretArn,resourceArn },args) => {
|
|
116
|
+
const prepareParams = ({ secretArn, resourceArn }, args) => {
|
|
86
117
|
return Object.assign(
|
|
87
|
-
{ secretArn,resourceArn }, // return Arns
|
|
88
|
-
typeof args[0] === 'object' ?
|
|
89
|
-
omit(args[0],['hydrateColumnNames','parameters']) : {} // merge any inputs
|
|
118
|
+
{ secretArn, resourceArn }, // return Arns
|
|
119
|
+
typeof args[0] === 'object' ? omit(args[0], ['hydrateColumnNames', 'parameters']) : {} // merge any inputs
|
|
90
120
|
)
|
|
91
121
|
}
|
|
92
122
|
|
|
93
123
|
// Utility function for removing certain keys from an object
|
|
94
|
-
const omit = (obj,values) =>
|
|
95
|
-
values.includes(x) ? acc : Object.assign(acc,{ [x]: obj[x] })
|
|
96
|
-
,{})
|
|
124
|
+
const omit = (obj, values) =>
|
|
125
|
+
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? acc : Object.assign(acc, { [x]: obj[x] })), {})
|
|
97
126
|
|
|
98
127
|
// Utility function for picking certain keys from an object
|
|
99
|
-
const pick = (obj,values) =>
|
|
100
|
-
values.includes(x) ? Object.assign(acc,{ [x]: obj[x] }) : acc
|
|
101
|
-
,{})
|
|
128
|
+
const pick = (obj, values) =>
|
|
129
|
+
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? Object.assign(acc, { [x]: obj[x] }) : acc), {})
|
|
102
130
|
|
|
103
131
|
// Utility function for flattening arrays
|
|
104
|
-
const flatten = arr => arr.reduce((acc,x) => acc.concat(x),[])
|
|
132
|
+
const flatten = (arr) => arr.reduce((acc, x) => acc.concat(x), [])
|
|
105
133
|
|
|
106
134
|
// Normize parameters so that they are all in standard format
|
|
107
|
-
const normalizeParams = params
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
135
|
+
const normalizeParams = (params) =>
|
|
136
|
+
params.reduce(
|
|
137
|
+
(acc, p) =>
|
|
138
|
+
Array.isArray(p)
|
|
139
|
+
? acc.concat([normalizeParams(p)])
|
|
140
|
+
: (Object.keys(p).length === 2 && p.name && typeof p.value !== 'undefined') ||
|
|
141
|
+
(Object.keys(p).length === 3 && p.name && typeof p.value !== 'undefined' && p.cast)
|
|
142
|
+
? acc.concat(p)
|
|
143
|
+
: acc.concat(splitParams(p)),
|
|
144
|
+
[]
|
|
145
|
+
) // end reduce
|
|
115
146
|
|
|
116
147
|
// Prepare parameters
|
|
117
|
-
const processParams = (engine,sql,sqlParams,params,formatOptions,row=0) => {
|
|
148
|
+
const processParams = (engine, sql, sqlParams, params, formatOptions, row = 0) => {
|
|
118
149
|
return {
|
|
119
|
-
processedParams: params.reduce((acc,p) => {
|
|
150
|
+
processedParams: params.reduce((acc, p) => {
|
|
120
151
|
if (Array.isArray(p)) {
|
|
121
|
-
const result = processParams(engine,sql,sqlParams,p,formatOptions,row)
|
|
122
|
-
if (row === 0) {
|
|
152
|
+
const result = processParams(engine, sql, sqlParams, p, formatOptions, row)
|
|
153
|
+
if (row === 0) {
|
|
154
|
+
sql = result.escapedSql
|
|
155
|
+
row++
|
|
156
|
+
}
|
|
123
157
|
return acc.concat([result.processedParams])
|
|
124
158
|
} else if (sqlParams[p.name]) {
|
|
125
159
|
if (sqlParams[p.name].type === 'n_ph') {
|
|
126
160
|
if (p.cast) {
|
|
127
161
|
const regex = new RegExp(':' + p.name + '\\b', 'g')
|
|
128
|
-
sql = sql.replace(
|
|
129
|
-
regex,
|
|
130
|
-
engine === 'pg'
|
|
131
|
-
? `:${p.name}::${p.cast}`
|
|
132
|
-
: `CAST(:${p.name} AS ${p.cast})`
|
|
133
|
-
)
|
|
162
|
+
sql = sql.replace(regex, engine === 'pg' ? `:${p.name}::${p.cast}` : `CAST(:${p.name} AS ${p.cast})`)
|
|
134
163
|
}
|
|
135
|
-
acc.push(formatParam(p.name,p.value,formatOptions))
|
|
164
|
+
acc.push(formatParam(p.name, p.value, formatOptions))
|
|
136
165
|
} else if (row === 0) {
|
|
137
166
|
const regex = new RegExp('::' + p.name + '\\b', 'g')
|
|
138
167
|
sql = sql.replace(regex, sqlString.escapeId(p.value))
|
|
@@ -141,84 +170,91 @@ const processParams = (engine,sql,sqlParams,params,formatOptions,row=0) => {
|
|
|
141
170
|
} else {
|
|
142
171
|
return acc
|
|
143
172
|
}
|
|
144
|
-
},[]),
|
|
173
|
+
}, []),
|
|
145
174
|
escapedSql: sql
|
|
146
175
|
}
|
|
147
176
|
}
|
|
148
177
|
|
|
149
178
|
// Converts parameter to the name/value format
|
|
150
|
-
const formatParam = (n,v,formatOptions) => formatType(n,v,getType(v),getTypeHint(v),formatOptions)
|
|
179
|
+
const formatParam = (n, v, formatOptions) => formatType(n, v, getType(v), getTypeHint(v), formatOptions)
|
|
151
180
|
|
|
152
181
|
// Converts object params into name/value format
|
|
153
|
-
const splitParams = p => Object.keys(p).reduce((arr,x) =>
|
|
154
|
-
arr.concat({ name: x, value: p[x] }),[])
|
|
182
|
+
const splitParams = (p) => Object.keys(p).reduce((arr, x) => arr.concat({ name: x, value: p[x] }), [])
|
|
155
183
|
|
|
156
184
|
// Get all the sql parameters and assign them types
|
|
157
|
-
const getSqlParams = sql => {
|
|
185
|
+
const getSqlParams = (sql) => {
|
|
158
186
|
// TODO: probably need to remove comments from the sql
|
|
159
187
|
// TODO: placeholders?
|
|
160
188
|
// sql.match(/\:{1,2}\w+|\?+/g).map((p,i) => {
|
|
161
|
-
return (sql.match(/:{1,2}\w+/g) || [])
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
189
|
+
return (sql.match(/:{1,2}\w+/g) || [])
|
|
190
|
+
.map((p) => {
|
|
191
|
+
// TODO: future support for placeholder parsing?
|
|
192
|
+
// return p === '??' ? { type: 'id' } // identifier
|
|
193
|
+
// : p === '?' ? { type: 'ph', label: '__d'+i } // placeholder
|
|
194
|
+
return p.startsWith('::')
|
|
195
|
+
? { type: 'n_id', label: p.substr(2) } // named id
|
|
196
|
+
: { type: 'n_ph', label: p.substr(1) } // named placeholder
|
|
197
|
+
})
|
|
198
|
+
.reduce((acc, x) => {
|
|
199
|
+
return Object.assign(acc, {
|
|
170
200
|
[x.label]: {
|
|
171
201
|
type: x.type
|
|
172
202
|
}
|
|
173
|
-
}
|
|
174
|
-
)
|
|
175
|
-
},{}) // end reduce
|
|
203
|
+
})
|
|
204
|
+
}, {}) // end reduce
|
|
176
205
|
}
|
|
177
206
|
|
|
178
207
|
// Gets the value type and returns the correct value field name
|
|
179
208
|
// TODO: Support more types as the are released
|
|
180
|
-
const getType = val =>
|
|
181
|
-
typeof val === 'string'
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
209
|
+
const getType = (val) =>
|
|
210
|
+
typeof val === 'string'
|
|
211
|
+
? 'stringValue'
|
|
212
|
+
: typeof val === 'boolean'
|
|
213
|
+
? 'booleanValue'
|
|
214
|
+
: typeof val === 'number' && parseInt(val) === val
|
|
215
|
+
? 'longValue'
|
|
216
|
+
: typeof val === 'number' && parseFloat(val) === val
|
|
217
|
+
? 'doubleValue'
|
|
218
|
+
: val === null
|
|
219
|
+
? 'isNull'
|
|
220
|
+
: isDate(val)
|
|
221
|
+
? 'stringValue'
|
|
222
|
+
: Buffer.isBuffer(val)
|
|
223
|
+
? 'blobValue'
|
|
224
|
+
: // : Array.isArray(val) ? 'arrayValue' This doesn't work yet
|
|
225
|
+
// TODO: there is a 'structValue' now for postgres
|
|
226
|
+
typeof val === 'object' && Object.keys(val).length === 1 && supportedTypes.includes(Object.keys(val)[0])
|
|
227
|
+
? null
|
|
228
|
+
: undefined
|
|
194
229
|
|
|
195
230
|
// Hint to specify the underlying object type for data type mapping
|
|
196
|
-
const getTypeHint = val =>
|
|
197
|
-
isDate(val) ? 'TIMESTAMP' : undefined
|
|
231
|
+
const getTypeHint = (val) => (isDate(val) ? 'TIMESTAMP' : undefined)
|
|
198
232
|
|
|
199
|
-
const isDate = val =>
|
|
200
|
-
val instanceof Date
|
|
233
|
+
const isDate = (val) => val instanceof Date
|
|
201
234
|
|
|
202
235
|
// Creates a standard Data API parameter using the supplied inputs
|
|
203
|
-
const formatType = (name,value,type,typeHint,formatOptions) => {
|
|
236
|
+
const formatType = (name, value, type, typeHint, formatOptions) => {
|
|
204
237
|
return Object.assign(
|
|
205
238
|
typeHint != null ? { name, typeHint } : { name },
|
|
206
|
-
type === null
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
type === null
|
|
240
|
+
? { value }
|
|
241
|
+
: {
|
|
242
|
+
value: {
|
|
243
|
+
[type ? type : error(`'${name}' is an invalid type`)]:
|
|
244
|
+
type === 'isNull'
|
|
245
|
+
? true
|
|
246
|
+
: isDate(value)
|
|
247
|
+
? formatToTimeStamp(value, formatOptions && formatOptions.treatAsLocalDate)
|
|
248
|
+
: value
|
|
249
|
+
}
|
|
250
|
+
}
|
|
215
251
|
)
|
|
216
252
|
} // end formatType
|
|
217
253
|
|
|
218
254
|
// Formats the (UTC) date to the AWS accepted YYYY-MM-DD HH:MM:SS[.FFF] format
|
|
219
255
|
// See https://docs.aws.amazon.com/rdsdataservice/latest/APIReference/API_SqlParameter.html
|
|
220
256
|
const formatToTimeStamp = (date, treatAsLocalDate) => {
|
|
221
|
-
const pad = (val,num=2) => '0'.repeat(num-(val + '').length) + val
|
|
257
|
+
const pad = (val, num = 2) => '0'.repeat(num - (val + '').length) + val
|
|
222
258
|
|
|
223
259
|
const year = treatAsLocalDate ? date.getFullYear() : date.getUTCFullYear()
|
|
224
260
|
const month = (treatAsLocalDate ? date.getMonth() : date.getUTCMonth()) + 1 // Convert to human month
|
|
@@ -229,7 +265,7 @@ const formatToTimeStamp = (date, treatAsLocalDate) => {
|
|
|
229
265
|
const seconds = treatAsLocalDate ? date.getSeconds() : date.getUTCSeconds()
|
|
230
266
|
const ms = treatAsLocalDate ? date.getMilliseconds() : date.getUTCMilliseconds()
|
|
231
267
|
|
|
232
|
-
const fraction = ms <= 0 ? '' : `.${pad(ms,3)}`
|
|
268
|
+
const fraction = ms <= 0 ? '' : `.${pad(ms, 3)}`
|
|
233
269
|
|
|
234
270
|
return `${year}-${pad(month)}-${pad(day)} ${pad(hours)}:${pad(minutes)}:${pad(seconds)}${fraction}`
|
|
235
271
|
}
|
|
@@ -237,14 +273,15 @@ const formatToTimeStamp = (date, treatAsLocalDate) => {
|
|
|
237
273
|
// Converts the string value to a Date object.
|
|
238
274
|
// If standard TIMESTAMP format (YYYY-MM-DD[ HH:MM:SS[.FFF]]) without TZ + treatAsLocalDate=false then assume UTC Date
|
|
239
275
|
// In all other cases convert value to datetime as-is (also values with TZ info)
|
|
240
|
-
const formatFromTimeStamp = (value,treatAsLocalDate) =>
|
|
241
|
-
!treatAsLocalDate && /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}:\d{2}(\.\d
|
|
242
|
-
new Date(value + 'Z')
|
|
243
|
-
new Date(value)
|
|
276
|
+
const formatFromTimeStamp = (value, treatAsLocalDate) =>
|
|
277
|
+
!treatAsLocalDate && /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}:\d{2}(\.\d+)?)?$/.test(value)
|
|
278
|
+
? new Date(value + 'Z')
|
|
279
|
+
: new Date(value)
|
|
244
280
|
|
|
245
281
|
// Formats the results of a query response
|
|
246
282
|
const formatResults = (
|
|
247
|
-
{
|
|
283
|
+
{
|
|
284
|
+
// destructure results
|
|
248
285
|
columnMetadata, // ONLY when hydrate or includeResultMetadata is true
|
|
249
286
|
numberOfRecordsUpdated, // ONLY for executeStatement method
|
|
250
287
|
records, // ONLY for executeStatement method
|
|
@@ -254,92 +291,104 @@ const formatResults = (
|
|
|
254
291
|
hydrate,
|
|
255
292
|
includeMeta,
|
|
256
293
|
formatOptions
|
|
257
|
-
) =>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
records
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
294
|
+
) =>
|
|
295
|
+
Object.assign(
|
|
296
|
+
includeMeta ? { columnMetadata } : {},
|
|
297
|
+
numberOfRecordsUpdated !== undefined && !records ? { numberOfRecordsUpdated } : {},
|
|
298
|
+
records
|
|
299
|
+
? {
|
|
300
|
+
records: formatRecords(records, columnMetadata, hydrate, formatOptions)
|
|
301
|
+
}
|
|
302
|
+
: {},
|
|
303
|
+
updateResults ? { updateResults: formatUpdateResults(updateResults) } : {},
|
|
304
|
+
generatedFields && generatedFields.length > 0 ? { insertId: generatedFields[0].longValue } : {}
|
|
305
|
+
)
|
|
267
306
|
|
|
268
307
|
// Processes records and either extracts Typed Values into an array, or
|
|
269
308
|
// object with named column labels
|
|
270
|
-
const formatRecords = (recs,columns,hydrate,formatOptions) => {
|
|
271
|
-
|
|
309
|
+
const formatRecords = (recs, columns, hydrate, formatOptions) => {
|
|
272
310
|
// Create map for efficient value parsing
|
|
273
|
-
let fmap =
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// Map over all the records (rows)
|
|
279
|
-
return recs ? recs.map(rec => {
|
|
280
|
-
|
|
281
|
-
// Reduce each field in the record (row)
|
|
282
|
-
return rec.reduce((acc,field,i) => {
|
|
283
|
-
|
|
284
|
-
// If the field is null, always return null
|
|
285
|
-
if (field.isNull === true) {
|
|
286
|
-
return hydrate ? // object if hydrate, else array
|
|
287
|
-
Object.assign(acc,{ [fmap[i].label]: null })
|
|
288
|
-
: acc.concat(null)
|
|
289
|
-
|
|
290
|
-
// If the field is mapped, return the mapped field
|
|
291
|
-
} else if (fmap[i] && fmap[i].field) {
|
|
292
|
-
const value = formatRecordValue(field[fmap[i].field],fmap[i].typeName,formatOptions)
|
|
293
|
-
return hydrate ? // object if hydrate, else array
|
|
294
|
-
Object.assign(acc,{ [fmap[i].label]: value })
|
|
295
|
-
: acc.concat(value)
|
|
296
|
-
|
|
297
|
-
// Else discover the field type
|
|
298
|
-
} else {
|
|
299
|
-
|
|
300
|
-
// Look for non-null fields
|
|
301
|
-
Object.keys(field).map(type => {
|
|
302
|
-
if (type !== 'isNull' && field[type] !== null) {
|
|
303
|
-
fmap[i]['field'] = type
|
|
304
|
-
}
|
|
311
|
+
let fmap =
|
|
312
|
+
recs && recs[0]
|
|
313
|
+
? recs[0].map((x, i) => {
|
|
314
|
+
return Object.assign({}, columns ? { label: columns[i].label, typeName: columns[i].typeName } : {}) // add column label and typeName
|
|
305
315
|
})
|
|
316
|
+
: {}
|
|
306
317
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
318
|
+
// Map over all the records (rows)
|
|
319
|
+
return recs
|
|
320
|
+
? recs.map((rec) => {
|
|
321
|
+
// Reduce each field in the record (row)
|
|
322
|
+
return rec.reduce(
|
|
323
|
+
(acc, field, i) => {
|
|
324
|
+
// If the field is null, always return null
|
|
325
|
+
if (field.isNull === true) {
|
|
326
|
+
return hydrate // object if hydrate, else array
|
|
327
|
+
? Object.assign(acc, { [fmap[i].label]: null })
|
|
328
|
+
: acc.concat(null)
|
|
329
|
+
|
|
330
|
+
// If the field is mapped, return the mapped field
|
|
331
|
+
} else if (fmap[i] && fmap[i].field) {
|
|
332
|
+
const value = formatRecordValue(field[fmap[i].field], fmap[i].typeName, formatOptions)
|
|
333
|
+
return hydrate // object if hydrate, else array
|
|
334
|
+
? Object.assign(acc, { [fmap[i].label]: value })
|
|
335
|
+
: acc.concat(value)
|
|
336
|
+
|
|
337
|
+
// Else discover the field type
|
|
338
|
+
} else {
|
|
339
|
+
// Look for non-null fields
|
|
340
|
+
Object.keys(field).map((type) => {
|
|
341
|
+
if (type !== 'isNull' && field[type] !== null) {
|
|
342
|
+
fmap[i]['field'] = type
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
// Return the mapped field (this should NEVER be null)
|
|
347
|
+
const value = formatRecordValue(field[fmap[i].field], fmap[i].typeName, formatOptions)
|
|
348
|
+
return hydrate // object if hydrate, else array
|
|
349
|
+
? Object.assign(acc, { [fmap[i].label]: value })
|
|
350
|
+
: acc.concat(value)
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
hydrate ? {} : []
|
|
354
|
+
) // init object if hydrate, else init array
|
|
355
|
+
})
|
|
356
|
+
: [] // empty record set returns an array
|
|
316
357
|
} // end formatRecords
|
|
317
358
|
|
|
318
359
|
// Format record value based on its value, the database column's typeName and the formatting options
|
|
319
|
-
const formatRecordValue = (value,typeName,formatOptions) =>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
360
|
+
const formatRecordValue = (value, typeName, formatOptions) => {
|
|
361
|
+
if (
|
|
362
|
+
formatOptions &&
|
|
363
|
+
formatOptions.deserializeDate &&
|
|
364
|
+
['DATE', 'DATETIME', 'TIMESTAMP', 'TIMESTAMPTZ', 'TIMESTAMP WITH TIME ZONE'].includes(typeName.toUpperCase())
|
|
365
|
+
) {
|
|
366
|
+
return formatFromTimeStamp(
|
|
367
|
+
value,
|
|
368
|
+
(formatOptions && formatOptions.treatAsLocalDate) || typeName === 'TIMESTAMP WITH TIME ZONE'
|
|
369
|
+
)
|
|
370
|
+
} else if (typeName === 'JSON') {
|
|
371
|
+
return JSON.parse(value)
|
|
372
|
+
} else {
|
|
373
|
+
return value
|
|
374
|
+
}
|
|
375
|
+
}
|
|
323
376
|
|
|
324
377
|
// Format updateResults and extract insertIds
|
|
325
|
-
const formatUpdateResults = res =>
|
|
326
|
-
|
|
327
|
-
{ insertId: x.generatedFields[0].longValue } : {}
|
|
328
|
-
})
|
|
329
|
-
|
|
378
|
+
const formatUpdateResults = (res) =>
|
|
379
|
+
res.map((x) => {
|
|
380
|
+
return x.generatedFields && x.generatedFields.length > 0 ? { insertId: x.generatedFields[0].longValue } : {}
|
|
381
|
+
})
|
|
330
382
|
|
|
331
383
|
// Merge configuration data with supplied arguments
|
|
332
|
-
const mergeConfig = (initialConfig,args) =>
|
|
333
|
-
Object.assign(initialConfig,args)
|
|
334
|
-
|
|
335
|
-
|
|
384
|
+
const mergeConfig = (initialConfig, args) => Object.assign(initialConfig, args)
|
|
336
385
|
|
|
337
386
|
/********************************************************************/
|
|
338
387
|
/** QUERY MANAGEMENT **/
|
|
339
388
|
/********************************************************************/
|
|
340
389
|
|
|
341
390
|
// Query function (use standard form for `this` context)
|
|
342
|
-
const query = async function(config
|
|
391
|
+
const query = async function (config, ..._args) {
|
|
343
392
|
// Flatten array if nested arrays (fixes #30)
|
|
344
393
|
const args = Array.isArray(_args[0]) ? flatten(_args) : _args
|
|
345
394
|
|
|
@@ -348,92 +397,80 @@ const query = async function(config,..._args) {
|
|
|
348
397
|
const sqlParams = getSqlParams(sql)
|
|
349
398
|
|
|
350
399
|
// Parse hydration setting
|
|
351
|
-
const hydrateColumnNames = parseHydrate(config,args)
|
|
400
|
+
const hydrateColumnNames = parseHydrate(config, args)
|
|
352
401
|
|
|
353
402
|
// Parse data format settings
|
|
354
|
-
const formatOptions = parseFormatOptions(config,args)
|
|
403
|
+
const formatOptions = parseFormatOptions(config, args)
|
|
355
404
|
|
|
356
405
|
// Parse and normalize parameters
|
|
357
406
|
const parameters = normalizeParams(parseParams(args))
|
|
358
407
|
|
|
359
408
|
// Process parameters and escape necessary SQL
|
|
360
|
-
const { processedParams,escapedSql } = processParams(config.engine,sql,sqlParams,parameters,formatOptions)
|
|
409
|
+
const { processedParams, escapedSql } = processParams(config.engine, sql, sqlParams, parameters, formatOptions)
|
|
361
410
|
|
|
362
411
|
// Determine if this is a batch request
|
|
363
|
-
const isBatch = processedParams.length > 0
|
|
364
|
-
&& Array.isArray(processedParams[0])
|
|
412
|
+
const isBatch = processedParams.length > 0 && Array.isArray(processedParams[0])
|
|
365
413
|
|
|
366
414
|
// Create/format the parameters
|
|
367
415
|
const params = Object.assign(
|
|
368
|
-
prepareParams(config,args),
|
|
416
|
+
prepareParams(config, args),
|
|
369
417
|
{
|
|
370
|
-
database: parseDatabase(config,args), // add database
|
|
418
|
+
database: parseDatabase(config, args), // add database
|
|
371
419
|
sql: escapedSql // add escaped sql statement
|
|
372
420
|
},
|
|
373
421
|
// Only include parameters if they exist
|
|
374
|
-
processedParams.length > 0
|
|
375
|
-
// Batch statements require parameterSets instead of parameters
|
|
376
|
-
|
|
422
|
+
processedParams.length > 0
|
|
423
|
+
? // Batch statements require parameterSets instead of parameters
|
|
424
|
+
{ [isBatch ? 'parameterSets' : 'parameters']: processedParams }
|
|
425
|
+
: {},
|
|
377
426
|
// Force meta data if set and not a batch
|
|
378
427
|
hydrateColumnNames && !isBatch ? { includeResultMetadata: true } : {},
|
|
379
428
|
// If a transactionId is passed, overwrite any manual input
|
|
380
429
|
config.transactionId ? { transactionId: config.transactionId } : {}
|
|
381
430
|
) // end params
|
|
382
431
|
|
|
383
|
-
try {
|
|
432
|
+
try {
|
|
433
|
+
// attempt to run the query
|
|
384
434
|
|
|
385
435
|
// Capture the result for debugging
|
|
386
|
-
let result = await (isBatch
|
|
387
|
-
|
|
436
|
+
let result = await (isBatch
|
|
437
|
+
? config.methods.batchExecuteStatement(params)
|
|
438
|
+
: config.methods.executeStatement(params))
|
|
388
439
|
|
|
389
440
|
// Format and return the results
|
|
390
|
-
return formatResults(
|
|
391
|
-
|
|
392
|
-
hydrateColumnNames,
|
|
393
|
-
args[0].includeResultMetadata === true,
|
|
394
|
-
formatOptions
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
} catch(e) {
|
|
398
|
-
|
|
441
|
+
return formatResults(result, hydrateColumnNames, args[0].includeResultMetadata === true, formatOptions)
|
|
442
|
+
} catch (e) {
|
|
399
443
|
if (this && this.rollback) {
|
|
400
|
-
let rollback = await config.
|
|
401
|
-
pick(params,['resourceArn','secretArn','transactionId'])
|
|
402
|
-
)
|
|
444
|
+
let rollback = await config.methods.rollbackTransaction(
|
|
445
|
+
pick(params, ['resourceArn', 'secretArn', 'transactionId'])
|
|
446
|
+
)
|
|
403
447
|
|
|
404
|
-
this.rollback(e,rollback)
|
|
448
|
+
this.rollback(e, rollback)
|
|
405
449
|
}
|
|
406
450
|
// Throw the error
|
|
407
451
|
throw e
|
|
408
452
|
}
|
|
409
|
-
|
|
410
453
|
} // end query
|
|
411
454
|
|
|
412
|
-
|
|
413
|
-
|
|
414
455
|
/********************************************************************/
|
|
415
456
|
/** TRANSACTION MANAGEMENT **/
|
|
416
457
|
/********************************************************************/
|
|
417
458
|
|
|
418
459
|
// Init a transaction object and return methods
|
|
419
|
-
const transaction = (config,_args) => {
|
|
420
|
-
|
|
460
|
+
const transaction = (config, _args) => {
|
|
421
461
|
let args = typeof _args === 'object' ? [_args] : [{}]
|
|
422
462
|
let queries = [] // keep track of queries
|
|
423
463
|
let rollback = () => {} // default rollback event
|
|
424
464
|
|
|
425
|
-
const txConfig = Object.assign(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
RDS: config.RDS // reference the RDSDataService instance
|
|
432
|
-
}
|
|
433
|
-
)
|
|
465
|
+
const txConfig = Object.assign(prepareParams(config, args), {
|
|
466
|
+
database: parseDatabase(config, args), // add database
|
|
467
|
+
hydrateColumnNames: parseHydrate(config, args), // add hydrate
|
|
468
|
+
formatOptions: parseFormatOptions(config, args), // add formatOptions
|
|
469
|
+
methods: config.methods // pass the methods
|
|
470
|
+
})
|
|
434
471
|
|
|
435
472
|
return {
|
|
436
|
-
query: function(...args) {
|
|
473
|
+
query: function (...args) {
|
|
437
474
|
if (typeof args[0] === 'function') {
|
|
438
475
|
queries.push(args[0])
|
|
439
476
|
} else {
|
|
@@ -441,23 +478,26 @@ const transaction = (config,_args) => {
|
|
|
441
478
|
}
|
|
442
479
|
return this
|
|
443
480
|
},
|
|
444
|
-
rollback: function(fn) {
|
|
445
|
-
if (typeof fn === 'function') {
|
|
481
|
+
rollback: function (fn) {
|
|
482
|
+
if (typeof fn === 'function') {
|
|
483
|
+
rollback = fn
|
|
484
|
+
}
|
|
446
485
|
return this
|
|
447
486
|
},
|
|
448
|
-
commit: async function() {
|
|
487
|
+
commit: async function () {
|
|
488
|
+
return await commit(txConfig, queries, rollback)
|
|
489
|
+
}
|
|
449
490
|
}
|
|
450
491
|
}
|
|
451
492
|
|
|
452
493
|
// Commit transaction by running queries
|
|
453
|
-
const commit = async (config,queries,rollback) => {
|
|
454
|
-
|
|
494
|
+
const commit = async (config, queries, rollback) => {
|
|
455
495
|
let results = [] // keep track of results
|
|
456
496
|
|
|
457
497
|
// Start a transaction
|
|
458
|
-
const { transactionId } = await config.
|
|
459
|
-
pick(config,['resourceArn','secretArn','database'])
|
|
460
|
-
)
|
|
498
|
+
const { transactionId } = await config.methods.beginTransaction(
|
|
499
|
+
pick(config, ['resourceArn', 'secretArn', 'database'])
|
|
500
|
+
)
|
|
461
501
|
|
|
462
502
|
// Add transactionId to the config
|
|
463
503
|
let txConfig = Object.assign(config, { transactionId })
|
|
@@ -465,18 +505,18 @@ const commit = async (config,queries,rollback) => {
|
|
|
465
505
|
// Loop through queries
|
|
466
506
|
for (let i = 0; i < queries.length; i++) {
|
|
467
507
|
// Execute the queries, pass the rollback as context
|
|
468
|
-
let result = await query.apply({rollback},[config,queries[i](results[results.length-1],results)])
|
|
508
|
+
let result = await query.apply({ rollback }, [config, queries[i](results[results.length - 1], results)])
|
|
469
509
|
// Add the result to the main results accumulator
|
|
470
510
|
results.push(result)
|
|
471
511
|
}
|
|
472
512
|
|
|
473
513
|
// Commit our transaction
|
|
474
|
-
const { transactionStatus } = await txConfig.
|
|
475
|
-
pick(config,['resourceArn','secretArn','transactionId'])
|
|
476
|
-
)
|
|
514
|
+
const { transactionStatus } = await txConfig.methods.commitTransaction(
|
|
515
|
+
pick(config, ['resourceArn', 'secretArn', 'transactionId'])
|
|
516
|
+
)
|
|
477
517
|
|
|
478
518
|
// Add the transaction status to the results
|
|
479
|
-
results.push({transactionStatus})
|
|
519
|
+
results.push({ transactionStatus })
|
|
480
520
|
|
|
481
521
|
// Return the results
|
|
482
522
|
return results
|
|
@@ -502,50 +542,41 @@ const commit = async (config,queries,rollback) => {
|
|
|
502
542
|
* @param {object} [params.formatOptions] Date-related formatting options
|
|
503
543
|
* @param {boolean} [params.formatOptions.deserializeDate=false]
|
|
504
544
|
* @param {boolean} [params.formatOptions.treatAsLocalDate=false]
|
|
505
|
-
* @param {boolean} [params.keepAlive] DEPRECATED
|
|
506
|
-
* @param {boolean} [params.sslEnabled=true] DEPRECATED
|
|
507
|
-
* @param {string} [params.region] DEPRECATED
|
|
508
545
|
*
|
|
509
546
|
*/
|
|
510
|
-
const init = params => {
|
|
511
|
-
|
|
547
|
+
const init = (params) => {
|
|
512
548
|
// Set the options for the RDSDataService
|
|
513
|
-
const options =
|
|
514
|
-
|
|
515
|
-
|
|
549
|
+
const options =
|
|
550
|
+
typeof params.options === 'object'
|
|
551
|
+
? params.options
|
|
552
|
+
: params.options !== undefined
|
|
553
|
+
? error(`'options' must be an object`)
|
|
554
|
+
: {}
|
|
516
555
|
|
|
517
556
|
// Update the AWS http agent with the region
|
|
518
557
|
if (typeof params.region === 'string') {
|
|
519
558
|
options.region = params.region
|
|
520
559
|
}
|
|
521
560
|
|
|
522
|
-
// Disable ssl if wanted for local development
|
|
523
|
-
if (params.sslEnabled === false) {
|
|
524
|
-
options.sslEnabled = false
|
|
525
|
-
}
|
|
526
|
-
|
|
527
561
|
// Set the configuration for this instance
|
|
528
562
|
const config = {
|
|
529
563
|
// Require engine
|
|
530
|
-
engine: typeof params.engine === 'string' ?
|
|
531
|
-
params.engine
|
|
532
|
-
: 'mysql',
|
|
564
|
+
engine: typeof params.engine === 'string' ? params.engine : 'mysql',
|
|
533
565
|
|
|
534
566
|
// Require secretArn
|
|
535
|
-
secretArn: typeof params.secretArn === 'string' ?
|
|
536
|
-
params.secretArn
|
|
537
|
-
: error('\'secretArn\' string value required'),
|
|
567
|
+
secretArn: typeof params.secretArn === 'string' ? params.secretArn : error(`'secretArn' string value required`),
|
|
538
568
|
|
|
539
569
|
// Require resourceArn
|
|
540
|
-
resourceArn:
|
|
541
|
-
params.resourceArn
|
|
542
|
-
: error('\'resourceArn\' string value required'),
|
|
570
|
+
resourceArn:
|
|
571
|
+
typeof params.resourceArn === 'string' ? params.resourceArn : error(`'resourceArn' string value required`),
|
|
543
572
|
|
|
544
573
|
// Load optional database
|
|
545
|
-
database:
|
|
546
|
-
params.database
|
|
547
|
-
|
|
548
|
-
|
|
574
|
+
database:
|
|
575
|
+
typeof params.database === 'string'
|
|
576
|
+
? params.database
|
|
577
|
+
: params.database !== undefined
|
|
578
|
+
? error(`'database' must be a string`)
|
|
579
|
+
: undefined,
|
|
549
580
|
|
|
550
581
|
// Load optional schema DISABLED for now since this isn't used with MySQL
|
|
551
582
|
// schema: typeof params.schema === 'string' ? params.schema
|
|
@@ -553,54 +584,59 @@ const init = params => {
|
|
|
553
584
|
// : undefined,
|
|
554
585
|
|
|
555
586
|
// Set hydrateColumnNames (default to true)
|
|
556
|
-
hydrateColumnNames:
|
|
557
|
-
typeof params.hydrateColumnNames === 'boolean' ?
|
|
558
|
-
params.hydrateColumnNames : true,
|
|
587
|
+
hydrateColumnNames: typeof params.hydrateColumnNames === 'boolean' ? params.hydrateColumnNames : true,
|
|
559
588
|
|
|
560
589
|
// Value formatting options. For date the deserialization is enabled and (re)stored as UTC
|
|
561
590
|
formatOptions: {
|
|
562
591
|
deserializeDate:
|
|
563
592
|
typeof params.formatOptions === 'object' && params.formatOptions.deserializeDate === false ? false : true,
|
|
564
|
-
treatAsLocalDate:
|
|
565
|
-
typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate
|
|
593
|
+
treatAsLocalDate: typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate
|
|
566
594
|
},
|
|
567
595
|
|
|
568
|
-
// TODO: Put this in a separate module for testing?
|
|
569
596
|
// Create an instance of RDSDataService
|
|
570
|
-
|
|
571
|
-
|
|
597
|
+
client: params.wrapper ? params.wrapper(new RDSDataClient(options)) : new RDSDataClient(options),
|
|
598
|
+
|
|
599
|
+
// Create simplified v3 RDSDataClient method
|
|
600
|
+
methods: {
|
|
601
|
+
executeStatement: async (args) => {
|
|
602
|
+
const command = new ExecuteStatementCommand(
|
|
603
|
+
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
604
|
+
)
|
|
605
|
+
return await config.client.send(command)
|
|
606
|
+
},
|
|
607
|
+
batchExecuteStatement: async (args) => {
|
|
608
|
+
const command = new BatchExecuteStatementCommand(
|
|
609
|
+
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
610
|
+
)
|
|
611
|
+
return await config.client.send(command)
|
|
612
|
+
},
|
|
613
|
+
beginTransaction: async (args) => {
|
|
614
|
+
const command = new BeginTransactionCommand(
|
|
615
|
+
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
616
|
+
)
|
|
617
|
+
return await config.client.send(command)
|
|
618
|
+
},
|
|
619
|
+
commitTransaction: async (args) => {
|
|
620
|
+
const command = new CommitTransactionCommand(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args))
|
|
621
|
+
return await config.client.send(command)
|
|
622
|
+
},
|
|
623
|
+
rollbackTransaction: async (args) => {
|
|
624
|
+
const command = new RollbackTransactionCommand(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args))
|
|
625
|
+
return await config.client.send(command)
|
|
626
|
+
}
|
|
627
|
+
}
|
|
572
628
|
} // end config
|
|
573
629
|
|
|
574
630
|
// Return public methods
|
|
575
631
|
return {
|
|
576
632
|
// Query method, pass config and parameters
|
|
577
|
-
query: (...x) => query(config
|
|
633
|
+
query: (...x) => query(config, ...x),
|
|
578
634
|
// Transaction method, pass config and parameters
|
|
579
|
-
transaction: (x) => transaction(config,x),
|
|
580
|
-
|
|
581
|
-
// Export promisified versions of the RDSDataService methods
|
|
582
|
-
batchExecuteStatement: (args) =>
|
|
583
|
-
config.RDS.batchExecuteStatement(
|
|
584
|
-
mergeConfig(pick(config,['resourceArn','secretArn','database']),args)
|
|
585
|
-
).promise(),
|
|
586
|
-
beginTransaction: (args) =>
|
|
587
|
-
config.RDS.beginTransaction(
|
|
588
|
-
mergeConfig(pick(config,['resourceArn','secretArn','database']),args)
|
|
589
|
-
).promise(),
|
|
590
|
-
commitTransaction: (args) =>
|
|
591
|
-
config.RDS.commitTransaction(
|
|
592
|
-
mergeConfig(pick(config,['resourceArn','secretArn']),args)
|
|
593
|
-
).promise(),
|
|
594
|
-
executeStatement: (args) =>
|
|
595
|
-
config.RDS.executeStatement(
|
|
596
|
-
mergeConfig(pick(config,['resourceArn','secretArn','database']),args)
|
|
597
|
-
).promise(),
|
|
598
|
-
rollbackTransaction: (args) =>
|
|
599
|
-
config.RDS.rollbackTransaction(
|
|
600
|
-
mergeConfig(pick(config,['resourceArn','secretArn']),args)
|
|
601
|
-
).promise()
|
|
602
|
-
}
|
|
635
|
+
transaction: (x) => transaction(config, x),
|
|
603
636
|
|
|
637
|
+
// Export simplified v3 RDSDataClient methods
|
|
638
|
+
...config.methods
|
|
639
|
+
}
|
|
604
640
|
} // end exports
|
|
605
641
|
|
|
606
642
|
module.exports = init
|