Skip to content

Commit 8bc9796

Browse files
committed
Add support for vector datatype with test case and formatting updates
1 parent 86ce4d4 commit 8bc9796

31 files changed

+2875
-23
lines changed

.eslintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"parserOptions": {
3-
"ecmaVersion": 2019
3+
"ecmaVersion": 2020
44
},
55
"rules": {
66
"require-await": "error",

.npmignore

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Makefile.win32
1313
/examples/*
1414
!/examples/example.js
1515
!/examples/dbconfig.js
16+
!/examples/vectortype1.js
17+
!/examples/vectortype2.js
1618
/test
1719
/build/Release/oracledb.node
1820
/build/oracledb.target.mk

binding.gyp

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
"odpi/src/dpiStringList.c",
5757
"odpi/src/dpiSubscr.c",
5858
"odpi/src/dpiUtils.c",
59-
"odpi/src/dpiVar.c"
59+
"odpi/src/dpiVar.c",
60+
"odpi/src/dpiVector.c"
6061
],
6162
"conditions" : [
6263
[

examples/vectortype1.js

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/* Copyright (c) 2024, 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+
* vectortype1.js
27+
*
28+
* DESCRIPTION
29+
* Insert and query VECTOR columns.
30+
*
31+
* On binding, typed arrays can be binded directly without providing bind information.
32+
* Normal Arrays need to use explicit type, 'DB_TYPE_VECTOR' in bind information.
33+
*
34+
* For queries, VECTOR columns are fetched as JavaScript Arrays and
35+
* Typed Arrays, default being typed arrays.
36+
*
37+
*****************************************************************************/
38+
39+
'use strict';
40+
41+
Error.stackTraceLimit = 50;
42+
43+
44+
const oracledb = require('oracledb');
45+
const dbConfig = require('./dbconfig.js');
46+
const assert = require('assert');
47+
const tableName = 'testvectornodejs1';
48+
49+
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
50+
let clientOpts = {};
51+
// On Windows and macOS Intel platforms, set the environment
52+
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
53+
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
54+
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
55+
}
56+
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
57+
}
58+
59+
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
60+
61+
// By Default typed arrays are returned. output fetch handler like
62+
// below can be used to convert to Array objects.
63+
oracledb.fetchTypeHandler = function(metadata) {
64+
if (metadata.dbType === oracledb.DB_TYPE_VECTOR) {
65+
const myConverter = (v) => {
66+
if (v !== null) {
67+
return Array.from(v);
68+
}
69+
return v;
70+
};
71+
return {converter: myConverter};
72+
}
73+
};
74+
75+
async function run() {
76+
77+
const connection = await oracledb.getConnection(dbConfig);
78+
79+
try {
80+
let result;
81+
82+
const serverVersion = connection.oracleServerVersion;
83+
if (serverVersion < 2304000000) {
84+
console.log(`DB version ${serverVersion} does not support VECTOR type`);
85+
return;
86+
}
87+
88+
console.log('Creating table');
89+
await connection.execute(`DROP TABLE if exists ${tableName}`);
90+
await connection.execute(`CREATE TABLE ${tableName} (id NUMBER,
91+
VCOL32 VECTOR(4, float32),
92+
VCOL64 VECTOR(4, float64),
93+
VCOL8 VECTOR(4, int8),
94+
VCOL VECTOR(4),
95+
VCOLFlexDouble VECTOR(*, *),
96+
VCOLFlexFloat VECTOR(*, *))`);
97+
98+
const arr = [2345.67, 12.2, -23.4, -65.2];
99+
const float64arr = new Float64Array(arr);
100+
const float32arr = new Float32Array(arr);
101+
const int8arr = new Int8Array([126, 125, -126, -23]);
102+
// Add both float32 and float64 range elements
103+
const arrFlexDouble = [2345.67, 12.666428727762776];
104+
// Add only float32 range elements
105+
const arrFlexFloat32 = [2345.67, 12.66, 43.23];
106+
107+
console.log('Inserting Vector ');
108+
result = await connection.execute(`insert into ${tableName} values(:id, :vec32, :vec64, :vec8, :vec,
109+
:vecFlexDouble, :vecFlexFloat)`,
110+
{ id: 1,
111+
vec32: float32arr,
112+
vec64: float64arr,
113+
vec8: int8arr,
114+
vec: {type: oracledb.DB_TYPE_VECTOR, val: arr},
115+
vecFlexDouble: {type: oracledb.DB_TYPE_VECTOR, val: arrFlexDouble},
116+
vecFlexFloat: {type: oracledb.DB_TYPE_VECTOR, val: arrFlexFloat32}
117+
});
118+
console.log('Rows inserted: ' + result.rowsAffected);
119+
120+
console.log('Query Results:');
121+
result = await connection.execute(
122+
`select id, VCOL32, VCOL64, VCOL8, VCOL, VCOLFlexDouble, VCOLFlexFloat from ${tableName} ORDER BY id`);
123+
console.log("Query metadata:", result.metaData);
124+
console.log("Query rows:", result.rows);
125+
const vec32 = result.rows[0].VCOL32;
126+
const vec64 = result.rows[0].VCOL64;
127+
const vec8 = result.rows[0].VCOL8;
128+
const vec = result.rows[0].VCOL;
129+
const vecFlexDouble = result.rows[0].VCOLFLEXDOUBLE;
130+
const vecFlexFloat = result.rows[0].VCOLFLEXFLOAT;
131+
132+
assert(vec32.constructor, Array);
133+
assert(vec64.constructor, Array);
134+
assert(vec.constructor, Array);
135+
assert(vecFlexDouble.constructor, Array);
136+
assert(vecFlexFloat.constructor, Array);
137+
assert(vec8.constructor, Array);
138+
139+
// Reading vector as string.
140+
console.log("Fetch Vector Column as string");
141+
const options = {};
142+
options.fetchInfo = { "VCOL64": { type: oracledb.STRING } };
143+
144+
// Reset fetch type handler for VECTOR Column
145+
// as we need to fetch VECTOR as string from driver.
146+
oracledb.fetchTypeHandler = undefined;
147+
148+
// using fetchInfo
149+
result = await connection.execute(
150+
`select VCOL64 from ${tableName}`, [], options);
151+
console.log("VCOL64 as string :", result.rows[0].VCOL64);
152+
153+
// using oracledb.fetchAsString
154+
oracledb.fetchAsString = [oracledb.DB_TYPE_VECTOR];
155+
result = await connection.execute(
156+
`select VCOL32 from ${tableName}`);
157+
console.log("VCOL32 as string :", result.rows[0].VCOL32);
158+
} catch (err) {
159+
console.error(err);
160+
} finally {
161+
if (connection) {
162+
try {
163+
await connection.close();
164+
} catch (err) {
165+
console.error(err);
166+
}
167+
}
168+
}
169+
}
170+
171+
run();

examples/vectortype2.js

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/* Copyright (c) 2024, 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+
* vectortype2.js
27+
*
28+
* DESCRIPTION
29+
* Insert data into VECTOR columns and verify vector operations.
30+
*
31+
*****************************************************************************/
32+
33+
'use strict';
34+
35+
Error.stackTraceLimit = 50;
36+
37+
38+
const oracledb = require('oracledb');
39+
const dbConfig = require('./dbconfig.js');
40+
const tableName = 'testvectornodejs2';
41+
const assert = require('assert');
42+
43+
if (process.env.NODE_ORACLEDB_DRIVER_MODE === 'thick') {
44+
let clientOpts = {};
45+
// On Windows and macOS Intel platforms, set the environment
46+
// variable NODE_ORACLEDB_CLIENT_LIB_DIR to the Oracle Client library path
47+
if (process.platform === 'win32' || (process.platform === 'darwin' && process.arch === 'x64')) {
48+
clientOpts = { libDir: process.env.NODE_ORACLEDB_CLIENT_LIB_DIR };
49+
}
50+
oracledb.initOracleClient(clientOpts); // enable node-oracledb Thick mode
51+
}
52+
53+
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
54+
55+
// By Default typed arrays are returned. output fetch handler like
56+
// below can be used to convert to Array objects.
57+
oracledb.fetchTypeHandler = function(metadata) {
58+
if (metadata.dbType === oracledb.DB_TYPE_VECTOR) {
59+
const myConverter = (v) => {
60+
if (v !== null) {
61+
return Array.from(v);
62+
}
63+
return v;
64+
};
65+
return {converter: myConverter};
66+
}
67+
};
68+
69+
async function run() {
70+
71+
const connection = await oracledb.getConnection(dbConfig);
72+
73+
try {
74+
let result;
75+
76+
const serverVersion = connection.oracleServerVersion;
77+
if (serverVersion < 2304000000) {
78+
console.log(`DB version ${serverVersion} does not support VECTOR type`);
79+
return;
80+
}
81+
82+
console.log('Creating table');
83+
await connection.execute(`DROP TABLE if exists ${tableName}`);
84+
await connection.execute(`CREATE TABLE ${tableName} (id NUMBER,
85+
embedding VECTOR(3))`);
86+
87+
let i = 0;
88+
const binds = [], num = 4;
89+
const expectedArrays = [
90+
[1, 2, 3],
91+
[4, 5, 6],
92+
[42, 52, 613],
93+
[-1, -2, -3]
94+
];
95+
for (i = 0; i < num; i++) {
96+
binds.push({id: i, vec: expectedArrays[i]});
97+
}
98+
99+
console.log('Inserting Rows ', binds);
100+
const options = {
101+
bindDefs: {
102+
id: { type: oracledb.DB_TYPE_NUMBER },
103+
vec: { type: oracledb.DB_TYPE_VECTOR }
104+
}
105+
};
106+
result = await connection.executeMany(`insert into ${tableName} values(:id, :vec)`, binds, options);
107+
console.log('Rows inserted: ' + result.rowsAffected);
108+
109+
console.log('Query Results and Verify values returned:');
110+
result = await connection.execute(
111+
`select id, embedding from ${tableName} ORDER BY id`);
112+
console.log(result.rows);
113+
for (i = 0; i < num; i++) {
114+
assert.deepStrictEqual(result.rows[i], {ID: i, EMBEDDING: expectedArrays[i]});
115+
}
116+
117+
// Calculate distance to a given embedding
118+
const vecQuery = new Float64Array([3, 1, 2]);
119+
let expectedValues = [6, 33, 377443, 50];
120+
result = await connection.execute(
121+
`select vector_distance (embedding, :1) from ${tableName}`, [vecQuery]);
122+
console.log(`Distance from Input Embedding `, [3, 1, 2]);
123+
console.log(result.rows);
124+
for (i = 0; i < num; i++) {
125+
assert.deepStrictEqual(result.rows[i], {"VECTOR_DISTANCE(EMBEDDING,:1)": expectedValues[i]});
126+
}
127+
128+
// Calculate top 3 similarity search to a given embedding
129+
result = await connection.execute(
130+
`select embedding, vector_distance (embedding, :1) as distance from ${tableName}
131+
order by distance FETCH FIRST 3 ROWS ONLY`, [vecQuery]);
132+
console.log(`Top 3 vectors for Input Embedding `, [3, 1, 2]);
133+
console.log(result.rows);
134+
assert.deepStrictEqual(result.rows[0].EMBEDDING, expectedArrays[0]);
135+
assert.deepStrictEqual(result.rows[1].EMBEDDING, expectedArrays[1]);
136+
assert.deepStrictEqual(result.rows[2].EMBEDDING, expectedArrays[3]);
137+
138+
// Nearest Neighbours (distance < 34) for a given embedding [3,1,2]
139+
// gives [1,2,3] and [4,5,6]
140+
console.log('Nearest Neighbours with distance < 34:');
141+
result = await connection.execute(
142+
`select * from ${tableName} where vector_distance (embedding, vector('[3,1,2]', 3)) < 34 `);
143+
console.log(result.rows);
144+
assert.deepStrictEqual(result.rows[0].EMBEDDING, expectedArrays[0]);
145+
assert.deepStrictEqual(result.rows[1].EMBEDDING, expectedArrays[1]);
146+
147+
// Cosine Distance
148+
result = await connection.execute(
149+
`select cosine_distance (embedding, vector('[3,1,2]', 3)) as cosdistance from ${tableName}`);
150+
expectedValues = [0.2142857142857143, 0.11673988938389968, 0.3914785344302253, 1.7857142857142856];
151+
for (i = 0; i < num; i++) {
152+
assert.deepStrictEqual(result.rows[i], {"COSDISTANCE": expectedValues[i]});
153+
}
154+
console.log(`Cosine Distance from Input Embedding `, [3, 1, 2]);
155+
console.log(result.rows);
156+
157+
// inner product
158+
result = await connection.execute(
159+
`select inner_product (embedding, vector('[3,1,2]', 3)) from ${tableName}`);
160+
console.log(`Inner product with Input Embedding `, [3, 1, 2]);
161+
console.log(result.rows);
162+
expectedValues = [11, 29, 1404, -11];
163+
for (i = 0; i < num; i++) {
164+
assert.deepStrictEqual(result.rows[i], {"INNER_PRODUCT(EMBEDDING,VECTOR('[3,1,2]',3))": expectedValues[i]});
165+
}
166+
} catch (err) {
167+
console.error(err);
168+
} finally {
169+
if (connection) {
170+
try {
171+
await connection.close();
172+
} catch (err) {
173+
console.error(err);
174+
}
175+
}
176+
}
177+
}
178+
179+
run();

lib/connection.js

+3
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ class Connection extends EventEmitter {
200200
typeof value === 'string' ||
201201
typeof value === 'boolean' ||
202202
Array.isArray(value) ||
203+
value instanceof Float32Array ||
204+
value instanceof Float64Array ||
205+
value instanceof Int8Array ||
203206
Buffer.isBuffer(value) ||
204207
util.isDate(value) ||
205208
value instanceof Lob ||

lib/constants.js

+6
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,10 @@ module.exports = {
200200
// TPC/XA two-phase commit flags
201201
TPC_END_NORMAL: 0,
202202
TPC_END_SUSPEND: 0x00100000,
203+
204+
// vector types
205+
VECTOR_FORMAT_FLOAT32: 2,
206+
VECTOR_FORMAT_FLOAT64: 3,
207+
VECTOR_FORMAT_INT8: 4,
208+
203209
};

0 commit comments

Comments
 (0)