diff --git a/.vscode/settings.json b/.vscode/settings.json
index 16667e414..6f3bd17c1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -21,5 +21,6 @@
},
},
"java.debug.settings.onBuildFailureProceed": true,
- "java.compile.nullAnalysis.mode": "disabled"
+ "java.compile.nullAnalysis.mode": "disabled",
+ "java.configuration.updateBuildConfiguration": "interactive"
}
diff --git a/agents/semantickernel-agents-core/pom.xml b/agents/semantickernel-agents-core/pom.xml
index 9d40d75d4..cafee140a 100644
--- a/agents/semantickernel-agents-core/pom.xml
+++ b/agents/semantickernel-agents-core/pom.xml
@@ -1,5 +1,6 @@
-
+4.0.0com.microsoft.semantic-kernel
@@ -18,6 +19,18 @@
com.microsoft.semantic-kernelsemantickernel-api
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-builders
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-ai-services
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+
\ No newline at end of file
diff --git a/aiservices/google/pom.xml b/aiservices/google/pom.xml
index a205a60bb..390c3bc11 100644
--- a/aiservices/google/pom.xml
+++ b/aiservices/google/pom.xml
@@ -34,6 +34,22 @@
com.microsoft.semantic-kernelsemantickernel-api
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-builders
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-ai-services
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-localization
+ com.fasterxml.jackson.core
diff --git a/aiservices/huggingface/pom.xml b/aiservices/huggingface/pom.xml
index aaed6aad6..4ce69b952 100644
--- a/aiservices/huggingface/pom.xml
+++ b/aiservices/huggingface/pom.xml
@@ -31,6 +31,14 @@
com.microsoft.semantic-kernelsemantickernel-api
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-ai-services
+ com.azureazure-core
diff --git a/aiservices/openai/pom.xml b/aiservices/openai/pom.xml
index 4a93b679b..04b31ca39 100644
--- a/aiservices/openai/pom.xml
+++ b/aiservices/openai/pom.xml
@@ -19,6 +19,30 @@
com.microsoft.semantic-kernelsemantickernel-api
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-data
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-ai-services
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-builders
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-textembedding-services
+
+
com.azureazure-ai-openai
diff --git a/api-test/integration-tests/pom.xml b/api-test/integration-tests/pom.xml
index 433e14a8a..94e577f1c 100644
--- a/api-test/integration-tests/pom.xml
+++ b/api-test/integration-tests/pom.xml
@@ -61,6 +61,21 @@
semantickernel-data-jdbctest
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-mysql
+ test
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-hsqldb
+ test
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-sqlite
+ test
+ com.microsoft.semantic-kernelsemantickernel-data-redis
diff --git a/data/semantickernel-data-azureaisearch/pom.xml b/data/semantickernel-data-azureaisearch/pom.xml
index 74dc84bcd..038cc038c 100644
--- a/data/semantickernel-data-azureaisearch/pom.xml
+++ b/data/semantickernel-data-azureaisearch/pom.xml
@@ -21,6 +21,14 @@
com.microsoft.semantic-kernelsemantickernel-api
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-data
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+ com.azure
diff --git a/data/semantickernel-data-hsqldb/pom.xml b/data/semantickernel-data-hsqldb/pom.xml
new file mode 100644
index 000000000..42245185f
--- /dev/null
+++ b/data/semantickernel-data-hsqldb/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+ com.microsoft.semantic-kernel
+ semantickernel-parent
+ 1.4.4-RC2-SNAPSHOT
+ ../../pom.xml
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-hsqldb
+ Semantic Kernel HLSQLDB connector
+ Provides a HLSQLDB connector for the Semantic Kernel
+
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-jdbc
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-data
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-builders
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ compile
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ compile
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+
+
+
\ No newline at end of file
diff --git a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/hsqldb/HSQLDBVectorStoreQueryProvider.java b/data/semantickernel-data-hsqldb/src/main/java/com/microsoft/semantickernel/data/jdbc/hsqldb/HSQLDBVectorStoreQueryProvider.java
similarity index 100%
rename from data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/hsqldb/HSQLDBVectorStoreQueryProvider.java
rename to data/semantickernel-data-hsqldb/src/main/java/com/microsoft/semantickernel/data/jdbc/hsqldb/HSQLDBVectorStoreQueryProvider.java
diff --git a/data/semantickernel-data-jdbc/pom.xml b/data/semantickernel-data-jdbc/pom.xml
index 4cf608b5d..f1e62c6a4 100644
--- a/data/semantickernel-data-jdbc/pom.xml
+++ b/data/semantickernel-data-jdbc/pom.xml
@@ -15,9 +15,19 @@
com.microsoft.semantic-kernel
- semantickernel-api
+ semantickernel-api-data
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-builders
+ provided
-
org.slf4jslf4j-api
@@ -63,5 +73,10 @@
sqlite-jdbc3.47.0.0
+
+ com.oracle.database.jdbc
+ ojdbc11
+ 23.7.0.25.01
+
\ No newline at end of file
diff --git a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreQueryProvider.java b/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreQueryProvider.java
index ac21c61fc..2fcc2d5b7 100644
--- a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreQueryProvider.java
+++ b/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreQueryProvider.java
@@ -106,7 +106,7 @@ public JDBCVectorStoreQueryProvider(
@SuppressFBWarnings("EI_EXPOSE_REP2") @Nonnull DataSource dataSource,
@Nonnull String collectionsTable,
@Nonnull String prefixForCollectionTables,
- @Nonnull HashMap, String> supportedKeyTypes,
+ @Nonnull Map, String> supportedKeyTypes,
@Nonnull Map, String> supportedDataTypes,
@Nonnull Map, String> supportedVectorTypes) {
this.dataSource = dataSource;
@@ -220,7 +220,7 @@ public Map, String> getSupportedVectorTypes() {
@Override
public void prepareVectorStore() {
String createCollectionsTable = formatQuery(
- "CREATE TABLE IF NOT EXISTS %s (collectionId VARCHAR(255) PRIMARY KEY);",
+ "CREATE TABLE IF NOT EXISTS %s (collectionId VARCHAR(255) PRIMARY KEY)",
validateSQLidentifier(collectionsTable));
try (Connection connection = dataSource.getConnection();
@@ -701,6 +701,16 @@ public String getAnyTagEqualToFilter(AnyTagEqualToFilterClause filterClause) {
return String.format("%s LIKE ?", fieldName);
}
+ @Override
+ public VectorStoreRecordMapper getVectorStoreRecordMapper(
+ Class recordClass,
+ VectorStoreRecordDefinition recordDefinition) {
+ return JDBCVectorStoreRecordMapper.builder()
+ .withRecordClass(recordClass)
+ .withVectorStoreRecordDefinition(recordDefinition)
+ .build();
+ }
+
/**
* The builder for {@link JDBCVectorStoreQueryProvider}.
*/
@@ -755,4 +765,5 @@ public JDBCVectorStoreQueryProvider build() {
prefixForCollectionTables);
}
}
+
}
diff --git a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreRecordCollection.java b/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreRecordCollection.java
index 1c1cea5df..1d6b3e09b 100644
--- a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreRecordCollection.java
+++ b/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreRecordCollection.java
@@ -2,9 +2,6 @@
package com.microsoft.semantickernel.data.jdbc;
import com.microsoft.semantickernel.builders.SemanticKernelBuilder;
-import com.microsoft.semantickernel.data.jdbc.mysql.MySQLVectorStoreQueryProvider;
-import com.microsoft.semantickernel.data.jdbc.postgres.PostgreSQLVectorStoreQueryProvider;
-import com.microsoft.semantickernel.data.jdbc.postgres.PostgreSQLVectorStoreRecordMapper;
import com.microsoft.semantickernel.data.vectorsearch.VectorSearchResults;
import com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordCollection;
import com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordMapper;
@@ -37,10 +34,10 @@ public class JDBCVectorStoreRecordCollection
implements SQLVectorStoreRecordCollection {
private final String collectionName;
- private final VectorStoreRecordDefinition recordDefinition;
- private final VectorStoreRecordMapper vectorStoreRecordMapper;
+ protected final VectorStoreRecordDefinition recordDefinition;
+ protected final VectorStoreRecordMapper vectorStoreRecordMapper;
private final JDBCVectorStoreRecordCollectionOptions options;
- private final SQLVectorStoreQueryProvider queryProvider;
+ protected final SQLVectorStoreQueryProvider queryProvider;
/**
* Creates a new instance of the {@link JDBCVectorStoreRecordCollection}.
@@ -73,25 +70,9 @@ public JDBCVectorStoreRecordCollection(
// If mapper is not provided, set a default one
if (options.getVectorStoreRecordMapper() == null) {
- // Default mapper for PostgreSQL
- if (this.queryProvider instanceof PostgreSQLVectorStoreQueryProvider) {
- vectorStoreRecordMapper = PostgreSQLVectorStoreRecordMapper.builder()
- .withRecordClass(options.getRecordClass())
- .withVectorStoreRecordDefinition(recordDefinition)
- .build();
- // Default mapper for MySQL
- } else if (this.queryProvider instanceof MySQLVectorStoreQueryProvider) {
- vectorStoreRecordMapper = JDBCVectorStoreRecordMapper.builder()
- .withRecordClass(options.getRecordClass())
- .withVectorStoreRecordDefinition(recordDefinition)
- .build();
- // Default mapper for other databases
- } else {
- vectorStoreRecordMapper = JDBCVectorStoreRecordMapper.builder()
- .withRecordClass(options.getRecordClass())
- .withVectorStoreRecordDefinition(recordDefinition)
- .build();
- }
+ vectorStoreRecordMapper = options.getQueryProvider()
+ .getVectorStoreRecordMapper(options.getRecordClass(),
+ recordDefinition);
} else {
vectorStoreRecordMapper = options.getVectorStoreRecordMapper();
}
@@ -214,7 +195,7 @@ protected String getKeyFromRecord(Record data) {
Field keyField = data.getClass()
.getDeclaredField(recordDefinition.getKeyField().getName());
keyField.setAccessible(true);
- return (String) keyField.get(data);
+ return keyField.get(data).toString();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new SKException("Failed to get key from record", e);
}
diff --git a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/SQLVectorStoreQueryProvider.java b/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/SQLVectorStoreQueryProvider.java
index 18806c5d7..30a535fb0 100644
--- a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/SQLVectorStoreQueryProvider.java
+++ b/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/SQLVectorStoreQueryProvider.java
@@ -151,6 +151,18 @@ VectorSearchResults search(String collectionName,
VectorStoreRecordDefinition recordDefinition,
VectorStoreRecordMapper mapper);
+ /**
+ * Gets the record mapper for the given record class and definition.
+ *
+ * @param the record type
+ * @param recordClass the record class
+ * @param recordDefinition the record definition
+ * @return the record mapper that maps JDBC result sets to the given record.
+ */
+ VectorStoreRecordMapper getVectorStoreRecordMapper(
+ final Class recordClass,
+ final VectorStoreRecordDefinition recordDefinition);
+
/**
* The builder for the JDBC vector store query provider.
*/
diff --git a/data/semantickernel-data-mysql/pom.xml b/data/semantickernel-data-mysql/pom.xml
new file mode 100644
index 000000000..899f1f2aa
--- /dev/null
+++ b/data/semantickernel-data-mysql/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+ com.microsoft.semantic-kernel
+ semantickernel-parent
+ 1.4.4-RC2-SNAPSHOT
+ ../../pom.xml
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-mysql
+ Semantic Kernel MySQL connector
+ Provides a MySQL connector for the Semantic Kernel
+
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-jdbc
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-data
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-builders
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ compile
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ compile
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+
+
+
\ No newline at end of file
diff --git a/data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/mysql/MySQLVectorStoreQueryProvider.java b/data/semantickernel-data-mysql/src/main/java/com/microsoft/semantickernel/data/jdbc/mysql/MySQLVectorStoreQueryProvider.java
similarity index 100%
rename from data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/mysql/MySQLVectorStoreQueryProvider.java
rename to data/semantickernel-data-mysql/src/main/java/com/microsoft/semantickernel/data/jdbc/mysql/MySQLVectorStoreQueryProvider.java
diff --git a/data/semantickernel-data-oracle/pom.xml b/data/semantickernel-data-oracle/pom.xml
new file mode 100644
index 000000000..aa9538702
--- /dev/null
+++ b/data/semantickernel-data-oracle/pom.xml
@@ -0,0 +1,124 @@
+
+
+ 4.0.0
+
+ com.microsoft.semantic-kernel
+ semantickernel-parent
+ 1.4.4-RC2-SNAPSHOT
+ ../../pom.xml
+
+
+ semantickernel-data-oracle
+ Semantic Kernel Oracle connector
+ Provides a Oracle connector for the Semantic Kernel
+
+
+ 1.20.4
+
+
+
+
+
+ org.testcontainers
+ testcontainers-bom
+ ${testcontainers.version}
+ pom
+ import
+
+
+
+
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-data
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-exceptions
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-api-builders
+ provided
+
+
+ com.microsoft.semantic-kernel
+ semantickernel-data-jdbc
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ compile
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ compile
+
+
+ com.oracle.database.jdbc
+ ojdbc11
+ 23.7.0.25.01
+
+
+ com.oracle.database.jdbc
+ ojdbc-provider-jackson-oson
+ 1.0.4
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ oracle-free
+ test
+
+
+
+
+
+ org.codehaus.mojo
+ animal-sniffer-maven-plugin
+
+
+ android
+ test
+
+ check
+
+
+
+
+ true
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleDataTypesMapping.java b/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleDataTypesMapping.java
new file mode 100644
index 000000000..1d5db527f
--- /dev/null
+++ b/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleDataTypesMapping.java
@@ -0,0 +1,93 @@
+/*
+ ** Oracle Database Vector Store Connector for Semantic Kernel (Java)
+ **
+ ** Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
+ **
+ ** The MIT License (MIT)
+ **
+ ** Permission is hereby granted, free of charge, to any person obtaining a copy
+ ** of this software and associated documentation files (the "Software"), to
+ ** deal in the Software without restriction, including without limitation the
+ ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ ** sell copies of the Software, and to permit persons to whom the Software is
+ ** furnished to do so, subject to the following conditions:
+ **
+ ** The above copyright notice and this permission notice shall be included in
+ ** all copies or substantial portions of the Software.
+ **
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ ** IN THE SOFTWARE.
+ */
+package com.microsoft.semantickernel.data.jdbc.oracle;
+
+/**
+ * Defines oracle database type constants for supported java types.
+ */
+public class OracleDataTypesMapping {
+
+ /**
+ * Oracle database type used when strings are mapped to VARCHAR
+ */
+ public static final String STRING_VARCHAR = "VARCHAR2(%s)";
+ /**
+ * Oracle database type used when strings are mapped to CLOB
+ */
+ public static final String STRING_CLOB = "CLOB";
+ /**
+ * Oracle database type used to map booleans
+ */
+ public static final String BOOLEAN = "BOOLEAN";
+ /**
+ * Oracle database type used to map bytes
+ */
+ public static final String BYTE = "NUMBER(3)";
+ /**
+ * Oracle database type used to map byte arrays
+ */
+ public static final String BYTE_ARRAY = "RAW(2000)";
+ /**
+ * Oracle database type used to map shorts
+ */
+ public static final String SHORT = "NUMBER(5)";
+ /**
+ * Oracle database type used to map ints
+ */
+ public static final String INTEGER = "NUMBER(10)";
+ /**
+ * Oracle database type used to map longs
+ */
+ public static final String LONG = "NUMBER(19)";
+ /**
+ * Oracle database type used to map float
+ */
+ public static final String FLOAT = "BINARY_FLOAT";
+ /**
+ * Oracle database type used to map double
+ */
+ public static final String DOUBLE = "BINARY_DOUBLE";
+ /**
+ * Oracle database type used to map BigDecimal
+ */
+ public static final String DECIMAL = "NUMBER";
+ /**
+ * Oracle database type used to map offset date time
+ */
+ public static final String OFFSET_DATE_TIME = "TIMESTAMP(9) WITH TIME ZONE";
+ /**
+ * Oracle database type used to map UUID
+ */
+ public static final String UUID = "VARCHAR2(36)";
+ /**
+ * Oracle database type used to map lists
+ */
+ public static final String JSON = "JSON";
+ /**
+ * Oracle database type used to map vectors (the parameter is the dimension of the vector)
+ */
+ public static final String VECTOR_FLOAT = "VECTOR(%s, FLOAT32)";
+}
diff --git a/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleVectorStoreFieldHelper.java b/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleVectorStoreFieldHelper.java
new file mode 100644
index 000000000..bf4dd2952
--- /dev/null
+++ b/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleVectorStoreFieldHelper.java
@@ -0,0 +1,287 @@
+/*
+ ** Oracle Database Vector Store Connector for Semantic Kernel (Java)
+ **
+ ** Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
+ **
+ ** The MIT License (MIT)
+ **
+ ** Permission is hereby granted, free of charge, to any person obtaining a copy
+ ** of this software and associated documentation files (the "Software"), to
+ ** deal in the Software without restriction, including without limitation the
+ ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ ** sell copies of the Software, and to permit persons to whom the Software is
+ ** furnished to do so, subject to the following conditions:
+ **
+ ** The above copyright notice and this permission notice shall be included in
+ ** all copies or substantial portions of the Software.
+ **
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ ** IN THE SOFTWARE.
+ */
+package com.microsoft.semantickernel.data.jdbc.oracle;
+
+import com.microsoft.semantickernel.data.jdbc.oracle.OracleVectorStoreQueryProvider.StringTypeMapping;
+import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordDataField;
+import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordKeyField;
+import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordVectorField;
+import com.microsoft.semantickernel.exceptions.SKException;
+import java.math.BigDecimal;
+import java.time.OffsetDateTime;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class for field operations. Handles mapping between field java types to DB types and
+ * generating SQL statement to create field indexes.
+ */
+class OracleVectorStoreFieldHelper {
+
+ /**
+ * Object naming regular expression
+ */
+ private static final String OBJECT_NAMING_REGEXP = "[a-zA-Z_][a-zA-Z0-9_]{1,128}";
+ /**
+ * The logger
+ */
+ private static final Logger LOGGER = Logger.getLogger(OracleVectorStoreFieldHelper.class.getName());
+
+ /**
+ * Maps supported key java classes to Oracle database types
+ */
+ private static final HashMap, String> supportedKeyTypes = new HashMap();
+ static {
+ supportedKeyTypes.put(String.class, String.format(OracleDataTypesMapping.STRING_VARCHAR, 255));
+ }
+
+ /**
+ * Maps supported vector java classes to Oracle database types
+ */
+ private static final Map, String> supportedVectorTypes = new HashMap();
+ static {
+ supportedVectorTypes.put(String.class, OracleDataTypesMapping.VECTOR_FLOAT);
+ supportedVectorTypes.put(List.class, OracleDataTypesMapping.VECTOR_FLOAT);
+ supportedVectorTypes.put(Collection.class, OracleDataTypesMapping.VECTOR_FLOAT);
+ supportedVectorTypes.put(float[].class, OracleDataTypesMapping.VECTOR_FLOAT);
+ supportedVectorTypes.put(Float[].class, OracleDataTypesMapping.VECTOR_FLOAT);
+ }
+
+ /**
+ * Maps supported data java classes to Oracle database types
+ */
+ private static final HashMap, String> supportedDataTypes = new HashMap();
+ static {
+ supportedDataTypes.put(byte.class, OracleDataTypesMapping.BYTE);
+ supportedDataTypes.put(Byte.class, OracleDataTypesMapping.BYTE);
+ supportedDataTypes.put(short.class, OracleDataTypesMapping.SHORT);
+ supportedDataTypes.put(Short.class, OracleDataTypesMapping.SHORT);
+ supportedDataTypes.put(int.class, OracleDataTypesMapping.INTEGER);
+ supportedDataTypes.put(Integer.class, OracleDataTypesMapping.INTEGER);
+ supportedDataTypes.put(long.class, OracleDataTypesMapping.LONG);
+ supportedDataTypes.put(Long.class, OracleDataTypesMapping.LONG);
+ supportedDataTypes.put(Float.class, OracleDataTypesMapping.FLOAT);
+ supportedDataTypes.put(float.class, OracleDataTypesMapping.FLOAT);
+ supportedDataTypes.put(Double.class, OracleDataTypesMapping.DOUBLE);
+ supportedDataTypes.put(double.class, OracleDataTypesMapping.DOUBLE);
+ supportedDataTypes.put(BigDecimal.class, OracleDataTypesMapping.DECIMAL);
+ supportedDataTypes.put(Boolean.class, OracleDataTypesMapping.BOOLEAN);
+ supportedDataTypes.put(boolean.class, OracleDataTypesMapping.BOOLEAN);
+ supportedDataTypes.put(OffsetDateTime.class, OracleDataTypesMapping.OFFSET_DATE_TIME);
+ supportedDataTypes.put(UUID.class, OracleDataTypesMapping.UUID);
+ supportedDataTypes.put(byte[].class, OracleDataTypesMapping.BYTE_ARRAY);
+ supportedDataTypes.put(List.class, OracleDataTypesMapping.JSON);
+ }
+
+ /**
+ * Suffix added to the effective column name to generate the index name for a vector column.
+ */
+ public static final String VECTOR_INDEX_SUFFIX = "_VECTOR_INDEX";
+
+ /**
+ * Gets the mapping between the supported Java key types and the Oracle database type.
+ *
+ * @return the mapping between the supported Java key types and the Oracle database type.
+ */
+ static Map, String> getSupportedKeyTypes() {
+
+ return Collections.unmodifiableMap(supportedKeyTypes);
+ }
+
+ /**
+ * Gets the mapping between the supported Java data types and the Oracle database type.
+ *
+ * @return the mapping between the supported Java data types and the Oracle database type.
+ */
+ static Map, String> getSupportedDataTypes(
+ StringTypeMapping stringTypeMapping, int defaultVarCharLength) {
+ String stringType = stringTypeMapping.equals(StringTypeMapping.USE_VARCHAR)
+ ? String.format(OracleDataTypesMapping.STRING_VARCHAR, defaultVarCharLength)
+ : OracleDataTypesMapping.STRING_CLOB;
+ supportedDataTypes.put(String.class, stringType);
+ LOGGER.finest("Mapping String columns to " + stringType);
+ return Collections.unmodifiableMap(supportedDataTypes);
+ }
+
+ /**
+ * Gets the mapping between the supported Java data types and the Oracle database type.
+ *
+ * @return the mapping between the supported Java data types and the Oracle database type.
+ */
+ static Map, String> getSupportedVectorTypes() {
+
+ return Collections.unmodifiableMap(supportedVectorTypes);
+ }
+
+ /**
+ * Generates the statement to create the index according to the vector field definition.
+ *
+ * @return the CREATE VECTOR INDEX statement to create the index according to the vector
+ * field definition.
+ */
+ static String getCreateVectorIndexStatement(VectorStoreRecordVectorField field, String collectionTableName) {
+ switch (field.getIndexKind()) {
+ case IVFFLAT:
+ return "CREATE VECTOR INDEX IF NOT EXISTS "
+ + validateObjectNaming(getIndexName(field.getEffectiveStorageName()))
+ + " ON "
+ + validateObjectNaming(collectionTableName)
+ + "( " + validateObjectNaming(field.getEffectiveStorageName()) + " ) "
+ + " ORGANIZATION NEIGHBOR PARTITIONS "
+ + " WITH DISTANCE COSINE "
+ + "PARAMETERS ( TYPE IVF )";
+ case HNSW:
+ return "CREATE VECTOR INDEX IF NOT EXISTS "
+ + validateObjectNaming(getIndexName(field.getEffectiveStorageName()))
+ + " ON "
+ + validateObjectNaming(collectionTableName)
+ + "( " + validateObjectNaming(field.getEffectiveStorageName()) + " ) "
+ + "ORGANIZATION INMEMORY GRAPH "
+ + "WITH DISTANCE COSINE "
+ + "PARAMETERS (TYPE HNSW)";
+ case UNDEFINED:
+ return null;
+ default:
+ LOGGER.warning("Unsupported index kind: " + field.getIndexKind());
+ return null;
+ }
+ }
+
+ /**
+ * Generates the statement to create the index according to the field definition.
+ *
+ * @return the CREATE INDEX statement to create the index according to the field definition.
+ */
+ static String createIndexForDataField(String collectionTableName, VectorStoreRecordDataField dataField, Map, String> supportedDataTypes) {
+ if (supportedDataTypes.get(dataField.getFieldType()) == "JSON") {
+ String dataFieldIndex = "CREATE MULTIVALUE INDEX IF NOT EXISTS %s ON %s t (t.%s.%s)";
+ return String.format(dataFieldIndex,
+ validateObjectNaming(collectionTableName + "_" + dataField.getEffectiveStorageName()),
+ validateObjectNaming(collectionTableName),
+ validateObjectNaming(dataField.getEffectiveStorageName()),
+ getFunctionForType(supportedDataTypes.get(dataField.getFieldSubType())));
+ } else {
+ String dataFieldIndex = "CREATE INDEX IF NOT EXISTS %s ON %s (%s ASC)";
+ return String.format(dataFieldIndex,
+ validateObjectNaming(collectionTableName + "_" + dataField.getEffectiveStorageName()),
+ validateObjectNaming(collectionTableName),
+ validateObjectNaming(dataField.getEffectiveStorageName())
+ );
+ }
+ }
+
+ /**
+ * Returns vector columns names and types for CREATE TABLE statement
+ * @param fields list of vector record fields.
+ * @return comma separated list of columns and types for CREATE TABLE statement.
+ */
+ static String getVectorColumnNamesAndTypes(List fields) {
+ List columns = fields.stream()
+ .map(field -> validateObjectNaming(field.getEffectiveStorageName()) + " " +
+ OracleVectorStoreFieldHelper.getTypeForVectorField(field)
+ ).collect(Collectors.toList());
+
+ return String.join(", ", columns);
+ }
+
+ /**
+ * Returns key column names and type for key column for CREATE TABLE statement
+ * @param field the key field.
+ * @return column name and type of the key field for CREATE TABLE statement.
+ */
+ static String getKeyColumnNameAndType(VectorStoreRecordKeyField field) {
+ return validateObjectNaming(field.getEffectiveStorageName()) + " " + supportedKeyTypes.get(field.getFieldType());
+ }
+
+
+ /**
+ * Generates the index name given the field name. by suffixing "_VECTOR_INDEX" to the field name.
+ * @param effectiveStorageName the field name.
+ * @return the index name.
+ */
+ static String getIndexName(String effectiveStorageName) {
+ return effectiveStorageName + VECTOR_INDEX_SUFFIX;
+ }
+
+ /**
+ * Gets the type of the vector given the field definition. This method is not needed if only
+ *
+ * @param field the vector field definition.
+ * @return returns the type of vector for the given field type.
+ */
+ private static String getTypeForVectorField(VectorStoreRecordVectorField field) {
+ String dimension = field.getDimensions() > 0 ? String.valueOf(field.getDimensions()) : "*";
+ return String.format(supportedVectorTypes.get(field.getFieldType()), dimension);
+ }
+
+ /**
+ * Gets the function that allows to return the function that converts the JSON value to the
+ * data type.
+ * @param jdbcType The JDBC type.
+ * @return the function that allows to return the function that converts the JSON value to the
+ * data type.
+ */
+ private static String getFunctionForType(String jdbcType) {
+ switch (jdbcType) {
+ case OracleDataTypesMapping.BOOLEAN:
+ return "boolean()";
+ case OracleDataTypesMapping.BYTE:
+ case OracleDataTypesMapping.SHORT:
+ case OracleDataTypesMapping.INTEGER:
+ case OracleDataTypesMapping.LONG:
+ case OracleDataTypesMapping.FLOAT:
+ case OracleDataTypesMapping.DOUBLE:
+ case OracleDataTypesMapping.DECIMAL:
+ return "numberOnly()";
+ case OracleDataTypesMapping.OFFSET_DATE_TIME:
+ return "timestamp()";
+ default:
+ return "string()";
+ }
+ }
+
+
+ /**
+ * Validates an SQL identifier.
+ *
+ * @param identifier the identifier
+ * @return the identifier if it is valid
+ * @throws SKException if the identifier is invalid
+ */
+ static String validateObjectNaming(String identifier) {
+ if (identifier.matches(OBJECT_NAMING_REGEXP)) {
+ return identifier;
+ }
+ throw new SKException("Invalid SQL identifier: " + identifier);
+ }
+
+}
diff --git a/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleVectorStoreQueryProvider.java b/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleVectorStoreQueryProvider.java
new file mode 100644
index 000000000..e3fa157b9
--- /dev/null
+++ b/data/semantickernel-data-oracle/src/main/java/com/microsoft/semantickernel/data/jdbc/oracle/OracleVectorStoreQueryProvider.java
@@ -0,0 +1,866 @@
+/*
+ ** Oracle Database Vector Store Connector for Semantic Kernel (Java)
+ **
+ ** Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
+ **
+ ** The MIT License (MIT)
+ **
+ ** Permission is hereby granted, free of charge, to any person obtaining a copy
+ ** of this software and associated documentation files (the "Software"), to
+ ** deal in the Software without restriction, including without limitation the
+ ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ ** sell copies of the Software, and to permit persons to whom the Software is
+ ** furnished to do so, subject to the following conditions:
+ **
+ ** The above copyright notice and this permission notice shall be included in
+ ** all copies or substantial portions of the Software.
+ **
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ ** IN THE SOFTWARE.
+ */
+package com.microsoft.semantickernel.data.jdbc.oracle;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.microsoft.semantickernel.data.filter.AnyTagEqualToFilterClause;
+import com.microsoft.semantickernel.data.filter.EqualToFilterClause;
+import com.microsoft.semantickernel.data.jdbc.JDBCVectorStoreQueryProvider;
+import com.microsoft.semantickernel.data.vectorsearch.VectorSearchFilter;
+import com.microsoft.semantickernel.data.vectorsearch.VectorSearchResult;
+import com.microsoft.semantickernel.data.vectorsearch.VectorSearchResults;
+import com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordMapper;
+import com.microsoft.semantickernel.data.vectorstorage.definition.DistanceFunction;
+import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordDataField;
+import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordDefinition;
+import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordField;
+import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordVectorField;
+import com.microsoft.semantickernel.data.vectorstorage.options.GetRecordOptions;
+import com.microsoft.semantickernel.data.vectorstorage.options.UpsertRecordOptions;
+import com.microsoft.semantickernel.data.vectorstorage.options.VectorSearchOptions;
+import com.microsoft.semantickernel.exceptions.SKException;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import javax.sql.DataSource;
+import oracle.jdbc.OraclePreparedStatement;
+import oracle.jdbc.OracleStatement;
+import oracle.jdbc.OracleTypes;
+import oracle.jdbc.provider.oson.OsonFactory;
+import oracle.sql.TIMESTAMPTZ;
+
+/**
+ * JDBC Vector Store for the Oracle Database
+ */
+public class OracleVectorStoreQueryProvider extends JDBCVectorStoreQueryProvider {
+
+ // This could be removed if super.collectionTable made protected
+ private final String collectionsTable;
+
+ // This could be common to all query providers
+ private final ObjectMapper objectMapper;
+
+ /**
+ * Lock used to ensure that only one thread can create a collection at a time.
+ */
+ private static final ReentrantLock dbCreationLock = new ReentrantLock();
+
+ /**
+ * The logger
+ */
+ private static final Logger LOGGER = Logger.getLogger(OracleVectorStoreQueryProvider.class.getName());
+
+ public enum StringTypeMapping {
+ /**
+ * Maps String to CLOB
+ */
+ USE_CLOB,
+ /**
+ * Maps String to VARCHAR2(4000)
+ */
+ USE_VARCHAR
+ }
+
+ /**
+ * Create an instance of OracleVectorStoreQueryProvider.
+ *
+ * @param dataSource the datasource
+ * @param collectionsTable the collections table name
+ * @param prefixForCollectionTables the prefix for the collection table name
+ * @param defaultVarcharSize the size of VARCHAR columns
+ * @param stringTypeMapping the storage type of string columns (VARCHAR or CLOB)
+ * @param objectMapper the object mapper.
+ */
+ private OracleVectorStoreQueryProvider(
+ @Nonnull DataSource dataSource,
+ @Nonnull String collectionsTable,
+ @Nonnull String prefixForCollectionTables,
+ int defaultVarcharSize,
+ @Nonnull StringTypeMapping stringTypeMapping,
+ ObjectMapper objectMapper) {
+ super(
+ dataSource,
+ collectionsTable,
+ prefixForCollectionTables,
+ OracleVectorStoreFieldHelper.getSupportedKeyTypes(),
+ OracleVectorStoreFieldHelper.getSupportedDataTypes(stringTypeMapping, defaultVarcharSize),
+ OracleVectorStoreFieldHelper.getSupportedVectorTypes());
+ this.collectionsTable = collectionsTable;
+ this.objectMapper = objectMapper;
+ // The JavaTimeModule must be registered to handle OffsetDateTime. To make sure that it is
+ // registered enable the feature IGNORE_DUPLICATE_MODULE_REGISTRATIONS and register the
+ // module.
+ this.objectMapper.enable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS);
+ this.objectMapper.registerModule(new JavaTimeModule());
+ }
+
+ /**
+ *
+ * Creates a collection with the given name and record definition.
+ *
+ * A collection is represented as a table in an Oracle DB containing columns
+ * that match the record definition. The table name is the name of the collection
+ * prefixed by the provided collection prefix. If no prefix was provided the default
+ * prefix will be used.
+ *
+ * @param collectionName the name of the collection
+ * @param recordDefinition the record definition
+ */
+ @Override
+ @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
+ @GuardedBy("dbCreationLock")
+ public void createCollection(String collectionName,
+ VectorStoreRecordDefinition recordDefinition) {
+
+ dbCreationLock.lock();
+ try {
+
+ List vectorFields = recordDefinition.getVectorFields();
+ String createStorageTable = formatQuery("CREATE TABLE IF NOT EXISTS %s ("
+ + "%s PRIMARY KEY, "
+ + "%s, "
+ + "%s)",
+ getCollectionTableName(collectionName),
+ OracleVectorStoreFieldHelper.getKeyColumnNameAndType(recordDefinition.getKeyField()),
+ getColumnNamesAndTypes(new ArrayList<>(recordDefinition.getDataFields()),
+ getSupportedDataTypes()),
+ OracleVectorStoreFieldHelper.getVectorColumnNamesAndTypes(
+ new ArrayList<>(vectorFields)));
+
+ String insertCollectionQuery = this.getInsertCollectionQuery(collectionsTable);
+
+ try (Connection connection = dataSource.getConnection()) {
+ // set auto commit of, either all statements should be executed or none
+ connection.setAutoCommit(false);
+ try (Statement statement = connection.createStatement()) {
+ // Create table
+ statement.addBatch(createStorageTable);
+ LOGGER.finest("Creating collection " + collectionName +
+ " using statement: " + createStorageTable);
+
+ // Index filterable data columns
+ for (VectorStoreRecordDataField dataField : recordDefinition.getDataFields()) {
+ if (dataField.isFilterable()) {
+ String dataFieldIndex = OracleVectorStoreFieldHelper.createIndexForDataField(
+ getCollectionTableName(collectionName), dataField, supportedDataTypes);
+ statement.addBatch(dataFieldIndex);
+ LOGGER.finest("Creating index on column "
+ + dataField.getEffectiveStorageName() + " using the statement: "
+ + dataFieldIndex);
+ }
+ }
+
+ // Create index for vectorFields
+ for (VectorStoreRecordVectorField vectorField : vectorFields) {
+ String createVectorIndex = OracleVectorStoreFieldHelper.getCreateVectorIndexStatement(
+ vectorField, getCollectionTableName(collectionName));
+ if (createVectorIndex != null) {
+ statement.addBatch(createVectorIndex);
+ LOGGER.finest("Creating index on vector column "
+ + vectorField.getEffectiveStorageName() + " using the statement: "
+ + createVectorIndex);
+
+ }
+ }
+ statement.executeBatch();
+
+ // Insert the collection to the store (collections table) using MERGE statement
+ try (PreparedStatement insert = connection.prepareStatement(
+ insertCollectionQuery)) {
+ insert.setString(1, collectionName);
+ insert.execute();
+ LOGGER.finest("Inserting collection to store using statement: " +
+ insertCollectionQuery);
+ }
+
+ connection.commit();
+ } catch (SQLException e) {
+ connection.rollback();
+ throw new SKException("Failed to create collection", e);
+ }
+ } catch (SQLException e) {
+ throw new SKException("Failed to create collection", e);
+ }
+ } finally {
+ dbCreationLock.unlock();
+ }
+ }
+
+ /**
+ *
+ * Inserts or updates record of a collection given the collection name, the records, the record
+ * definition and the upsert options.
+ *
+ * @Note At the moment {@link UpsertRecordOptions} is an empty class. No options are available.
+ *
+ *
+ * @param collectionName the collection name
+ * @param records the records to update or insert
+ * @param recordDefinition the record definition
+ * @param options the options
+ */
+ @Override
+ @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
+ public void upsertRecords(String collectionName,
+ List> records,
+ VectorStoreRecordDefinition recordDefinition,
+ UpsertRecordOptions options) {
+
+ final String NEW_VALUE = "new";
+ final String EXISTING_VALUE = "existing";
+
+ // generate the comma separated list of new fields
+ // Ex.: new.field1, new.field2 ... new.fieldn
+ String insertNewFieldList = recordDefinition.getAllFields().stream()
+ .map(f -> NEW_VALUE + "." + f.getEffectiveStorageName())
+ .collect(Collectors.joining(", "));
+
+ // generate the comma separated list of existing fields
+ // Ex.: existing.field1, existing.field2 ... existing.fieldn
+ String insertExistingFieldList = recordDefinition.getAllFields().stream()
+ .map(f -> EXISTING_VALUE + "." + f.getEffectiveStorageName())
+ .collect(Collectors.joining(", "));
+
+ // generate the comma separated list for setting new values on fields
+ // Ex.: new.field1 = existing.field1, new.field2 = existing.field2 ... new.fieldn = existing.fieldn
+ String updateFieldList = recordDefinition.getAllFields().stream()
+ .filter(f -> f != recordDefinition.getKeyField())
+ .map(f -> EXISTING_VALUE + "." + f.getEffectiveStorageName() + " = " + NEW_VALUE + "." + f.getEffectiveStorageName())
+ .collect(Collectors.joining(", "));
+
+ // generate the comma separated list of placeholders "?" for each field
+ // Ex.: ? field1, ? field2 ... ? fieldn
+ String namedPlaceholders = recordDefinition.getAllFields().stream().map(f -> "? " + f.getEffectiveStorageName())
+ .collect(Collectors.joining(", "));
+
+ // Generate the MERGE statement to perform the upsert.
+ String upsertStatement = formatQuery("MERGE INTO %s existing "+
+ "USING (SELECT %s FROM DUAL) new ON (existing.%s = new.%s) " +
+ "WHEN MATCHED THEN UPDATE SET %s " +
+ "WHEN NOT MATCHED THEN INSERT (%s) VALUES (%s)",
+ getCollectionTableName(collectionName),
+ namedPlaceholders,
+ getKeyColumnName(recordDefinition.getKeyField()),
+ getKeyColumnName(recordDefinition.getKeyField()),
+ updateFieldList,
+ insertExistingFieldList,
+ insertNewFieldList);
+
+ LOGGER.finest("Generated upsert statement: " + upsertStatement);
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement = connection.prepareStatement(upsertStatement)) {
+ // Loop through records, set values and add values to batch
+ for (Object record : records) {
+ setUpsertStatementValues(statement, record, recordDefinition.getAllFields());
+ statement.addBatch();
+ }
+
+ // Execute the upsert statement
+ statement.executeBatch();
+ } catch (SQLException e) {
+ throw new SKException("Failed to upsert records", e);
+ }
+ }
+
+ /**
+ * Generates the MERGE statement to add the given collection to the store.
+ *
+ * @param collectionsTable the name of the DB table containing all collections.
+ * @return a SQL statement that inserts a collection to the store if it does not exist.
+ */
+ @Override
+ protected String getInsertCollectionQuery(String collectionsTable) {
+ return formatQuery(
+ "MERGE INTO %s existing "+
+ "USING (SELECT ? AS collectionId FROM DUAL) new ON (existing.collectionId = new.collectionId) " +
+ "WHEN NOT MATCHED THEN INSERT (existing.collectionId) VALUES (new.collectionId)",
+ collectionsTable);
+ }
+
+ /**
+ * The {@link OracleVectorStoreQueryProvider#upsertRecords(String, List, VectorStoreRecordDefinition, UpsertRecordOptions)}
+ * method adds a placeholder for each field. This method sets the value of each field on the
+ * MERGE statement with the value of the record. The placeholder and values are set in the order
+ * of the fields in the list.
+ *
+ * @param upsertStatement the MERGE statement
+ * @param record the record containing the values
+ * @param fields the list of fields.
+ */
+ private void setUpsertStatementValues(PreparedStatement upsertStatement, Object record,
+ List fields) {
+
+ // use the object mapper to convert the record to an equivalent tree mode JsonNode value,
+ // this allows to retrieve the values using the effective storage name of the fields and
+ // avoids the use of introspection.
+ JsonNode jsonNode = objectMapper.valueToTree(record);
+
+ for (int i = 0; i < fields.size(); ++i) {
+ VectorStoreRecordField field = fields.get(i);
+ try {
+
+ JsonNode valueNode = jsonNode.get(field.getEffectiveStorageName());
+
+ // Some field types require special treatment to convert the java type to the
+ // DB type
+ if (field instanceof VectorStoreRecordVectorField) {
+ // If the vector field is not set as a string convert to an array of floats
+ // and set the value
+ if (!field.getFieldType().equals(String.class)) {
+ if (valueNode != null && !valueNode.isNull() && valueNode.isArray()) {
+ final float[] values = new float[valueNode.size()];
+ for (int j = 0; j < ((ArrayNode)valueNode).size(); j++) {
+ values[j] = ((ArrayNode)valueNode).get(j).floatValue();
+ }
+ upsertStatement.setObject(i + 1, values, OracleTypes.VECTOR_FLOAT32);
+ } else {
+ upsertStatement.setNull(i + 1, OracleTypes.VECTOR_FLOAT32);
+ }
+ continue;
+ }
+ } else if (field instanceof VectorStoreRecordDataField) {
+ // Lists are stored as JSON objects, write the list using the JDBC OSON
+ // extensions.
+ if (field.getFieldType().equals(List.class)) {
+ JsonFactory osonFactory = new OsonFactory();
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ try (JsonGenerator osonGen = osonFactory.createGenerator(out)) {
+ objectMapper.writeValue(osonGen, valueNode);
+ }
+ upsertStatement.setBytes(i + 1, out.toByteArray());
+ } catch (IOException ioEx) {
+ throw new SKException("Failed to convert list to JSON value", ioEx);
+ }
+ continue;
+ }
+ // Convert UUID to string before setting the value.
+ if (field.getFieldType().equals(UUID.class)) {
+ upsertStatement.setObject(i + 1, valueNode.isNull() ? null : valueNode.asText());
+ continue;
+ }
+ // Convert value node (its representations depends on Jackson JSON features)
+ // to OffsetDateTime before setting the value.
+ if (field.getFieldType().equals(OffsetDateTime.class)) {
+ if (valueNode == null || valueNode.isNull()) {
+ upsertStatement.setNull(i + 1, OracleTypes.TIMESTAMPTZ);
+ } else {
+ OffsetDateTime offsetDateTime = (OffsetDateTime) objectMapper.convertValue(valueNode, field.getFieldType());
+ upsertStatement.setObject(i + 1, offsetDateTime);
+ }
+ continue;
+ }
+ }
+
+ // For all other field type use setObject with the field value
+ upsertStatement.setObject(i + 1,
+ objectMapper.convertValue(valueNode,field.getFieldType()));
+ } catch (SQLException e) {
+ throw new SKException(e);
+ }
+ }
+ }
+
+ /**
+ *
+ * Executes a vector search query, using the search options and returns the results. The results
+ * are mapped to the specified record type using the provided mapper. The query is executed
+ * against the specified collection.
+ *
+ *
+ *
+ * @param collectionName the collection name
+ * @param vector the vector to search with
+ * @param options the search options
+ * @param recordDefinition the record definition
+ * @param mapper the mapper, responsible for mapping the result set to the record
+ * type.
+ * @return the search results
+ * @param the record type
+ */
+ @Override
+ @SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
+ public VectorSearchResults search(String collectionName, List vector,
+ VectorSearchOptions options, VectorStoreRecordDefinition recordDefinition,
+ VectorStoreRecordMapper mapper) {
+
+
+ if (vector != null && recordDefinition.getVectorFields().isEmpty()) {
+ throw new SKException("Record definition must contain at least one vector field"
+ + " to perform a vector search");
+ }
+
+ // Gets the search vector field and its distance function. If not vector field was provided,
+ // use the first one
+ VectorStoreRecordVectorField vectorField = null;
+ if (vector != null) {
+ vectorField = getVectorFieldByName(recordDefinition, options.getVectorFieldName());
+ }
+
+
+
+ // get list of fields that should be returned by the query
+ List fields = (options.isIncludeVectors())
+ ? recordDefinition.getAllFields()
+ : recordDefinition.getNonVectorFields();
+
+ // get search filters and get the list of parameters for the filters
+ String filter = getFilter(options.getVectorSearchFilter(), recordDefinition);
+ List