Repository navigation
Expand file tree
/
Copy pathjsonSample.js
More file actions
385 lines (336 loc) · 12.7 KB
/
Copy pathjsonSample.js
File metadata and controls
385 lines (336 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown
* at http://oss.oracle.com/licenses/upl
*
* DESCRIPTION
* Demonstrates TimesTen JSON features using the node-oracledb driver.
*/
'use strict';
const fs = require('fs/promises');
const path = require('path');
const oracledb = require('oracledb');
const accessControl = require('./AccessControl');
oracledb.initOracleClient();
oracledb.autoCommit = true;
oracledb.fetchAsString = [oracledb.CLOB];
const TABLE_NAME = 'j_purchaseorder';
const JSON_DOC1 = '../../quickstart/sample_code/common/jsondoc1.json';
const JSON_DOC1_V2 = '../../quickstart/sample_code/common/jsondoc1-v2.json';
const JSON_DOC2 = '../../quickstart/sample_code/common/jsondoc2.json';
const ALLOWED_ARGS = ['-u', '-p', '-c', '-keep', '-help', '-h'];
const JSON_FETCH_OPTIONS = { outFormat: oracledb.OUT_FORMAT_OBJECT };
const USAGE = `
Usage: node jsonSample.js -u <userName> -p <password> [-c <connectionString>] [-keep]
Demonstrates TimesTen JSON support by creating table ${TABLE_NAME}, loading
purchase-order documents, indexing JSON data, running queries, and optionally
dropping the table at completion. Provide credentials using -u/-p and an
optional connect string with -c. Use -keep to retain the table afterwards.
`;
main();
/**
* Entrypoint for the JSON sample. Parses command-line arguments, establishes a
* database connection, runs the sample workflow, and ensures the connection is
* closed. Displays usage details when requested.
*
* @returns {Promise<void>} Resolves when the sample completes or exits.
*/
async function main() {
const args = accessControl.parseArgs(ALLOWED_ARGS);
if (!args || args['-help'] || args['-h']) {
console.log(USAGE);
return;
}
let credentials;
try {
credentials = accessControl.getCredentials('jsonSample.js');
}
catch (err) {
console.error(err);
process.exitCode = 1;
return;
}
let connection;
const keepData = Boolean(args['-keep']);
try {
console.log('Connecting');
const connectionOptions = {
user : credentials['-u'],
password : credentials['-p'],
connectString : credentials['-c']
};
connection = await oracledb.getConnection(connectionOptions);
console.log('Connected');
await runSample(connection, keepData);
console.log('Completed JSON sample operations');
if (!keepData) {
await dropTable(connection, true);
}
else {
console.log(`Leaving table ${TABLE_NAME} in place (-keep specified)`);
}
}
catch (err) {
console.error(err);
process.exitCode = 1;
}
finally {
if (connection) {
try {
await connection.close();
console.log('Connection has been released');
}
catch (closeErr) {
console.error(closeErr);
}
}
}
}
/**
* Executes the JSON sample workflow by recreating the table, loading data,
* indexing JSON content, running query demonstrations, and cleaning up.
*
* @param {oracledb.Connection} connection Active database connection.
* @param {boolean} keepData Indicates whether the table should remain after execution.
* @returns {Promise<void>} Resolves when the workflow completes.
*/
async function runSample(connection, keepData) {
await dropTable(connection, false);
await createTable(connection);
const jsonDoc1 = await readJsonDocument(JSON_DOC1);
const jsonDoc1v2 = await readJsonDocument(JSON_DOC1_V2);
const jsonDoc2 = await readJsonDocument(JSON_DOC2);
await insertPurchaseOrder(connection, '1600', jsonDoc1);
await insertPurchaseOrder(connection, '1721', jsonDoc2);
await createJsonIndex(connection);
await updatePurchaseOrder(connection, '1600', jsonDoc1v2, JSON_DOC1_V2);
await selectPurchaseOrderById(connection, '1600');
await selectPurchaseOrderById(connection, '1721');
await selectPurchaseOrdersByUser(connection, 'ABULL');
await selectPurchaseOrdersByUser(connection, 'CGIRAFFE');
await listLineItemsForOrder(connection, '1600');
}
/**
* Creates the sample purchase order table used by the JSON demonstration.
*
* @param {oracledb.Connection} connection Active database connection.
* @returns {Promise<void>} Resolves when the table has been created.
*/
async function createTable(connection) {
const sql = `CREATE TABLE ${TABLE_NAME} (
id VARCHAR2(32) NOT NULL PRIMARY KEY,
date_loaded TIMESTAMP,
po_document JSON
)`;
await connection.execute(sql);
console.log(`Table ${TABLE_NAME} created`);
}
/**
* Drops the sample table if it exists. Optionally reports when the table is
* missing instead of treating it as an error.
*
* @param {oracledb.Connection} connection Active database connection.
* @param {boolean} reportMissing When true, logs an informational message if the table does not exist.
* @returns {Promise<void>} Resolves regardless of table existence.
*/
async function dropTable(connection, reportMissing) {
const sql = `DROP TABLE ${TABLE_NAME}`;
try {
await connection.execute(sql);
console.log(`Table ${TABLE_NAME} dropped`);
}
catch (err) {
if (reportMissing) {
console.log(`Table ${TABLE_NAME} not dropped: ${err.message}`);
}
}
}
/**
* Inserts a JSON purchase order document into the sample table.
*
* @param {oracledb.Connection} connection Active database connection.
* @param {string} id Purchase order identifier.
* @param {string} jsonDocument JSON document content.
* @returns {Promise<void>} Resolves when the row has been inserted.
*/
async function insertPurchaseOrder(connection, id, jsonDocument) {
const sql = `INSERT INTO ${TABLE_NAME} (id, date_loaded, po_document)
VALUES (:id, :date_loaded, :po_document)`;
await connection.execute(sql, {
id,
date_loaded : new Date(),
po_document : jsonDocument
});
console.log(`Inserted purchase order with id ${id}`);
}
/**
* Updates an existing purchase order document, recording the update timestamp.
* Reports whether the row was updated and which document file was used.
*
* @param {oracledb.Connection} connection Active database connection.
* @param {string} id Purchase order identifier.
* @param {string} jsonDocument Updated JSON document content.
* @param {string} sourcePath Path of the JSON file used for the update.
* @returns {Promise<void>} Resolves when the update completes.
*/
async function updatePurchaseOrder(connection, id, jsonDocument, sourcePath) {
const sql = `UPDATE ${TABLE_NAME}
SET date_loaded = :date_loaded,
po_document = :po_document
WHERE id = :id`;
const result = await connection.execute(sql, {
date_loaded : new Date(),
po_document : jsonDocument,
id
});
if (result.rowsAffected > 0) {
console.log(`Updated purchase order with id ${id} using ${path.basename(sourcePath)}`);
}
else {
console.log(`No rows updated for id ${id}`);
}
}
/**
* Creates a JSON value index on the purchase order user attribute to
* accelerate user-based queries.
*
* @param {oracledb.Connection} connection Active database connection.
* @returns {Promise<void>} Resolves when the index has been created.
*/
async function createJsonIndex(connection) {
const sql = `CREATE INDEX idx_json_user ON ${TABLE_NAME} (
JSON_VALUE(po_document, '$.User' RETURNING VARCHAR2(128) ERROR ON ERROR)
)`;
await connection.execute(sql);
console.log('JSON index IDX_JSON_USER created');
}
/**
* Retrieves and prints a formatted JSON purchase order document by its
* identifier.
*
* @param {oracledb.Connection} connection Active database connection.
* @param {string} id Purchase order identifier.
* @returns {Promise<void>} Resolves when output has been written to the console.
*/
async function selectPurchaseOrderById(connection, id) {
const sql = `SELECT JSON_SERIALIZE(po_document PRETTY) AS po
FROM ${TABLE_NAME}
WHERE id = :id`;
const result = await connection.execute(sql, { id }, JSON_FETCH_OPTIONS);
if (result.rows.length === 0) {
console.log(`No purchase order found for id ${id}`);
return;
}
console.log(`Purchase order ${id}:`);
result.rows.forEach(row => console.log(row.PO));
}
/**
* Retrieves and prints all purchase orders associated with a specific user.
*
* @param {oracledb.Connection} connection Active database connection.
* @param {string} user Purchase order user value.
* @returns {Promise<void>} Resolves when output has been written to the console.
*/
async function selectPurchaseOrdersByUser(connection, user) {
const sql = `SELECT JSON_SERIALIZE(po_document PRETTY) AS po
FROM ${TABLE_NAME}
WHERE JSON_VALUE(po_document, '$.User' RETURNING VARCHAR2(128)) = :user`;
const result = await connection.execute(sql, { user }, JSON_FETCH_OPTIONS);
if (result.rows.length === 0) {
console.log(`No purchase orders found for user ${user}`);
return;
}
console.log(`Purchase orders for user ${user}:`);
result.rows.forEach(row => console.log(row.PO));
}
/**
* Lists and formats each line item within a specified purchase order,
* calculating the extended price for display.
*
* @param {oracledb.Connection} connection Active database connection.
* @param {string} id Purchase order identifier.
* @returns {Promise<void>} Resolves when the line items have been printed.
*/
async function listLineItemsForOrder(connection, id) {
const sql = `
SELECT jt.line_number AS line_number,
jt.sku AS sku,
jt.description AS description,
jt.quantity AS quantity,
jt.unit_price AS unit_price
FROM ${TABLE_NAME} po,
JSON_TABLE(po.po_document, '$.LineItems[*]'
COLUMNS (
line_number FOR ORDINALITY,
sku VARCHAR2(40) PATH '$.Part.UPCCode',
description VARCHAR2(256) PATH '$.Part.Description',
quantity NUMBER PATH '$.Quantity',
unit_price NUMBER PATH '$.Part.UnitPrice'
)
) jt
WHERE po.id = :id`;
const result = await connection.execute(sql, { id }, JSON_FETCH_OPTIONS);
if (result.rows.length === 0) {
console.log(`No line items found for purchase order ${id}`);
return;
}
console.log(`Line items for purchase order ${id}:`);
console.log('Line SKU Description Qty Unit Price Extended');
result.rows.forEach(row => {
const extended = row.UNIT_PRICE * row.QUANTITY;
const line = row.LINE_NUMBER.toString().padStart(4, ' ');
const sku = (row.SKU || '').toString().padEnd(12, ' ');
const desc = (row.DESCRIPTION || '').toString().padEnd(30, ' ');
const qty = Number(row.QUANTITY).toFixed(0).padStart(4, ' ');
const unit = Number(row.UNIT_PRICE).toFixed(2).padStart(10, ' ');
const ext = extended.toFixed(2).padStart(8, ' ');
console.log(`${line} ${sku} ${desc} ${qty} ${unit} ${ext}`);
});
}
/**
* Reads a JSON document from a relative path either under the sample's
* directory or the current working directory.
*
* @param {string} relativePath Relative path to the JSON document.
* @returns {Promise<string>} Resolves with the file contents.
*/
async function readJsonDocument(relativePath) {
const resolved = await locateJsonFile(relativePath);
console.log(`Reading ${path.basename(resolved)}`);
return fs.readFile(resolved, 'utf8');
}
/**
* Determines the absolute path to a JSON document by checking the sample
* directory first and then the current working directory.
*
* @param {string} relativePath Relative path to search for.
* @returns {Promise<string>} Resolves with the absolute path when found.
* @throws {Error} When the document cannot be located.
*/
async function locateJsonFile(relativePath) {
const candidate = path.resolve(__dirname, relativePath);
if (await fileExists(candidate)) {
return candidate;
}
const local = path.resolve(process.cwd(), path.basename(relativePath));
if (await fileExists(local)) {
return local;
}
throw new Error(`JSON document not found: ${relativePath}`);
}
/**
* Checks for file existence using read access.
*
* @param {string} filePath Absolute path to a file.
* @returns {Promise<boolean>} Resolves true when the file exists.
*/
async function fileExists(filePath) {
try {
await fs.access(filePath);
return true;
}
catch (err) {
return false;
}
}