Skip to content

Commit c5f05b4

Browse files
committed
Support returning the rowid of the last row updated by a DML
1 parent 7c992ed commit c5f05b4

File tree

6 files changed

+227
-11
lines changed

6 files changed

+227
-11
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
support for Continuous Query Notification (CQN) and other subscription based
88
notifications.
99

10+
- Added
11+
[`result.lastRowid`](https://oracle.github.io/node-oracledb/doc/api.html#execlastrowid)
12+
to `execute()`. It contains the ROWID of the last row affected by an INSERT,
13+
UPDATE, DELETE or MERGE statement.
14+
1015
- Fixed various execution failures with Node.js 13.2 due to Node.js NULL pointer behavior change ([ODPI-C
1116
change](https://github.com/oracle/odpi/commit/7693865bb6a98568546aa319cc0fdb9e208cf9d4)).
1217

doc/api.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,13 @@ For installation information, see the [Node-oracledb Installation Instructions][
171171
- 4.2.6.3.8 [`resultSet`](#propexecresultset)
172172
- 4.2.6.4 [`execute()`: Callback Function](#executecallback)
173173
- 4.2.6.4.1 [`implicitResults`](#execimplicitresults)
174-
- 4.2.6.4.2 [`metaData`](#execmetadata)
174+
- 4.2.6.4.2 [`lastRowid`](#execlastrowid)
175+
- 4.2.6.4.3 [`metaData`](#execmetadata)
175176
- [`byteSize`](#execmetadata), [`dbType`](#execmetadata), [`dbTypeClass`](#execmetadata), [`dbTypeName`](#execmetadata), [`fetchType`](#execmetadata), [`name`](#execmetadata), [`nullable`](#execmetadata), [`precision`](#execmetadata), [`scale`](#execmetadata)
176-
- 4.2.6.4.3 [`outBinds`](#execoutbinds)
177-
- 4.2.6.4.4 [`resultSet`](#execresultset)
178-
- 4.2.6.4.5 [`rows`](#execrows)
179-
- 4.2.6.4.6 [`rowsAffected`](#execrowsaffected)
177+
- 4.2.6.4.4 [`outBinds`](#execoutbinds)
178+
- 4.2.6.4.5 [`resultSet`](#execresultset)
179+
- 4.2.6.4.6 [`rows`](#execrows)
180+
- 4.2.6.4.7 [`rowsAffected`](#execrowsaffected)
180181
- 4.2.7 [`executeMany()`](#executemany)
181182
- 4.2.7.1 [`executeMany()`: SQL Statement](#executemanysqlparam)
182183
- 4.2.7.2 [`executeMany()`: Binds](#executemanybinds)
@@ -3199,7 +3200,19 @@ This property was added in node-oracledb 4.0. Implicit Results
31993200
requires Oracle Database 12.1 or later, and Oracle Client 12.1 or
32003201
later.
32013202

3202-
###### <a name="execmetadata"></a> 4.2.6.4.2 `metaData`
3203+
###### <a name="execlastrowid"></a> 4.2.6.4.2 `lastRowid`
3204+
3205+
```
3206+
readonly String lastRowid
3207+
```
3208+
3209+
The ROWID of a row affected by an INSERT, UPDATE, DELETE or MERGE statement.
3210+
For other statements, or if no row was affected, it is not set. If more than
3211+
one row was affected, only the ROWID of the last row is returned.
3212+
3213+
This property was added in node-oracledb 4.2.
3214+
3215+
###### <a name="execmetadata"></a> 4.2.6.4.3 `metaData`
32033216

32043217
```
32053218
readonly Array metaData
@@ -3241,7 +3254,7 @@ such as `WHERE 1 = 0` so the database does minimal work.
32413254

32423255
See [Query Column Metadata](#querymeta) for examples.
32433256

3244-
###### <a name="execoutbinds"></a> 4.2.6.4.3 `outBinds`
3257+
###### <a name="execoutbinds"></a> 4.2.6.4.4 `outBinds`
32453258

32463259
```
32473260
Array/object outBinds
@@ -3253,7 +3266,7 @@ If [`bindParams`](#executebindParams) is passed as an array, then
32533266
object, then `outBinds` is returned as an object. If there are no OUT
32543267
or IN OUT binds, the value is undefined.
32553268

3256-
###### <a name="execresultset"></a> 4.2.6.4.4 `resultSet`
3269+
###### <a name="execresultset"></a> 4.2.6.4.5 `resultSet`
32573270

32583271
```
32593272
Object resultSet
@@ -3268,7 +3281,7 @@ When using this option, [`resultSet.close()`](#close) must be called
32683281
when the ResultSet is no longer needed. This is true whether or not
32693282
rows have been fetched from the ResultSet.
32703283

3271-
###### <a name="execrows"></a> 4.2.6.4.5 `rows`
3284+
###### <a name="execrows"></a> 4.2.6.4.6 `rows`
32723285

32733286
```
32743287
Array rows
@@ -3288,7 +3301,7 @@ The number of rows returned is limited by
32883301
`maxRows` is 0, then the number of rows is limited by Node.js memory
32893302
constraints.
32903303

3291-
###### <a name="execrowsaffected"></a> 4.2.6.4.6 `rowsAffected`
3304+
###### <a name="execrowsaffected"></a> 4.2.6.4.7 `rowsAffected`
32923305

32933306
```
32943307
Number rowsAffected

src/njsConnection.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,8 +596,11 @@ static bool njsConnection_executeAsync(njsBaton *baton)
596596
static bool njsConnection_executePostAsync(njsBaton *baton, napi_env env,
597597
napi_value *args)
598598
{
599-
napi_value result, metadata, resultSet, rowsAffected, outBinds;
599+
napi_value result, metadata, resultSet, rowsAffected, outBinds, lastRowid;
600600
napi_value implicitResults;
601+
uint32_t rowidValueLength;
602+
const char *rowidValue;
603+
dpiRowid *rowid;
601604

602605
// create constructors used for various types that might be returned
603606
if (!njsBaton_setConstructors(baton, env))
@@ -643,6 +646,21 @@ static bool njsConnection_executePostAsync(njsBaton *baton, napi_env env,
643646
"outBinds", outBinds))
644647
}
645648

649+
// for DML statements, check to see if the last rowid is available
650+
if (baton->stmtInfo.isDML) {
651+
if (dpiStmt_getLastRowid(baton->dpiStmtHandle, &rowid) < 0)
652+
return njsBaton_setErrorDPI(baton);
653+
if (rowid) {
654+
if (dpiRowid_getStringValue(rowid, &rowidValue,
655+
&rowidValueLength) < 0)
656+
return njsBaton_setErrorDPI(baton);
657+
NJS_CHECK_NAPI(env, napi_create_string_utf8(env, rowidValue,
658+
rowidValueLength, &lastRowid))
659+
NJS_CHECK_NAPI(env, napi_set_named_property(env, result,
660+
"lastRowid", lastRowid))
661+
}
662+
}
663+
646664
// check for implicit results when executing PL/SQL
647665
if (baton->stmtInfo.isPLSQL) {
648666
implicitResults = NULL;

test/lastRowid.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. */
2+
3+
/******************************************************************************
4+
*
5+
* You may not use the identified files except in compliance with the Apache
6+
* License, Version 2.0 (the "License.")
7+
*
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
*
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* NAME
19+
* 228. lastRowid.js
20+
*
21+
* DESCRIPTION
22+
* Test getting rowid of last updated row for DML statements.
23+
*
24+
*****************************************************************************/
25+
'use strict';
26+
27+
const oracledb = require('oracledb');
28+
const should = require('should');
29+
const assert = require('assert');
30+
const dbconfig = require('./dbconfig.js');
31+
const testsUtil = require('./testsUtil.js');
32+
33+
describe('228. lastRowid.js', function() {
34+
35+
let conn;
36+
const TABLE = 'nodb_lastrowid';
37+
38+
before('get connection and create table', async () => {
39+
try {
40+
conn = await oracledb.getConnection(dbconfig);
41+
let sql =
42+
`create table ${TABLE} (
43+
id number(9) not null,
44+
value varchar2(100) not null
45+
)`;
46+
let plsql = testsUtil.sqlCreateTable(TABLE, sql);
47+
await conn.execute(plsql);
48+
} catch (err) {
49+
should.not.exist(err);
50+
}
51+
});
52+
53+
after(async () => {
54+
try {
55+
let sql = `drop table ${TABLE} purge`;
56+
await conn.execute(sql);
57+
await conn.close();
58+
} catch (err) {
59+
should.not.exist(err);
60+
}
61+
});
62+
63+
it('228.1 examples', async () => {
64+
const row1 = [1, "First"];
65+
const row2 = [2, "Second"];
66+
67+
try {
68+
// insert some rows and retain the rowid of each
69+
let sql = `insert into ${TABLE} values (:1, :2)`;
70+
const result1 = await conn.execute(sql, row1);
71+
should.exist(result1.lastRowid);
72+
should.strictEqual(result1.rowsAffected, 1);
73+
const result2 = await conn.execute(sql, row2);
74+
should.exist(result2.lastRowid);
75+
should.strictEqual(result2.rowsAffected, 1);
76+
const rowid2 = result2.lastRowid;
77+
78+
// the row can be fetched with the rowid that was retained
79+
sql = `select * from ${TABLE} where rowid = :1`;
80+
let result = await conn.execute(sql, [result1.lastRowid]);
81+
should.deepEqual(result.rows[0], row1);
82+
result = await conn.execute(sql, [result2.lastRowid]);
83+
should.deepEqual(result.rows[0], row2);
84+
85+
// updating multiple rows only returns the rowid of the last updated row
86+
sql = `update ${TABLE} set value = value || ' (Modified)'`;
87+
result = await conn.execute(sql);
88+
should.strictEqual(result.lastRowid, rowid2);
89+
90+
// deleting multiple rows only returns the rowid of the last deleted row
91+
sql = `delete from ${TABLE}`;
92+
result = await conn.execute(sql);
93+
should.strictEqual(result.lastRowid, rowid2);
94+
95+
// deleting no rows results in an undefined value
96+
result = await conn.execute(sql);
97+
should.not.exist(result.lastRowid);
98+
} catch (err) {
99+
should.not.exist(err);
100+
}
101+
}); // 228.1
102+
103+
it('228.2 MERGE statement', async () => {
104+
const row1 = [11, "Eleventh"];
105+
const sqlMerge = `
106+
merge into ${TABLE} x
107+
using (select :id as tempId, :value as tempValue from dual) y
108+
on (x.id = y.tempId)
109+
when matched then
110+
update set x.value = y.tempValue
111+
when not matched then
112+
insert (x.id, x.value)
113+
values (y.tempId, y.tempValue)
114+
`;
115+
try {
116+
const result1 = await conn.execute(sqlMerge, row1, { autoCommit: true });
117+
should.exist(result1.lastRowid);
118+
should.strictEqual(result1.rowsAffected, 1);
119+
const rowID = result1.lastRowid;
120+
121+
// check it out
122+
let sql = `select * from ${TABLE} where rowid = :1`;
123+
let result2 = await conn.execute(sql, [ rowID ]);
124+
should.deepEqual(result2.rows[0], row1);
125+
126+
} catch (err) {
127+
should.not.exist(err);
128+
}
129+
}); // 228.2
130+
131+
it('228.3 Negative - not applicable to executeMany()', async () => {
132+
const rows = [
133+
{ id: 21, value: "Twenty-first" },
134+
{ id: 22, value: "Twenty-second" }
135+
];
136+
const sqlMerge = `
137+
merge into ${TABLE} x
138+
using (select :id as tempId, :value as tempValue from dual) y
139+
on (x.id = y.tempId)
140+
when matched then
141+
update set x.value = y.tempValue
142+
when not matched then
143+
insert (x.id, x.value)
144+
values (y.tempId, y.tempValue)
145+
`;
146+
const options = {
147+
autoCommit: true,
148+
bindDefs: {
149+
id: { type: oracledb.NUMBER },
150+
value: { type: oracledb.STRING, maxSize: 2000 }
151+
}
152+
};
153+
154+
try {
155+
const result1 = await conn.executeMany(sqlMerge, rows, options);
156+
should.not.exist(result1.lastRowid);
157+
should.strictEqual(result1.rowsAffected, 2);
158+
159+
let sql = `select * from ${TABLE} where id >= :1`;
160+
let result2 = await conn.execute(
161+
sql,
162+
[ rows[0].id ],
163+
{ outFormat: oracledb.OUT_FORMAT_OBJECT }
164+
);
165+
should.strictEqual(result2.rows[0].ID, rows[0].id);
166+
should.strictEqual(result2.rows[0].VALUE, rows[0].value);
167+
should.strictEqual(result2.rows[1].ID, rows[1].id);
168+
should.strictEqual(result2.rows[1].VALUE, rows[1].value);
169+
} catch (err) {
170+
should.not.exist(err);
171+
}
172+
});
173+
});

test/list.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4641,3 +4641,8 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
46414641
223. accessPropertiesOnClosedObjects.js
46424642
223.1 access properties of closed Connection object
46434643
223.2 access properties of closed Lob object
4644+
4645+
228. lastRowid.js
4646+
228.1 examples
4647+
228.2 MERGE statement
4648+
228.3 Negative - not applicable to executeMany()

test/opts/mocha.opts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,5 @@ test/examineOwnedProperties.js
250250
test/connectionClass.js
251251
test/callTimeout.js
252252
test/accessPropertiesOnClosedObjects.js
253+
254+
test/lastRowid.js

0 commit comments

Comments
 (0)