@oracle/langchain-oracledb 0.1.1 → 0.2.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.
@@ -3,9 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OracleVS = exports.dropTablePurge = exports.createIndex = exports.createTable = exports.DistanceStrategy = exports.generateWhereClause = void 0;
6
+ exports.OracleVS = exports.dropTablePurge = exports.createIndex = exports.createTable = exports.DistanceStrategy = exports.VectorElementFormat = exports.VectorType = exports.generateWhereClause = void 0;
7
+ const node_crypto_1 = __importDefault(require("node:crypto"));
7
8
  const oracledb_1 = __importDefault(require("oracledb"));
8
- const crypto_1 = require("crypto");
9
9
  const vectorstores_1 = require("@langchain/core/vectorstores");
10
10
  const documents_1 = require("@langchain/core/documents");
11
11
  const math_1 = require("@langchain/core/utils/math");
@@ -97,6 +97,17 @@ function jsonCompare(column, op, value, bindValues) {
97
97
  const operator = op === "=" ? "==" : op; // Oracle requires '==' for equality
98
98
  return `JSON_EXISTS(metadata, '$.${column}?(@ ${operator} $${alias})' PASSING :${pos} AS "${alias}")`;
99
99
  }
100
+ exports.VectorType = {
101
+ DENSE: "DENSE",
102
+ SPARSE: "SPARSE",
103
+ };
104
+ exports.VectorElementFormat = {
105
+ INT8: "INT8",
106
+ FLOAT32: "FLOAT32",
107
+ FLOAT64: "FLOAT64",
108
+ BINARY: "BINARY",
109
+ FLEX: "*",
110
+ };
100
111
  exports.DistanceStrategy = {
101
112
  COSINE: "COSINE",
102
113
  DOT_PRODUCT: "DOT",
@@ -142,10 +153,78 @@ function quoteIdentifier(identifier) {
142
153
  const quotedParts = groups.map((g) => `"${g}"`);
143
154
  return quotedParts.join(".");
144
155
  }
145
- async function createTable(connection, tableName, embeddingDim) {
156
+ const isFloat32Array = (value) =>
157
+ // eslint-disable-next-line no-instanceof/no-instanceof
158
+ value instanceof Float32Array;
159
+ const isFloat64Array = (value) =>
160
+ // eslint-disable-next-line no-instanceof/no-instanceof
161
+ value instanceof Float64Array;
162
+ const isInt8Array = (value) =>
163
+ // eslint-disable-next-line no-instanceof/no-instanceof
164
+ value instanceof Int8Array;
165
+ const isUint8Array = (value) =>
166
+ // eslint-disable-next-line no-instanceof/no-instanceof
167
+ value instanceof Uint8Array;
168
+ const isSparseVector = (value) =>
169
+ // eslint-disable-next-line no-instanceof/no-instanceof
170
+ value instanceof oracledb_1.default.SparseVector;
171
+ function normalizeVectorTypeValue(value) {
172
+ const normalized = value?.toUpperCase();
173
+ if (normalized === exports.VectorType.DENSE || normalized === exports.VectorType.SPARSE) {
174
+ return normalized;
175
+ }
176
+ return exports.VectorType.DENSE;
177
+ }
178
+ function normalizeVectorFormat(value, vectorType) {
179
+ if (!value) {
180
+ return exports.VectorElementFormat.FLOAT32;
181
+ }
182
+ const normalizedValue = value === exports.VectorElementFormat.FLEX
183
+ ? exports.VectorElementFormat.FLEX
184
+ : value.toUpperCase();
185
+ const validFormats = Object.values(exports.VectorElementFormat);
186
+ const format = validFormats.includes(normalizedValue)
187
+ ? normalizedValue
188
+ : exports.VectorElementFormat.FLOAT32;
189
+ if (vectorType === exports.VectorType.SPARSE && format === exports.VectorElementFormat.BINARY) {
190
+ throw new Error("BINARY format is not supported for SPARSE vectors.");
191
+ }
192
+ return format;
193
+ }
194
+ function buildVectorColumnDefinition(embeddingDim, customization) {
195
+ if (embeddingDim === undefined || embeddingDim === null) {
196
+ throw new Error("Embedding dimension is required to create the vector column.");
197
+ }
198
+ if (!Number.isInteger(embeddingDim) || embeddingDim <= 0) {
199
+ throw new Error("Embedding dimension must be a positive integer.");
200
+ }
201
+ const vectorType = normalizeVectorTypeValue(customization?.vectorType);
202
+ if (vectorType === exports.VectorType.SPARSE && customization?.format === undefined) {
203
+ throw new Error("Sparse vector type requires a vector format to be specified.");
204
+ }
205
+ const format = normalizeVectorFormat(customization?.format, vectorType);
206
+ if (format === exports.VectorElementFormat.BINARY && embeddingDim % 8 !== 0) {
207
+ throw new Error("BINARY vector format requires dimensions to be a multiple of 8.");
208
+ }
209
+ const dimensionSegment = String(embeddingDim);
210
+ const formatSegment = format ?? exports.VectorElementFormat.FLOAT32;
211
+ if (vectorType === exports.VectorType.SPARSE) {
212
+ return `VECTOR(${dimensionSegment}, ${formatSegment}, SPARSE)`;
213
+ }
214
+ return `VECTOR(${dimensionSegment}, ${formatSegment})`;
215
+ }
216
+ function escapeCommentText(comment) {
217
+ return comment.replace(/'/g, "''");
218
+ }
219
+ function escapePlSqlLiteral(sql) {
220
+ return sql.replace(/'/g, "''");
221
+ }
222
+ async function createTable(connection, tableName, embeddingDim, customization) {
223
+ const tableIdentifier = quoteIdentifier(tableName);
146
224
  const colsDict = {
147
225
  id: "RAW(16) DEFAULT SYS_GUID() PRIMARY KEY",
148
- embedding: `vector(${embeddingDim}, FLOAT32)`,
226
+ external_id: `VARCHAR2(36) UNIQUE`,
227
+ embedding: buildVectorColumnDefinition(embeddingDim, customization),
149
228
  text: "CLOB",
150
229
  metadata: "JSON",
151
230
  };
@@ -153,11 +232,33 @@ async function createTable(connection, tableName, embeddingDim) {
153
232
  const ddlBody = Object.entries(colsDict)
154
233
  .map(([colName, colType]) => `${colName} ${colType}`)
155
234
  .join(", ");
156
- const ddl = `CREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)}
235
+ const createTableSql = `CREATE TABLE IF NOT EXISTS ${tableIdentifier}
157
236
  (
158
237
  ${ddlBody}
159
238
  )`;
160
- await connection.execute(ddl);
239
+ const statements = [
240
+ `EXECUTE IMMEDIATE '${escapePlSqlLiteral(createTableSql)}';`,
241
+ ];
242
+ const tableDescription = customization?.description?.trim();
243
+ if (tableDescription) {
244
+ const escapedDescription = escapeCommentText(tableDescription);
245
+ statements.push(`EXECUTE IMMEDIATE 'COMMENT ON TABLE ${tableIdentifier} IS ''${escapedDescription}''';`);
246
+ }
247
+ if (customization?.annotations) {
248
+ for (const [columnName, note] of Object.entries(customization.annotations)) {
249
+ const trimmedNote = note?.trim();
250
+ if (!trimmedNote)
251
+ continue;
252
+ const columnKey = columnName.trim().toLowerCase();
253
+ if (!(columnKey in colsDict))
254
+ continue;
255
+ const normalizedColumnIdentifier = `${tableIdentifier}.${quoteIdentifier(columnKey.toUpperCase())}`;
256
+ const escapedNote = escapeCommentText(trimmedNote);
257
+ statements.push(`EXECUTE IMMEDIATE 'COMMENT ON COLUMN ${normalizedColumnIdentifier} IS ''${escapedNote}''';`);
258
+ }
259
+ }
260
+ const block = `BEGIN\n ${statements.join("\n ")}\nEND;`;
261
+ await connection.execute(block);
161
262
  }
162
263
  catch (error) {
163
264
  handleError(error);
@@ -165,11 +266,63 @@ async function createTable(connection, tableName, embeddingDim) {
165
266
  }
166
267
  exports.createTable = createTable;
167
268
  function _getIndexName(baseName) {
168
- const uniqueId = crypto.randomUUID().replace(/-/g, "");
269
+ const uniqueId = node_crypto_1.default.randomUUID().replace(/-/g, "");
169
270
  return `${baseName}_${uniqueId}`;
170
271
  }
272
+ // Packs an array of 0/1 numbers into a big-endian Uint8Array for BINARY VECTOR storage.
273
+ function packBinaryVector(bits) {
274
+ const byteLength = Math.ceil(bits.length / 8);
275
+ const buffer = new Uint8Array(byteLength);
276
+ for (let i = 0; i < bits.length; i += 1) {
277
+ const bit = bits[i] > 0 ? 1 : 0;
278
+ if (bit === 1) {
279
+ const byteIndex = Math.floor(i / 8);
280
+ const bitOffset = 7 - (i % 8);
281
+ buffer[byteIndex] |= 1 << bitOffset;
282
+ }
283
+ }
284
+ return buffer;
285
+ }
286
+ // Converts a dense numeric vector into the typed array that matches the configured element format.
287
+ function convertDenseVectorForFormat(values, format) {
288
+ switch (format) {
289
+ case exports.VectorElementFormat.FLOAT32:
290
+ case undefined:
291
+ return new Float32Array(values);
292
+ case exports.VectorElementFormat.FLOAT64:
293
+ return new Float64Array(values);
294
+ case exports.VectorElementFormat.INT8: {
295
+ const clamped = new Int8Array(values.length);
296
+ for (let i = 0; i < values.length; i += 1) {
297
+ const rounded = Math.round(values[i]);
298
+ if (rounded < -128 || rounded > 127) {
299
+ throw new Error("INT8 vector values must be within [-128, 127].");
300
+ }
301
+ clamped[i] = rounded;
302
+ }
303
+ return clamped;
304
+ }
305
+ case exports.VectorElementFormat.BINARY:
306
+ return packBinaryVector(values);
307
+ default:
308
+ return new Float64Array(values);
309
+ }
310
+ }
311
+ function unpackBinaryVector(bytesLike, dimension) {
312
+ const bytes = isUint8Array(bytesLike)
313
+ ? bytesLike
314
+ : Uint8Array.from(bytesLike);
315
+ const result = new Float32Array(dimension);
316
+ for (let i = 0; i < dimension; i += 1) {
317
+ const byteIndex = Math.floor(i / 8);
318
+ const bitOffset = 7 - (i % 8);
319
+ const byte = bytes[byteIndex] ?? 0;
320
+ result[i] = (byte >> bitOffset) & 1;
321
+ }
322
+ return result;
323
+ }
171
324
  async function createIndex(client, vectorStore, params) {
172
- const idxType = params?.idxType || "HNSW";
325
+ const idxType = (params?.idxType ?? "HNSW").toUpperCase();
173
326
  if (idxType === "IVF") {
174
327
  await createIVFIndex(client, vectorStore, params);
175
328
  }
@@ -178,122 +331,48 @@ async function createIndex(client, vectorStore, params) {
178
331
  }
179
332
  }
180
333
  exports.createIndex = createIndex;
181
- async function createHNSWIndex(connection, oraclevs, params) {
334
+ async function createHNSWIndex(connection, oraclevs, params = {}) {
182
335
  try {
183
- const defaults = {
184
- idxName: "HNSW",
185
- idxType: "HNSW",
186
- neighbors: 32,
187
- efConstruction: 200,
188
- accuracy: 90,
189
- parallel: 8,
190
- };
191
- // if params then copy params to config
192
- const config = params
193
- ? { ...params }
194
- : { ...defaults };
195
- // Ensure compulsory parts are included
196
- const compulsoryKeys = ["idxName", "parallel"];
197
- for (const key of compulsoryKeys) {
198
- if (!(key in config)) {
199
- if (key === "idxName") {
200
- config[key] = _getIndexName(defaults[key]);
201
- }
202
- else {
203
- config[key] = defaults[key];
204
- }
205
- }
206
- }
207
- // Validate keys in config against defaults
208
- for (const key of Object.keys(config)) {
209
- if (!(key in defaults)) {
210
- throw new Error(`Invalid parameter: ${key}`);
211
- }
212
- }
213
- const { idxName } = config;
214
- const baseSql = `CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
215
- ON ${oraclevs.tableName}(embedding)
216
- ORGANIZATION INMEMORY NEIGHBOR GRAPH`;
217
- const accuracyPart = config.accuracy
218
- ? ` WITH TARGET ACCURACY ${config.accuracy}`
219
- : "";
220
- const distancePart = ` DISTANCE ${oraclevs.distanceStrategy}`;
221
- let parametersPart = "";
222
- if ("neighbors" in config && "efConstruction" in config) {
223
- parametersPart = ` parameters (type ${config.idxType},
224
- neighbors ${config.neighbors},
225
- efConstruction ${config.efConstruction})`;
226
- }
227
- else if ("neighbors" in config && !("efConstruction" in config)) {
228
- config.efConstruction = defaults.efConstruction;
229
- parametersPart = ` parameters (type ${config.idxType},
230
- neighbors ${config.neighbors},
231
- efConstruction ${config.efConstruction})`;
232
- }
233
- else if (!("neighbors" in config) && "efConstruction" in config) {
234
- config.neighbors = defaults.neighbors;
235
- parametersPart = ` parameters (type ${config.idxType},
236
- neighbors ${config.neighbors},
237
- efConstruction ${config.efConstruction})`;
238
- }
239
- const parallelPart = ` PARALLEL ${config.parallel}`;
240
- const ddl = baseSql + accuracyPart + distancePart + parametersPart + parallelPart;
336
+ const { idxName = _getIndexName("HNSW"), idxType = "HNSW", neighbors = 32, efConstruction = 200, accuracy = 90, parallel = 8, ...extra } = params;
337
+ const invalidKeys = Object.keys(extra);
338
+ if (invalidKeys.length > 0) {
339
+ throw new Error(`Invalid parameter(s): ${invalidKeys.join(", ")}`);
340
+ }
341
+ const ddl = `
342
+ CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
343
+ ON ${oraclevs.tableName}(embedding)
344
+ ORGANIZATION INMEMORY NEIGHBOR GRAPH
345
+ WITH TARGET ACCURACY ${accuracy}
346
+ DISTANCE ${oraclevs.distanceStrategy}
347
+ PARAMETERS (type ${idxType}, neighbors ${neighbors}, efConstruction ${efConstruction})
348
+ PARALLEL ${parallel}
349
+ `
350
+ .trim()
351
+ .replace(/\s+/g, " ");
241
352
  await connection.execute(ddl);
242
353
  }
243
354
  catch (error) {
244
355
  handleError(error);
245
356
  }
246
357
  }
247
- async function createIVFIndex(connection, oraclevs, params) {
358
+ async function createIVFIndex(connection, oraclevs, params = {}) {
248
359
  try {
249
- const defaults = {
250
- idxName: "IVF",
251
- idxType: "IVF",
252
- neighborPart: 32,
253
- accuracy: 90,
254
- parallel: 8,
255
- };
256
- // Combine defaults with any provided params. Note: params could contain keys not explicitly declared in IndexConfig
257
- const config = params
258
- ? { ...params }
259
- : { ...defaults };
260
- // Ensure compulsory parts are included
261
- const compulsoryKeys = ["idxName", "parallel"];
262
- for (const key of compulsoryKeys) {
263
- if (!(key in config)) {
264
- if (key === "idxName") {
265
- config[key] = _getIndexName(defaults[key]);
266
- }
267
- else {
268
- config[key] = defaults[key];
269
- }
270
- }
271
- }
272
- // Validate keys in config against defaults
273
- for (const key of Object.keys(config)) {
274
- if (!(key in defaults)) {
275
- throw new Error(`Invalid parameter: ${key}`);
276
- }
277
- }
278
- // Base SQL statement
279
- const { idxName } = config;
280
- const baseSql = `CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
281
- ON ${oraclevs.tableName}(embedding)
282
- ORGANIZATION NEIGHBOR PARTITIONS`;
283
- // Optional parts depending on parameters
284
- const accuracyPart = config.accuracy
285
- ? ` WITH TARGET ACCURACY ${config.accuracy}`
286
- : "";
287
- const distancePart = ` DISTANCE ${oraclevs.distanceStrategy}`;
288
- let parametersPart = "";
289
- if ("idxType" in config && "neighborPart" in config) {
290
- parametersPart = ` PARAMETERS (type ${config.idxType},
291
- neighbor partitions ${config.neighborPart})`;
292
- }
293
- // Always included part for parallel - assuming parallel is compulsory and always included
294
- const parallelPart = ` PARALLEL ${config.parallel}`;
295
- // Combine all parts
296
- const ddl = baseSql + accuracyPart + distancePart + parametersPart + parallelPart;
360
+ const { idxName = _getIndexName("IVF"), idxType = "IVF", neighborPart = 32, accuracy = 90, parallel = 8, ...extra } = params;
361
+ const invalidKeys = Object.keys(extra);
362
+ if (invalidKeys.length > 0) {
363
+ throw new Error(`Invalid parameter(s): ${invalidKeys.join(", ")}`);
364
+ }
365
+ const ddl = `
366
+ CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
367
+ ON ${oraclevs.tableName}(embedding)
368
+ ORGANIZATION NEIGHBOR PARTITIONS
369
+ WITH TARGET ACCURACY ${accuracy}
370
+ DISTANCE ${oraclevs.distanceStrategy}
371
+ PARAMETERS (type ${idxType}, neighbor partitions ${neighborPart})
372
+ PARALLEL ${parallel}
373
+ `
374
+ .trim()
375
+ .replace(/\s+/g, " ");
297
376
  await connection.execute(ddl);
298
377
  }
299
378
  catch (error) {
@@ -346,6 +425,30 @@ class OracleVS extends vectorstores_1.VectorStore {
346
425
  writable: true,
347
426
  value: void 0
348
427
  });
428
+ Object.defineProperty(this, "description", {
429
+ enumerable: true,
430
+ configurable: true,
431
+ writable: true,
432
+ value: void 0
433
+ });
434
+ Object.defineProperty(this, "annotations", {
435
+ enumerable: true,
436
+ configurable: true,
437
+ writable: true,
438
+ value: void 0
439
+ });
440
+ Object.defineProperty(this, "vectorType", {
441
+ enumerable: true,
442
+ configurable: true,
443
+ writable: true,
444
+ value: void 0
445
+ });
446
+ Object.defineProperty(this, "vectorFormat", {
447
+ enumerable: true,
448
+ configurable: true,
449
+ writable: true,
450
+ value: void 0
451
+ });
349
452
  Object.defineProperty(this, "query", {
350
453
  enumerable: true,
351
454
  configurable: true,
@@ -359,11 +462,120 @@ class OracleVS extends vectorstores_1.VectorStore {
359
462
  dbConfig.distanceStrategy ?? this.distanceStrategy;
360
463
  this.query = dbConfig.query;
361
464
  this.filter = dbConfig.filter;
465
+ this.description = dbConfig.description;
466
+ this.annotations = dbConfig.annotations
467
+ ? { ...dbConfig.annotations }
468
+ : undefined;
469
+ this.vectorType = dbConfig.vectorType;
470
+ this.vectorFormat = dbConfig.format;
362
471
  }
363
472
  catch (error) {
364
473
  handleError(error);
365
474
  }
366
475
  }
476
+ getActiveVectorType() {
477
+ return this.vectorType ? normalizeVectorTypeValue(this.vectorType) : exports.VectorType.DENSE;
478
+ }
479
+ ensureEmbeddingDimension() {
480
+ if (this.embeddingDimension === undefined || this.embeddingDimension === null) {
481
+ throw new Error("Embedding dimension is not initialized for this vector store.");
482
+ }
483
+ return this.embeddingDimension;
484
+ }
485
+ prepareVectorForStorage(vector) {
486
+ if (this.embeddingDimension === undefined || this.embeddingDimension === null) {
487
+ this.embeddingDimension = vector.length;
488
+ }
489
+ const dimension = this.ensureEmbeddingDimension();
490
+ const vectorType = this.getActiveVectorType();
491
+ const format = this.vectorFormat
492
+ ? normalizeVectorFormat(this.vectorFormat, vectorType)
493
+ : exports.VectorElementFormat.FLOAT32;
494
+ if (vectorType === exports.VectorType.SPARSE) {
495
+ if (vector.length !== dimension) {
496
+ throw new Error("Sparse vectors must supply full-dimension arrays for conversion.");
497
+ }
498
+ let sparseInput = vector;
499
+ if (format === exports.VectorElementFormat.INT8) {
500
+ const clamped = new Int8Array(vector.length);
501
+ for (let i = 0; i < vector.length; i += 1) {
502
+ const rounded = Math.round(vector[i]);
503
+ if (rounded < -128 || rounded > 127) {
504
+ throw new Error("INT8 sparse vector values must be within [-128, 127].");
505
+ }
506
+ clamped[i] = rounded;
507
+ }
508
+ sparseInput = clamped;
509
+ }
510
+ else if (format === exports.VectorElementFormat.FLOAT64) {
511
+ sparseInput = new Float64Array(vector);
512
+ }
513
+ else if (format === exports.VectorElementFormat.FLOAT32) {
514
+ sparseInput = new Float32Array(vector);
515
+ }
516
+ return new oracledb_1.default.SparseVector(sparseInput);
517
+ }
518
+ if (vector.length !== dimension) {
519
+ throw new Error("Vector length does not match the embedding dimension.");
520
+ }
521
+ return convertDenseVectorForFormat(vector, format);
522
+ }
523
+ prepareQueryVector(vector) {
524
+ return this.prepareVectorForStorage(vector);
525
+ }
526
+ // Preserves the native driver representation, only expanding sparse vectors to dense form.
527
+ // Preserves the native driver representation, only expanding sparse vectors to dense form.
528
+ normalizeReturnedEmbedding(value) {
529
+ if (isSparseVector(value)) {
530
+ const dense = value.dense?.();
531
+ if (!dense) {
532
+ throw new Error("Unable to expand sparse vector to dense representation.");
533
+ }
534
+ if (isFloat32Array(dense)) {
535
+ return dense;
536
+ }
537
+ if (isFloat64Array(dense)) {
538
+ return new Float32Array(dense);
539
+ }
540
+ if (isInt8Array(dense)) {
541
+ return Float32Array.from(dense);
542
+ }
543
+ if (Array.isArray(dense)) {
544
+ return Float32Array.from(dense);
545
+ }
546
+ return Float32Array.from(dense);
547
+ }
548
+ if (isFloat32Array(value) || isFloat64Array(value) || isInt8Array(value) || isUint8Array(value)) {
549
+ return value;
550
+ }
551
+ if (Array.isArray(value)) {
552
+ return value;
553
+ }
554
+ throw new Error("Received unsupported vector representation from the database.");
555
+ }
556
+ // Normalizes any supported vector representation into Float32Array so downstream math
557
+ // (MMR, distance calculations) can assume a consistent element type without forcing
558
+ // callers to give up the native shape returned by node-oracledb.
559
+ coerceEmbeddingToFloat32(value) {
560
+ if (isFloat32Array(value)) {
561
+ return value;
562
+ }
563
+ if (isFloat64Array(value)) {
564
+ return new Float32Array(value);
565
+ }
566
+ if (isInt8Array(value)) {
567
+ return Float32Array.from(value);
568
+ }
569
+ if (isUint8Array(value)) {
570
+ const byteLength = value.length;
571
+ const dimension = this.embeddingDimension ?? byteLength * 8;
572
+ return unpackBinaryVector(value, dimension);
573
+ }
574
+ if (Array.isArray(value)) {
575
+ return Float32Array.from(value);
576
+ }
577
+ return this.coerceEmbeddingToFloat32(this.normalizeReturnedEmbedding(value));
578
+ }
367
579
  async getEmbeddingDimension(query) {
368
580
  const embeddingVector = await this.embeddings.embedQuery(query);
369
581
  return embeddingVector.length;
@@ -373,7 +585,12 @@ class OracleVS extends vectorstores_1.VectorStore {
373
585
  try {
374
586
  this.embeddingDimension = await this.getEmbeddingDimension(this.query);
375
587
  connection = await this.getConnection();
376
- await createTable(connection, this.tableName, this.embeddingDimension);
588
+ await createTable(connection, this.tableName, this.embeddingDimension, {
589
+ description: this.description,
590
+ annotations: this.annotations,
591
+ vectorType: this.vectorType,
592
+ format: this.vectorFormat
593
+ });
377
594
  }
378
595
  catch (error) {
379
596
  handleError(error);
@@ -412,73 +629,79 @@ class OracleVS extends vectorstores_1.VectorStore {
412
629
  * @param vectors The vectors to add.
413
630
  * @param documents The documents associated with the vectors.
414
631
  * @param options
632
+ * ** Add { mutateOnDuplicate?: boolean } to do upsert
415
633
  * @returns Promise that resolves when the vectors have been added.
416
634
  */
417
635
  async addVectors(vectors, documents, options) {
418
636
  if (vectors.length === 0) {
419
637
  throw new Error("Vectors input null. Nothing to add...");
420
638
  }
421
- const ids = options?.ids;
639
+ const inputIds = options?.ids;
422
640
  let connection = null;
423
641
  try {
424
642
  // Ensure there are IDs for all documents
425
- if (ids !== undefined && ids.length !== vectors.length) {
643
+ if (inputIds !== undefined && inputIds.length !== vectors.length) {
426
644
  throw new Error("The number of ids must match the number of vectors provided.");
427
645
  }
428
646
  connection = await this.getConnection();
647
+ const finalIds = [];
429
648
  const binds = [];
430
649
  for (let index = 0; index < documents.length; index += 1) {
431
650
  const doc = documents[index];
432
- let processedId;
433
- if (ids) {
434
- processedId = (0, crypto_1.createHash)("sha256")
435
- .update(ids[index])
436
- .digest("hex")
437
- .substring(0, 16)
438
- .toUpperCase();
439
- }
440
- else {
441
- const sourceId = doc.metadata.id ?? crypto.randomUUID();
442
- processedId = (0, crypto_1.createHash)("sha256")
443
- .update(sourceId)
444
- .digest("hex")
445
- .substring(0, 16)
446
- .toUpperCase();
447
- }
448
- const idBuffer = Buffer.from(processedId, "hex");
449
- const bind = {
450
- id: idBuffer,
651
+ // Generate ID if not provided: Priority (Options -> Metadata -> Random)
652
+ const externalId = inputIds?.[index] ?? doc.metadata.id ?? node_crypto_1.default.randomUUID();
653
+ finalIds.push(externalId);
654
+ const preparedEmbedding = this.prepareVectorForStorage(vectors[index]);
655
+ const row = {
656
+ ext_id: externalId,
451
657
  text: doc.pageContent,
452
658
  metadata: doc.metadata,
453
- embedding: new Float32Array(vectors[index]),
659
+ embedding: preparedEmbedding,
454
660
  };
455
- binds.push(bind);
661
+ binds.push(row);
456
662
  }
457
- const sql = `INSERT INTO ${this.tableName} (id, embedding, text, metadata )
458
- VALUES (:id, :embedding, :text, :metadata)`;
459
- const options = {
663
+ const isMutateOnDuplicate = options?.mutateOnDuplicate ?? false; // Default to false for better performance
664
+ const insertSql = `
665
+ INSERT INTO ${this.tableName} (external_id, embedding, text, metadata)
666
+ VALUES (:ext_id, :embedding, :text, :metadata)`;
667
+ const mergeSql = `
668
+ MERGE INTO ${this.tableName} t
669
+ USING (
670
+ SELECT :ext_id as external_id, :embedding as embedding,
671
+ :metadata as metadata, :text as text FROM DUAL
672
+ ) s
673
+ ON (t.external_id = s.external_id)
674
+ WHEN MATCHED THEN
675
+ UPDATE SET
676
+ t.embedding = s.embedding,
677
+ t.metadata = s.metadata,
678
+ t.text = s.text
679
+ WHEN NOT MATCHED THEN
680
+ INSERT (external_id, embedding, metadata, text)
681
+ VALUES (s.external_id, s.embedding, s.metadata, s.text)`;
682
+ const sql = isMutateOnDuplicate ? mergeSql : insertSql;
683
+ const executeOptions = {
460
684
  bindDefs: {
461
- id: { type: oracledb_1.default.BUFFER, maxSize: 24 },
685
+ ext_id: { type: oracledb_1.default.STRING, maxSize: 255 },
462
686
  text: { type: oracledb_1.default.STRING, maxSize: 10000000 },
463
687
  metadata: { type: oracledb_1.default.DB_TYPE_JSON },
464
688
  embedding: { type: oracledb_1.default.DB_TYPE_VECTOR },
465
689
  },
466
690
  autoCommit: false,
467
691
  };
468
- await connection.executeMany(sql, binds, options);
692
+ await connection.executeMany(sql, binds, executeOptions);
469
693
  // Commit once all inserts are queued up
470
694
  await connection.commit();
471
- console.log("All documents have been inserted and committed.");
695
+ return finalIds;
472
696
  }
473
697
  catch (error) {
474
- handleError(error);
698
+ return handleError(error);
475
699
  }
476
700
  finally {
477
701
  if (connection) {
478
702
  await this.retConnection(connection);
479
703
  }
480
704
  }
481
- return ids;
482
705
  }
483
706
  async addDocuments(documents, options) {
484
707
  const texts = documents.map(({ pageContent }) => pageContent);
@@ -495,10 +718,9 @@ class OracleVS extends vectorstores_1.VectorStore {
495
718
  const docsScoresAndEmbeddings = [];
496
719
  let connection = null;
497
720
  try {
498
- const convertedEmbedding = new Float32Array(query);
499
- const bindValues = [convertedEmbedding];
721
+ const bindValues = [this.prepareQueryVector(query)];
500
722
  let sqlQuery = `
501
- SELECT id,
723
+ SELECT external_id,
502
724
  text,
503
725
  metadata,
504
726
  vector_distance(embedding, :1, ${this.distanceStrategy}) as distance,
@@ -523,11 +745,11 @@ class OracleVS extends vectorstores_1.VectorStore {
523
745
  const text = row[1];
524
746
  const metadata = row[2];
525
747
  const distance = row[3];
526
- const embedding = row[4];
748
+ const embedding = this.normalizeReturnedEmbedding(row[4]);
527
749
  const document = new documents_1.Document({
528
750
  pageContent: text || "",
529
751
  metadata: metadata || {},
530
- id: row[0].toString("hex"),
752
+ id: row[0],
531
753
  });
532
754
  docsScoresAndEmbeddings.push([document, distance, embedding]);
533
755
  }
@@ -586,10 +808,8 @@ class OracleVS extends vectorstores_1.VectorStore {
586
808
  // Split documents, scores, and embeddings
587
809
  const documents = docsScoresEmbeddings.map(([document]) => document);
588
810
  const scores = docsScoresEmbeddings.map(([, score]) => score);
589
- const embeddings = docsScoresEmbeddings.map(([, , embedding]) => new Float32Array(embedding));
590
- // Convert all embeddings to Float32Array for consistency
591
- const consistentEmbeddings = embeddings.map((embedding) => Array.from(embedding));
592
- const queryEmbedding = Array.from(embedding);
811
+ const consistentEmbeddings = docsScoresEmbeddings.map(([, , emb]) => Array.from(this.coerceEmbeddingToFloat32(emb)));
812
+ const queryEmbedding = Array.from(this.coerceEmbeddingToFloat32(embedding));
593
813
  // Ensure lambdaMult has a default value if not provided
594
814
  const lambdaMult = options.lambda ?? 0.5;
595
815
  const mmrSelectedIndices = (0, math_1.maximalMarginalRelevance)(queryEmbedding, consistentEmbeddings, lambdaMult, options.k);
@@ -626,7 +846,7 @@ class OracleVS extends vectorstores_1.VectorStore {
626
846
  await connection.close();
627
847
  }
628
848
  }
629
- static async fromDocuments(documents, embeddings, dbConfig) {
849
+ static async fromDocuments(documents, embeddings, dbConfig, options) {
630
850
  const { client } = dbConfig;
631
851
  if (!client)
632
852
  throw new Error("client parameter is required...");
@@ -636,7 +856,7 @@ class OracleVS extends vectorstores_1.VectorStore {
636
856
  const texts = documents.map(({ pageContent }) => pageContent);
637
857
  const vectors = await embeddings.embedDocuments(texts);
638
858
  // Assuming a method exists to handle adding texts and metadata appropriately
639
- await vss.addVectors(vectors, documents);
859
+ await vss.addVectors(vectors, documents, options);
640
860
  return vss;
641
861
  }
642
862
  catch (error) {