Skip to content

Commit fa3f23b

Browse files
committed
Add copy method to DbObject support for deep copy of database objects
1 parent 08d4b6b commit fa3f23b

File tree

10 files changed

+500
-82
lines changed

10 files changed

+500
-82
lines changed

doc/src/api_manual/dbobject.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ it is to convert that DbObject to and from a JavaScript object.
196196

197197
.. versionadded:: 6.4
198198

199+
.. method:: dbObject.copy()
200+
201+
Creates a copy of the object and returns it. For Thick mode, this method
202+
requires Oracle Client libraries 12.2 or higher, if you are copying
203+
:ref:`PL/SQL collection VARRAY types <plsqlvarray>`.
204+
205+
.. versionadded:: 6.8
206+
199207
.. method:: dbObject.trim(count)
200208

201209
Trims the specified number of elements from the end of the collection.

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Common Changes
2222
#) Added support for returning maximum identifier length allowed by the
2323
database using the new property :attr:`connection.maxIdentifierLength`.
2424

25+
#) Added :meth:`~dbObject.copy` method to create a deep copy of database
26+
objects.
27+
2528
Thin Mode Changes
2629
+++++++++++++++++
2730

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ File Name | Description
7676
[`dbconfig.js`](dbconfig.js) | Common file used by examples for setting connection credentials
7777
[`dbmsoutputgetline.js`](dbmsoutputgetline.js) | Show fetching DBMS_OUTPUT by binding buffers
7878
[`dbmsoutputpipe.js`](dbmsoutputpipe.js) | Show fetching DBMS_OUTPUT by using a pipelined table
79+
[`dbobjassocarray.js`](dbobjassocarray.js) | Tests Associative Array-type dbObjects indexed by integer
7980
[`demodrop.js`](demodrop.js) | Drops the schema objects created by the examples
8081
[`demosetup.js`](demosetup.js) | Used to create common schema objects for the examples
8182
[`dmlrupd.js`](dmlrupd.js) | Example of DML RETURNING where multiple rows are matched

examples/dbobjassocarray.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/* Copyright (c) 2025, Oracle and/or its affiliates. */
2+
3+
/******************************************************************************
4+
*
5+
* This software is dual-licensed to you under the Universal Permissive License
6+
* (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
7+
* 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
8+
* either license.
9+
*
10+
* If you elect to accept the software under the Apache License, Version 2.0,
11+
* the following applies:
12+
*
13+
* Licensed under the Apache License, Version 2.0 (the "License");
14+
* you may not use this file except in compliance with the License.
15+
* You may obtain a copy of the License at
16+
*
17+
* https://www.apache.org/licenses/LICENSE-2.0
18+
*
19+
* Unless required by applicable law or agreed to in writing, software
20+
* distributed under the License is distributed on an "AS IS" BASIS,
21+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22+
* See the License for the specific language governing permissions and
23+
* limitations under the License.
24+
*
25+
* NAME
26+
* dbobjassocarray.js
27+
*
28+
* DESCRIPTION
29+
* Tests the use of Associative Array-type dbObjects indexed by integer.
30+
*
31+
*
32+
*****************************************************************************/
33+
34+
'use strict';
35+
36+
Error.stackTraceLimit = 50;
37+
38+
const oracledb = require('oracledb');
39+
const dbConfig = require('./dbconfig.js');
40+
41+
// This example runs in both node-oracledb Thin and Thick modes.
42+
//
43+
// Optionally run in node-oracledb Thick mode
44+
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
45+
46+
// Thick mode requires Oracle Client or Oracle Instant Client libraries.
47+
// On Windows and macOS you can specify the directory containing the
48+
// libraries at runtime or before Node.js starts. On other platforms (where
49+
// Oracle libraries are available) the system library search path must always
50+
// include the Oracle library path before Node.js starts. If the search path
51+
// is not correct, you will get a DPI-1047 error. See the node-oracledb
52+
// installation documentation.
53+
let clientOpts = {};
54+
// On Windows and macOS platforms, set the environment variable
55+
// NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
56+
if (process.platform === 'win32' || process.platform === 'darwin') {
57+
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
58+
}
59+
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
60+
}
61+
62+
console.log(oracledb.thin ? 'Running in thin mode' : 'Running in thick mode');
63+
64+
async function run() {
65+
66+
let connection;
67+
68+
const PKG1 = 'NODB_PKG_OBJ_1_PLS_INTEGER';
69+
const TYPE1 = 'NODB_TYP_OBJ_1_PLS_ARR_INTEGER';
70+
71+
try {
72+
// Get a non-pooled connection
73+
connection = await oracledb.getConnection(dbConfig);
74+
75+
// Create the package with the Associative Array
76+
let pkgSql =
77+
`CREATE OR REPLACE PACKAGE ${PKG1} AUTHID DEFINER AS
78+
TYPE ${TYPE1} IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
79+
FUNCTION F1 RETURN ${TYPE1};
80+
END;`;
81+
await connection.execute(pkgSql);
82+
83+
pkgSql = `CREATE OR REPLACE PACKAGE BODY ${PKG1} AS
84+
FUNCTION F1 RETURN ${TYPE1} IS
85+
R ${TYPE1};
86+
BEGIN
87+
R(5):=55;
88+
R(2):=22;
89+
R(10):=120;
90+
RETURN R;
91+
END ;
92+
END ${PKG1} ;`;
93+
await connection.execute(pkgSql);
94+
95+
const result = await connection.execute(
96+
`BEGIN
97+
:ret := ${PKG1}.f1;
98+
END;`,
99+
{
100+
ret: {
101+
dir: oracledb.BIND_OUT,
102+
type: `${PKG1}.${TYPE1}`
103+
}
104+
});
105+
const dbObj = result.outBinds.ret;
106+
const dbObj1 = dbObj.copy();
107+
console.log('The original DbObject returned is:', dbObj.toMap());
108+
console.log('The copied DbObject is:', dbObj1.toMap());
109+
console.log('The element at position 2 in the original DbObject:', dbObj[2]);
110+
console.log('The element at position 2 in the copied DbObject:', dbObj1[2]);
111+
112+
dbObj1.deleteElement(2);
113+
console.log("\nDeleted element at index 2 in the copied DbObject");
114+
console.log("Now, the copied DbObject is:", dbObj1.toMap());
115+
console.log("The original DbObject is still:", dbObj.toMap());
116+
117+
dbObj.append(67);
118+
console.log("\nAppended the value 67 to the original DbObject");
119+
console.log("Now the length of original DbObject:", dbObj.length);
120+
console.log("The original DbObject is now:", dbObj.toMap());
121+
console.log("The keys in the original DbObject are:", dbObj.getKeys());
122+
console.log("The values in the original DbObject are:", dbObj.getValues());
123+
124+
} catch (err) {
125+
console.error(err);
126+
} finally {
127+
if (connection) {
128+
try {
129+
await connection.execute(
130+
`DECLARE
131+
e_type_missing EXCEPTION;
132+
PRAGMA EXCEPTION_INIT(e_type_missing, -4043);
133+
BEGIN
134+
EXECUTE IMMEDIATE ('DROP TYPE ${TYPE1} FORCE');
135+
EXCEPTION
136+
WHEN e_type_missing THEN NULL;
137+
END;`
138+
);
139+
await connection.execute(`DROP package ${PKG1}`);
140+
await connection.close();
141+
} catch (err) {
142+
console.error(err);
143+
}
144+
}
145+
}
146+
}
147+
148+
run();

lib/dbObject.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,21 @@ class BaseDbObject {
202202
return this._attributes;
203203
}
204204

205+
//---------------------------------------------------------------------------
206+
// copy
207+
//
208+
// Creates and returns a copy of the object. The copy is independent of
209+
// the original object that was copied.
210+
//---------------------------------------------------------------------------
211+
copy() {
212+
errors.assertArgCount(arguments, 0, 0);
213+
const newObj = Object.create(this);
214+
newObj._impl = this._impl.copy();
215+
if (this.isCollection)
216+
return new Proxy(newObj, BaseDbObject._collectionProxyHandler);
217+
return newObj;
218+
}
219+
205220
//---------------------------------------------------------------------------
206221
// deleteElement()
207222
//
@@ -494,6 +509,7 @@ wrapFns(BaseDbObject.prototype,
494509
"_getAttrValue",
495510
"_setAttrValue",
496511
"append",
512+
"copy",
497513
"deleteElement",
498514
"getElement",
499515
"getKeys",

lib/thin/dbObject.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,26 @@ class ThinDbObjectImpl extends DbObjectImpl {
446446
}
447447
}
448448

449+
//---------------------------------------------------------------------------
450+
// copy
451+
//
452+
// Creates and returns a copy of the ThinDBObjectImpl object. The copy is
453+
// independent of the original object that was copied.
454+
//---------------------------------------------------------------------------
455+
copy() {
456+
// We send in marshalled data of the original object to the constructor
457+
// when we create the object copy
458+
const newObjImpl = new ThinDbObjectImpl(this._objType, this._getPackedData());
459+
460+
// Set other properties
461+
newObjImpl.toid = this.toid;
462+
newObjImpl.flags = this.flags;
463+
newObjImpl.imageFlags = this.imageFlags;
464+
newObjImpl.imageVersion = this.imageVersion;
465+
466+
return newObjImpl;
467+
}
468+
449469
//---------------------------------------------------------------------------
450470
// deleteElement()
451471
//

test/dbObject1.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ describe('200. dbObject1.js', () => {
114114
const objClass = await conn.getDbObjectClass(TYPE);
115115
const testObj = new objClass(objData);
116116
assert.equal(testObj.length, undefined);
117+
118+
// Test if copy object works fine
119+
const testObjCopy = testObj.copy();
120+
assert.strictEqual(JSON.stringify(testObj), JSON.stringify(testObjCopy));
121+
assert.equal(testObj.ID, testObjCopy.ID);
122+
assert.equal(testObj.NAME, testObjCopy.NAME);
123+
117124
const seq = 101;
118125

119126
let result = await conn.execute(sql, [seq, testObj]);

0 commit comments

Comments
 (0)