diff --git a/pom.xml b/pom.xml
index 56004fd6519..7c0d9c0dbf0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,7 @@
spring-ai-spring-boot-starters/spring-ai-starter-chroma-store
spring-ai-spring-boot-starters/spring-ai-starter-milvus-store
spring-ai-spring-boot-starters/spring-ai-starter-pgvector-store
+ spring-ai-spring-boot-starters/spring-ai-starter-hanadb-store
spring-ai-spring-boot-starters/spring-ai-starter-pinecone-store
spring-ai-spring-boot-starters/spring-ai-starter-azure-store
spring-ai-spring-boot-starters/spring-ai-starter-weaviate-store
@@ -47,6 +48,7 @@
spring-ai-spring-boot-starters/spring-ai-starter-postgresml-embedding
spring-ai-docs
vector-stores/spring-ai-pgvector-store
+ vector-stores/spring-ai-hanadb-store
vector-stores/spring-ai-milvus-store
vector-stores/spring-ai-neo4j-store
document-readers/pdf-reader
@@ -138,6 +140,7 @@
3.0.1
0.1.4
+ 2.20.11
42.7.2
2.3.4
0.8.0
diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml
index bc1ebc3aa2b..1bc27de0c36 100644
--- a/spring-ai-bom/pom.xml
+++ b/spring-ai-bom/pom.xml
@@ -156,6 +156,12 @@
${project.version}
+
+ org.springframework.ai
+ spring-ai-hanadb-store
+ ${project.version}
+
+
org.springframework.ai
spring-ai-pinecone
@@ -272,6 +278,12 @@
${project.version}
+
+ org.springframework.ai
+ spring-ai-hanadb-store-spring-boot-starter
+ ${project.version}
+
+
org.springframework.ai
spring-ai-watsonx-ai
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/0.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/0.png
new file mode 100644
index 00000000000..d380f45a9dd
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/0.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/1.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/1.png
new file mode 100644
index 00000000000..cb4ab3516e6
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/1.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/10.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/10.png
new file mode 100644
index 00000000000..e2019651ef2
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/10.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/11.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/11.png
new file mode 100644
index 00000000000..ecde0d100a2
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/11.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/13.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/13.png
new file mode 100644
index 00000000000..1f24fe2f55c
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/13.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/14.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/14.png
new file mode 100644
index 00000000000..86353f1ff26
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/14.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/15.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/15.png
new file mode 100644
index 00000000000..32a747dd8fe
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/15.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/16.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/16.png
new file mode 100644
index 00000000000..2365e229fc7
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/16.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/17.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/17.png
new file mode 100644
index 00000000000..6ff6d0bf5af
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/17.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/18.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/18.png
new file mode 100644
index 00000000000..af28b7f1508
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/18.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/19.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/19.png
new file mode 100644
index 00000000000..4e23648aec1
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/19.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/2.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/2.png
new file mode 100644
index 00000000000..3acb0e37127
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/2.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/20.1.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/20.1.png
new file mode 100644
index 00000000000..dd1a9d723c1
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/20.1.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/20.2.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/20.2.png
new file mode 100644
index 00000000000..8794363da14
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/20.2.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/21.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/21.png
new file mode 100644
index 00000000000..97c059c4d62
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/21.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/22.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/22.png
new file mode 100644
index 00000000000..2952a0d8718
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/22.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/23.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/23.png
new file mode 100644
index 00000000000..acbba62978c
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/23.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/24.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/24.png
new file mode 100644
index 00000000000..1967dfb7e93
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/24.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/25.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/25.png
new file mode 100644
index 00000000000..cee49c78e39
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/25.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/26.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/26.png
new file mode 100644
index 00000000000..a87889a2be9
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/26.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/27.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/27.png
new file mode 100644
index 00000000000..e46b0ad2add
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/27.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/28.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/28.png
new file mode 100644
index 00000000000..13c104c5e36
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/28.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/29.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/29.png
new file mode 100644
index 00000000000..8a655e0c4ce
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/29.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/3.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/3.png
new file mode 100644
index 00000000000..97e27530cb6
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/3.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/30.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/30.png
new file mode 100644
index 00000000000..68f1420e718
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/30.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/31.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/31.png
new file mode 100644
index 00000000000..d6193e2ce9d
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/31.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/32.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/32.png
new file mode 100644
index 00000000000..b1d4eb04bac
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/32.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/33.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/33.png
new file mode 100644
index 00000000000..b41caa70d44
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/33.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/34.1.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/34.1.png
new file mode 100644
index 00000000000..fc23b9ace9a
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/34.1.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/34.2.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/34.2.png
new file mode 100644
index 00000000000..b9f9d94ef0a
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/34.2.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/35.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/35.png
new file mode 100644
index 00000000000..8fc160fbcc2
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/35.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/36.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/36.png
new file mode 100644
index 00000000000..01bd42d52f8
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/36.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/37.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/37.png
new file mode 100644
index 00000000000..cc7c77315be
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/37.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/38.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/38.png
new file mode 100644
index 00000000000..d7921f5982c
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/38.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/39.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/39.png
new file mode 100644
index 00000000000..087fc90bf6a
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/39.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/4.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/4.png
new file mode 100644
index 00000000000..edb7d2daa53
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/4.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/40.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/40.png
new file mode 100644
index 00000000000..34214aef9a7
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/40.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/5.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/5.png
new file mode 100644
index 00000000000..3ce821b519a
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/5.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/6.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/6.png
new file mode 100644
index 00000000000..29c75552450
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/6.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/7.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/7.png
new file mode 100644
index 00000000000..779b8fd44d2
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/7.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/8.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/8.png
new file mode 100644
index 00000000000..faa2e2cf104
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/8.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/9.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/9.png
new file mode 100644
index 00000000000..65fe65baea8
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/9.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/wikipedia.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/wikipedia.png
new file mode 100644
index 00000000000..fcd4c76ca0e
Binary files /dev/null and b/spring-ai-docs/src/main/antora/modules/ROOT/images/hanadb/wikipedia.png differ
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
index b8d6c396f61..d1650d19dfb 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
@@ -54,6 +54,7 @@
*** xref:api/vectordbs/pinecone.adoc[]
*** xref:api/vectordbs/qdrant.adoc[]
*** xref:api/vectordbs/gemfire.adoc[GemFire]
+*** xref:api/vectordbs/hana.adoc[SAP Hana Vector Engine]
** xref:api/functions.adoc[Function Calling]
** xref:api/prompt.adoc[]
** xref:api/output-parser.adoc[]
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/hana.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/hana.adoc
new file mode 100644
index 00000000000..6a41378fb75
--- /dev/null
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/hana.adoc
@@ -0,0 +1,253 @@
+= SAP HANA Cloud vector engine
+
+== How to setup a project that uses SAP Hana Cloud as the vector DB and leverage OpenAI to implement RAG pattern
+
+Refer xref:api/vectordbs/hanadb-provision-a-trial-account.adoc[SAP HANA Cloud vector engine - provision a trial account] guide
+to create a trial account
+
+==== Create a table `CRICKET_WORLD_CUP` in SAP Hana DB:
+[sql]
+----
+CREATE TABLE CRICKET_WORLD_CUP (
+ _ID VARCHAR2(255) PRIMARY KEY,
+ CONTENT CLOB,
+ EMBEDDING REAL_VECTOR(1536)
+)
+----
+
+==== Add the following dependencies in your `pom.xml`.
+You may set the property `spring-ai-version` as `1.0.0-SNAPSHOT`:
+[source,xml]
+----
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.ai
+ spring-ai-pdf-document-reader
+ ${spring-ai-version}
+
+
+
+ org.springframework.ai
+ spring-ai-openai-spring-boot-starter
+ ${spring-ai-version}
+
+
+
+ org.springframework.ai
+ spring-ai-hanadb-store-spring-boot-starter
+ ${spring-ai-version}
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ provided
+
+----
+
+==== Add the following properties in `application.properties` file:
+[yml]
+----
+spring.ai.openai.api-key=${OPENAI_API_KEY}
+spring.ai.openai.embedding.options.model=text-embedding-ada-002
+
+spring.datasource.driver-class-name=com.sap.db.jdbc.Driver
+spring.datasource.url=${HANA_DATASOURCE_URL}
+spring.datasource.username=${HANA_DATASOURCE_USERNAME}
+spring.datasource.password=${HANA_DATASOURCE_PASSWORD}
+
+spring.ai.vectorstore.hanadb.tableName=CRICKET_WORLD_CUP
+spring.ai.vectorstore.hanadb.topK=3
+----
+
+==== Create an `Entity` class named `CricketWorldCup` that extends from `HanaVectorEntity`:
+[source,java]
+----
+package com.interviewpedia.spring.ai.hana;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.jackson.Jacksonized;
+import org.springframework.ai.vectorstore.HanaVectorEntity;
+
+@Entity
+@Table(name = "CRICKET_WORLD_CUP")
+@Data
+@Jacksonized
+@NoArgsConstructor
+public class CricketWorldCup extends HanaVectorEntity {
+ @Column(name = "content")
+ private String content;
+}
+
+----
+
+==== Create a `Repository` named `CricketWorldCupRepository` that implements `HanaVectorRepository` interface:
+[source,java]
+----
+package com.interviewpedia.spring.ai.hana;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.transaction.Transactional;
+import org.springframework.ai.vectorstore.HanaVectorRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public class CricketWorldCupRepository implements HanaVectorRepository {
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Override
+ @Transactional
+ public void save(String tableName, String id, String embedding, String content) {
+ String sql = String.format("""
+ INSERT INTO %s (_ID, EMBEDDING, CONTENT)
+ VALUES(:_id, TO_REAL_VECTOR(:embedding), :content)
+ """, tableName);
+
+ entityManager.createNativeQuery(sql)
+ .setParameter("_id", id)
+ .setParameter("embedding", embedding)
+ .setParameter("content", content)
+ .executeUpdate();
+ }
+
+ @Override
+ @Transactional
+ public int deleteEmbeddingsById(String tableName, List idList) {
+ String sql = String.format("""
+ DELETE FROM %s WHERE _ID IN (:ids)
+ """, tableName);
+
+ return entityManager.createNativeQuery(sql)
+ .setParameter("ids", idList)
+ .executeUpdate();
+ }
+
+ @Override
+ @Transactional
+ public int deleteAllEmbeddings(String tableName) {
+ String sql = String.format("""
+ DELETE FROM %s
+ """, tableName);
+
+ return entityManager.createNativeQuery(sql).executeUpdate();
+ }
+
+ @Override
+ public List cosineSimilaritySearch(String tableName, int topK, String queryEmbedding) {
+ String sql = String.format("""
+ SELECT TOP :topK * FROM %s
+ ORDER BY COSINE_SIMILARITY(EMBEDDING, TO_REAL_VECTOR(:queryEmbedding)) DESC
+ """, tableName);
+
+ return entityManager.createNativeQuery(sql, CricketWorldCup.class)
+ .setParameter("topK", topK)
+ .setParameter("queryEmbedding", queryEmbedding)
+ .getResultList();
+ }
+}
+----
+
+==== Now, create a REST Controller class `CricketWorldCupHanaController`, and autowire `ChatClient` and `VectorStore` as dependencies:
+In this controller class, create the following REST endpoints:
+
+- `/ai/hana-vector-store/cricket-world-cup/purge-embeddings` - to purge all the embeddings from the Vector Store
+- `/ai/hana-vector-store/cricket-world-cup/upload` - to upload the Cricket_World_Cup.pdf so that its data gets stored in SAP Hana Cloud Vector DB as embeddings
+- `/ai/hana-vector-store/cricket-world-cup` - to implement `RAG` using link:https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/vectors-vector-embeddings-and-metrics[Cosine_Similarity in SAP Hana DB]
+
+[source,java]
+----
+package com.interviewpedia.spring.ai.hana;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.ChatClient;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.chat.prompt.SystemPromptTemplate;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
+import org.springframework.ai.transformer.splitter.TokenTextSplitter;
+import org.springframework.ai.vectorstore.HanaCloudVectorStore;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+@RestController
+@Slf4j
+public class CricketWorldCupHanaController {
+ private final VectorStore hanaCloudVectorStore;
+ private final ChatClient chatClient;
+
+ @Autowired
+ public CricketWorldCupHanaController(ChatClient chatClient, VectorStore hanaCloudVectorStore) {
+ this.chatClient = chatClient;
+ this.hanaCloudVectorStore = hanaCloudVectorStore;
+ }
+
+ @PostMapping("/ai/hana-vector-store/cricket-world-cup/purge-embeddings")
+ public ResponseEntity purgeEmbeddings() {
+ int deleteCount = ((HanaCloudVectorStore) this.hanaCloudVectorStore).purgeEmbeddings();
+ log.info("{} embeddings purged from CRICKET_WORLD_CUP table in Hana DB", deleteCount);
+ return ResponseEntity.ok().body(String.format("%d embeddings purged from CRICKET_WORLD_CUP table in Hana DB", deleteCount));
+ }
+
+ @PostMapping("/ai/hana-vector-store/cricket-world-cup/upload")
+ public ResponseEntity handleFileUpload(@RequestParam("pdf") MultipartFile file) throws IOException {
+ Resource pdf = file.getResource();
+ Supplier> reader = new PagePdfDocumentReader(pdf);
+ Function, List> splitter = new TokenTextSplitter();
+ List documents = splitter.apply(reader.get());
+ log.info("{} documents created from pdf file: {}", documents.size(), pdf.getFilename());
+ hanaCloudVectorStore.accept(documents);
+ return ResponseEntity.ok().body(String.format("%d documents created from pdf file: %s",
+ documents.size(), pdf.getFilename()));
+ }
+
+ @GetMapping("/ai/hana-vector-store/cricket-world-cup")
+ public Map hanaVectorStoreSearch(@RequestParam(value = "message") String message) {
+ var documents = this.hanaCloudVectorStore.similaritySearch(message);
+ var inlined = documents.stream().map(Document::getContent).collect(Collectors.joining(System.lineSeparator()));
+ var similarDocsMessage = new SystemPromptTemplate("Based on the following: {documents}")
+ .createMessage(Map.of("documents", inlined));
+
+ var userMessage = new UserMessage(message);
+ Prompt prompt = new Prompt(List.of(similarDocsMessage, userMessage));
+ String generation = chatClient.call(prompt).getResult().getOutput().getContent();
+ log.info("Generation: {}", generation);
+ return Map.of("generation", generation);
+ }
+}
+----
+
+
+==== Use a `contextual` pdf file from wikipedia
+Go to link:https://en.wikipedia.org/wiki/Cricket_World_Cup[wikipedia] and link:https://en.wikipedia.org/w/index.php?title=Special:DownloadAsPdf&page=Cricket_World_Cup&action=show-download-screen[download] `Cricket World Cup` page as a PDF file.
+
+image::../images/hanadb/wikipedia.png[width=800]
+
+Upload this PDF file using the file-upload REST endpoint that we created in the previous step.
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/hanadb-provision-a-trial-account.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/hanadb-provision-a-trial-account.adoc
new file mode 100644
index 00000000000..c92a59b40a6
--- /dev/null
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/hanadb-provision-a-trial-account.adoc
@@ -0,0 +1,186 @@
+= SAP HANA Cloud vector engine - provision a trial account
+
+== Below are the steps to provision SAP Hana Database using a trial account
+
+Let's start with creating a link:https://temp-mail.org/en/[temporary email] for registration purposes
+
+image::../images/hanadb/0.png[width=800]
+
+TIP: Don't close the above window, otherwise a new email id would get generated.
+
+Go to link:https://sap.com/[sap.com] and navigate to `products` -> `Trials and Demos`
+
+image::../images/hanadb/1.png[width=800]
+
+Click `Advanced Trials`
+
+image::../images/hanadb/2.png[width=800]
+
+Click `SAP BTP Trial`
+
+image::../images/hanadb/3.png[width=800]
+
+Click `Start your free 90-day trial`
+
+image::../images/hanadb/4.png[width=800]
+
+Paste the `temporary email id` that we created in the first step, and click `Next`
+
+image::../images/hanadb/5.png[width=800]
+
+We fill in our details and click `Submit`
+
+image::../images/hanadb/6.png[width=800]
+
+It's time to check the inbox of our temporary email account
+
+image::../images/hanadb/7.png[width=800]
+
+Notice that there is an email received in our temporary email account
+
+image::../images/hanadb/8.png[width=800]
+
+Open the email and `click to activate` the trial account
+
+image::../images/hanadb/9.png[width=800]
+
+It will prompt to create a `password`. Provide a password and click `Submit`
+
+image::../images/hanadb/10.png[width=800]
+
+The trial account is now created. Click to `start the trial`
+
+image::../images/hanadb/11.png[width=800]
+
+Provide your phone number and click `Continue`
+
+image::../images/hanadb/13.png[width=800]
+
+We receive an OTP on the phone number. Provide the `code` and click `continue`
+
+image::../images/hanadb/14.png[width=800]
+
+Select the `region` as `US East (VA) - AWS`
+
+image::../images/hanadb/15.png[width=800]
+
+Click `Continue`
+
+image::../images/hanadb/16.png[width=800]
+
+The `SAP BTP trial` account is ready. Click `Go to your Trial account`
+
+image::../images/hanadb/17.png[width=800]
+
+Click the `Trial` sub-account
+
+image::../images/hanadb/18.png[width=800]
+
+Open `Instances and Subscriptions`
+
+image::../images/hanadb/19.png[width=800]
+
+It's time to create a subscription. Click the `Create` button
+
+image::../images/hanadb/20.1.png[width=800]
+
+While creating a subscription, Select `service` as `SAP Hana Cloud` and `Plan` as `tools` and click `Create`
+
+image::../images/hanadb/20.2.png[width=800]
+
+Notice that `SAP Hana Cloud` subscription is now created. Click `Users` on the left panel
+
+image::../images/hanadb/21.png[width=800]
+
+Select the username (temporary email that we supplied earlier) and click `Assign Role Collection`
+
+image::../images/hanadb/22.png[width=800]
+
+Search `hana` and select all the 3 role collections that gets displayed. Click `Assign Role Collection`
+
+image::../images/hanadb/23.png[width=800]
+
+Our `user` now has all the 3 role collections. Click `Instances and Subscriptions`
+
+image::../images/hanadb/24.png[width=800]
+
+Now, click `SAP Hana Cloud` application under subscriptions
+
+image::../images/hanadb/25.png[width=800]
+
+There are no instances yet. Let's click `Create Instance`
+
+image::../images/hanadb/26.png[width=800]
+
+Select Type as `SAP HANA Cloud, SAP HANA Database`. Click `Next Step`
+
+image::../images/hanadb/27.png[width=800]
+
+Provide `Instance Name`, `Description`, `password` for DBADMIN administrator.
+Select the latest version `2024.2 (QRC 1/2024)`. Click `Next Step`
+
+image::../images/hanadb/28.png[width=800]
+
+Keep everything as default. Click `Next Step`
+
+image::../images/hanadb/29.png[width=800]
+
+Click `Next Step`
+
+image::../images/hanadb/30.png[width=800]
+
+Select `Allow all IP addresses` and click `Next Step`
+
+image::../images/hanadb/31.png[width=800]
+
+Click `Review and Create`
+
+image::../images/hanadb/32.png[width=800]
+
+Click `Create Instance`
+
+image::../images/hanadb/33.png[width=800]
+
+Notice that the provisioning of `SAP Hana Database` instance has started. It takes some time to provision - please be patient.
+
+image::../images/hanadb/34.1.png[width=800]
+
+Once the instance is provisioned (status is displayed as `Running`) we can get the datasource url (`SQL Endpoint`) by clicking the instance and selecting `Connections`
+
+image::../images/hanadb/34.2.png[width=800]
+
+We navigate to `SAP Hana Database Explorer` by click the `...`
+
+image::../images/hanadb/35.png[width=800]
+
+Provide the administrator credentials and click `OK`
+
+image::../images/hanadb/36.png[width=800]
+
+Open SQL console and create the table `CRICKET_WORLD_CUP` using the following DDL statement:
+[sql]
+----
+CREATE TABLE CRICKET_WORLD_CUP (
+ _ID VARCHAR2(255) PRIMARY KEY,
+ CONTENT CLOB,
+ EMBEDDING REAL_VECTOR(1536)
+)
+----
+
+image::../images/hanadb/37.png[width=800]
+
+Navigate to `hana_dev_db -> Catalog -> Tables` to find our table `CRICKET_WORLD_CUP`
+
+image::../images/hanadb/38.png[width=800]
+
+Right-click on the table and click `Open Data`
+
+image::../images/hanadb/39.png[width=800]
+
+Notice that the table data is now displayed. There are now rows as we didn't create any embeddings yet.
+
+image::../images/hanadb/40.png[width=800]
+
+Next steps: xref:api/vectordbs/hana.adoc[SAP Hana Vector Engine]
+
+
diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml
index aceee800753..547d79b7956 100644
--- a/spring-ai-spring-boot-autoconfigure/pom.xml
+++ b/spring-ai-spring-boot-autoconfigure/pom.xml
@@ -109,6 +109,14 @@
true
+
+
+ org.springframework.ai
+ spring-ai-hanadb-store
+ ${project.parent.version}
+ true
+
+
com.pgvector
pgvector
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreAutoConfiguration.java
new file mode 100644
index 00000000000..7c56afb1d57
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreAutoConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.autoconfigure.vectorstore.hanadb;
+
+import org.springframework.ai.embedding.EmbeddingClient;
+import org.springframework.ai.vectorstore.HanaCloudVectorStore;
+import org.springframework.ai.vectorstore.HanaCloudVectorStoreConfig;
+import org.springframework.ai.vectorstore.HanaVectorEntity;
+import org.springframework.ai.vectorstore.HanaVectorRepository;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import javax.sql.DataSource;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+@AutoConfiguration
+@ConditionalOnClass({ HanaCloudVectorStore.class, DataSource.class, HanaVectorEntity.class })
+@EnableConfigurationProperties(HanaCloudVectorStoreProperties.class)
+public class HanaCloudVectorStoreAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public HanaCloudVectorStore vectorStore(HanaVectorRepository extends HanaVectorEntity> repository,
+ EmbeddingClient embeddingClient, HanaCloudVectorStoreProperties properties) {
+
+ return new HanaCloudVectorStore(repository, embeddingClient,
+ HanaCloudVectorStoreConfig.builder()
+ .tableName(properties.getTableName())
+ .topK(properties.getTopK())
+ .build());
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreProperties.java
new file mode 100644
index 00000000000..79dfbfbc1af
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreProperties.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.autoconfigure.vectorstore.hanadb;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+@ConfigurationProperties(HanaCloudVectorStoreProperties.CONFIG_PREFIX)
+public class HanaCloudVectorStoreProperties {
+
+ public static final String CONFIG_PREFIX = "spring.ai.vectorstore.hanadb";
+
+ private String tableName;
+
+ private int topK;
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public void setTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public int getTopK() {
+ return topK;
+ }
+
+ public void setTopK(int topK) {
+ this.topK = topK;
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index f614b7711ec..3e3fd3574fe 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -24,6 +24,7 @@ org.springframework.ai.autoconfigure.vectorstore.azure.AzureVectorStoreAutoConfi
org.springframework.ai.autoconfigure.vectorstore.weaviate.WeaviateVectorStoreAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.neo4j.Neo4jVectorStoreAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration
+org.springframework.ai.autoconfigure.vectorstore.hanadb.HanaCloudVectorStoreAutoConfiguration
org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration
org.springframework.ai.autoconfigure.postgresml.PostgresMlAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.mongo.MongoDBAtlasVectorStoreAutoConfiguration
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreAutoConfigurationIT.java
new file mode 100644
index 00000000000..36bc049bf37
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStoreAutoConfigurationIT.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.autoconfigure.vectorstore.hanadb;
+
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
+import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.util.List;
+
+@Testcontainers
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_URL", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_USERNAME", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_PASSWORD", matches = ".+")
+@Disabled
+public class HanaCloudVectorStoreAutoConfigurationIT {
+
+ @Test
+ public void addAndSearch() {
+ contextRunner.run(context -> {
+ VectorStore vectorStore = context.getBean(VectorStore.class);
+ vectorStore.add(documents);
+
+ List results = vectorStore.similaritySearch("What is Great Depression?");
+ Assertions.assertEquals(1, results.size());
+
+ // Remove all documents from the store
+ vectorStore.delete(documents.stream().map(Document::getId).toList());
+ List results2 = vectorStore.similaritySearch("Great Depression");
+ Assertions.assertEquals(0, results2.size());
+ });
+ }
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(HanaCloudVectorStoreAutoConfiguration.class,
+ OpenAiAutoConfiguration.class, RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class,
+ JdbcRepositoriesAutoConfiguration.class))
+ .withPropertyValues("spring.ai.openai.api-key=" + System.getenv("OPENAI_API_KEY"),
+ "spring.ai.openai.embedding.options.model=text-embedding-ada-002",
+ "spring.datasource.url=" + System.getenv("HANA_DATASOURCE_URL"),
+ "spring.datasource.username=" + System.getenv("HANA_DATASOURCE_USERNAME"),
+ "spring.datasource.password=" + System.getenv("HANA_DATASOURCE_PASSWORD"),
+ "spring.ai.vectorstore.hanadb.tableName=CRICKET_WORLD_CUP", "spring.ai.vectorstore.hanadb.topK=1");
+
+ List documents = List.of(
+ new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!"),
+ new Document("Hello World Hello World Hello World Hello World Hello World Hello World Hello World"),
+ new Document(
+ "Great Depression Great Depression Great Depression Great Depression Great Depression Great Depression"));
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStorePropertiesTest.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStorePropertiesTest.java
new file mode 100644
index 00000000000..1756e8cd7dc
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/hanadb/HanaCloudVectorStorePropertiesTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.autoconfigure.vectorstore.hanadb;
+
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * @author Rahul Mittal
+ */
+public class HanaCloudVectorStorePropertiesTest {
+
+ @Test
+ public void testHanaCloudVectorStoreProperties() {
+ var props = new HanaCloudVectorStoreProperties();
+ props.setTableName("CRICKET_WORLD_CUP");
+ props.setTopK(5);
+
+ Assertions.assertEquals("CRICKET_WORLD_CUP", props.getTableName());
+ Assertions.assertEquals(5, props.getTopK());
+ }
+
+}
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-hanadb-store/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-hanadb-store/pom.xml
new file mode 100644
index 00000000000..bba6eddcef6
--- /dev/null
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-hanadb-store/pom.xml
@@ -0,0 +1,42 @@
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ spring-ai-hanadb-store-spring-boot-starter
+ jar
+ Spring AI Starter - SAP Hana Cloud Vector Store
+ Spring AI SAP Hana Cloud Vector Store Auto Configuration
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.ai
+ spring-ai-spring-boot-autoconfigure
+ ${project.parent.version}
+
+
+
+ org.springframework.ai
+ spring-ai-hanadb-store
+ ${project.parent.version}
+
+
+
+
diff --git a/vector-stores/spring-ai-hanadb-store/README.md b/vector-stores/spring-ai-hanadb-store/README.md
new file mode 100644
index 00000000000..a5f2443303a
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/README.md
@@ -0,0 +1 @@
+[SAP Hana Cloud Vector Store Documentation](https://docs.spring.io/spring-ai/reference/api/vectordbs/hana.html)
\ No newline at end of file
diff --git a/vector-stores/spring-ai-hanadb-store/pom.xml b/vector-stores/spring-ai-hanadb-store/pom.xml
new file mode 100644
index 00000000000..19b49e3a3c8
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ spring-ai-hanadb-store
+ jar
+ Spring AI Vector Store - HanaDB
+ Spring AI HanaDB Vector Store
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+ org.springframework.ai
+ spring-ai-core
+ ${parent.version}
+
+
+
+ org.springframework.data
+ spring-data-jpa
+
+
+
+ org.hibernate.orm
+ hibernate-core
+
+
+
+
+ com.sap.cloud.db.jdbc
+ ngdbc
+ ${sap.hanadb.version}
+
+
+
+
+ org.springframework.ai
+ spring-ai-openai
+ ${parent.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.ai
+ spring-ai-test
+ ${parent.version}
+ test
+
+
+
+ org.springframework.ai
+ spring-ai-pdf-document-reader
+ ${parent.version}
+ test
+
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
diff --git a/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaCloudVectorStore.java b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaCloudVectorStore.java
new file mode 100644
index 00000000000..4f0415b7b74
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaCloudVectorStore.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingClient;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * The SAP HANA Cloud vector engine offers multiple use cases in AI scenarios.
+ *
+ * Recent advances in Generative AI (GenAI) and Large Language Models (LLM) have led to
+ * increased awareness of and popularity for vector databases. Similarity search, a key
+ * functionality of vector databases, complements traditional relational databases as well
+ * as full-text search systems. Using natural language text as an example, embedding
+ * functions map data to high dimensional vectors to preserve their semantic similarity.
+ * Developers can then use vector-based semantic search to find similarity between
+ * different passages of text. Because the data within an LLM is current only up to a
+ * specific point in time, vector databases can offer additional relevant text to make
+ * searches more accurate – known as Retrieval Augmented Generation (RAG).
+ * Therefore, the addition of RAG to an LLM using a vector database like SAP HANA Cloud
+ * provides an effective approach to increase the quality of responses from an LLM.
+ *
+ * The SAP HANA Cloud vector engine supports the create, read, update, and delete (CRUD)
+ * operations involving vectors using SQL.
+ *
+ * HanaCloudVectorStore
is an implementation of
+ * org.springframework.ai.vectorstore.VectorStore
interface that provides
+ * implementation of COSINE_SIMILARITY
function introduced in HanaDB in Mar,
+ * 2024
+ *
+ * Hana DB introduced a new datatype REAL_VECTOR
that can store embeddings
+ * generated by org.springframework.ai.embedding.EmbeddingClient
+ *
+ * @author Rahul Mittal
+ * @see SAP
+ * HANA Database Vector Engine Guide
+ * @since 1.0.0
+ */
+public class HanaCloudVectorStore implements VectorStore {
+
+ private static final Logger logger = LoggerFactory.getLogger(HanaCloudVectorStore.class);
+
+ private final HanaVectorRepository extends HanaVectorEntity> repository;
+
+ private final EmbeddingClient embeddingClient;
+
+ private final HanaCloudVectorStoreConfig config;
+
+ public HanaCloudVectorStore(HanaVectorRepository extends HanaVectorEntity> repository,
+ EmbeddingClient embeddingClient, HanaCloudVectorStoreConfig config) {
+ this.repository = repository;
+ this.embeddingClient = embeddingClient;
+ this.config = config;
+ }
+
+ @Override
+ public void add(List documents) {
+ int count = 1;
+ for (Document document : documents) {
+ logger.info("[{}/{}] Calling EmbeddingClient for document id = {}", count++, documents.size(),
+ document.getId());
+ String content = document.getContent().replaceAll("\\s+", " ");
+ String embedding = getEmbedding(document);
+ repository.save(config.getTableName(), document.getId(), embedding, content);
+ }
+ logger.info("Embeddings saved in HanaCloudVectorStore for {} documents", count - 1);
+ }
+
+ @Override
+ public Optional delete(List idList) {
+ int deleteCount = repository.deleteEmbeddingsById(config.getTableName(), idList);
+ logger.info("{} embeddings deleted", deleteCount);
+ return Optional.of(deleteCount == idList.size());
+ }
+
+ public int purgeEmbeddings() {
+ int deleteCount = repository.deleteAllEmbeddings(config.getTableName());
+ logger.info("{} embeddings deleted", deleteCount);
+ return deleteCount;
+ }
+
+ @Override
+ public List similaritySearch(String query) {
+ return similaritySearch(SearchRequest.query(query).withTopK(config.getTopK()));
+ }
+
+ @Override
+ public List similaritySearch(SearchRequest request) {
+ if (request.hasFilterExpression()) {
+ throw new UnsupportedOperationException(
+ "SAPHanaVectorEngine does not support metadata filter expressions yet.");
+ }
+
+ String queryEmbedding = getEmbedding(request);
+ List extends HanaVectorEntity> searchResult = repository.cosineSimilaritySearch(config.getTableName(),
+ request.getTopK(), queryEmbedding);
+ logger.info("Hana cosine-similarity for query={}, with topK={} returned {} results", request.getQuery(),
+ request.getTopK(), searchResult.size());
+
+ return searchResult.stream().map(c -> {
+ try {
+ return new Document(c.get_id(), c.toJson(), Collections.emptyMap());
+ }
+ catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }).collect(Collectors.toList());
+ }
+
+ private String getEmbedding(SearchRequest searchRequest) {
+ return "[" + this.embeddingClient.embed(searchRequest.getQuery())
+ .stream()
+ .map(String::valueOf)
+ .collect(Collectors.joining(", ")) + "]";
+ }
+
+ private String getEmbedding(Document document) {
+ return "["
+ + this.embeddingClient.embed(document).stream().map(String::valueOf).collect(Collectors.joining(", "))
+ + "]";
+ }
+
+}
diff --git a/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaCloudVectorStoreConfig.java b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaCloudVectorStoreConfig.java
new file mode 100644
index 00000000000..315bbdca955
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaCloudVectorStoreConfig.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+public class HanaCloudVectorStoreConfig {
+
+ private String tableName;
+
+ private int topK;
+
+ private HanaCloudVectorStoreConfig() {
+ }
+
+ public static HanaCloudVectorStoreConfigBuilder builder() {
+ return new HanaCloudVectorStoreConfigBuilder();
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public int getTopK() {
+ return topK;
+ }
+
+ public static class HanaCloudVectorStoreConfigBuilder {
+
+ private String tableName;
+
+ private int topK;
+
+ public HanaCloudVectorStoreConfigBuilder tableName(String tableName) {
+ this.tableName = tableName;
+ return this;
+ }
+
+ public HanaCloudVectorStoreConfigBuilder topK(int topK) {
+ this.topK = topK;
+ return this;
+ }
+
+ public HanaCloudVectorStoreConfig build() {
+ HanaCloudVectorStoreConfig config = new HanaCloudVectorStoreConfig();
+ config.tableName = tableName;
+ config.topK = topK;
+ return config;
+ }
+
+ }
+
+}
diff --git a/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaVectorEntity.java b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaVectorEntity.java
new file mode 100644
index 00000000000..b5031b24892
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaVectorEntity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.persistence.Column;
+import jakarta.persistence.Id;
+import jakarta.persistence.MappedSuperclass;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+@MappedSuperclass
+public abstract class HanaVectorEntity {
+
+ @Id
+ @Column(name = "_id")
+ protected String _id;
+
+ public HanaVectorEntity() {
+ }
+
+ public String toJson() throws JsonProcessingException {
+ return new ObjectMapper().writeValueAsString(this);
+ }
+
+ public String get_id() {
+ return _id;
+ }
+
+}
diff --git a/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaVectorRepository.java b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaVectorRepository.java
new file mode 100644
index 00000000000..8481f9300ec
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/main/java/org/springframework/ai/vectorstore/HanaVectorRepository.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+import java.util.List;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+public interface HanaVectorRepository {
+
+ void save(String tableName, String id, String embedding, String content);
+
+ int deleteEmbeddingsById(String tableName, List idList);
+
+ int deleteAllEmbeddings(String tableName);
+
+ List cosineSimilaritySearch(String tableName, int topK, String queryEmbedding);
+
+}
diff --git a/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCup.java b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCup.java
new file mode 100644
index 00000000000..ed1f1e393ed
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCup.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+@Entity
+@Table(name = "CRICKET_WORLD_CUP")
+public class CricketWorldCup extends HanaVectorEntity {
+
+ @Column(name = "content")
+ private String content;
+
+ public String getContent() {
+ return content;
+ }
+
+}
diff --git a/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCupHanaController.java b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCupHanaController.java
new file mode 100644
index 00000000000..ad3f954a7a6
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCupHanaController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.chat.ChatClient;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.chat.prompt.SystemPromptTemplate;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
+import org.springframework.ai.transformer.splitter.TokenTextSplitter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+@RestController
+public class CricketWorldCupHanaController {
+
+ private static final Logger logger = LoggerFactory.getLogger(CricketWorldCupHanaController.class);
+
+ private final VectorStore hanaCloudVectorStore;
+
+ private final ChatClient chatClient;
+
+ @Autowired
+ public CricketWorldCupHanaController(ChatClient chatClient, VectorStore hanaCloudVectorStore) {
+ this.chatClient = chatClient;
+ this.hanaCloudVectorStore = hanaCloudVectorStore;
+ }
+
+ @PostMapping("/ai/hana-vector-store/cricket-world-cup/purge-embeddings")
+ public ResponseEntity purgeEmbeddings() {
+ int deleteCount = ((HanaCloudVectorStore) this.hanaCloudVectorStore).purgeEmbeddings();
+ logger.info("{} embeddings purged from CRICKET_WORLD_CUP table in Hana DB", deleteCount);
+ return ResponseEntity.ok()
+ .body(String.format("%d embeddings purged from CRICKET_WORLD_CUP table in Hana DB", deleteCount));
+ }
+
+ @PostMapping("/ai/hana-vector-store/cricket-world-cup/upload")
+ public ResponseEntity handleFileUpload(@RequestParam("pdf") MultipartFile file) throws IOException {
+ Resource pdf = file.getResource();
+ Supplier> reader = new PagePdfDocumentReader(pdf);
+ Function, List> splitter = new TokenTextSplitter();
+ List documents = splitter.apply(reader.get());
+ logger.info("{} documents created from pdf file: {}", documents.size(), pdf.getFilename());
+ hanaCloudVectorStore.accept(documents);
+ return ResponseEntity.ok()
+ .body(String.format("%d documents created from pdf file: %s", documents.size(), pdf.getFilename()));
+ }
+
+ @GetMapping("/ai/hana-vector-store/cricket-world-cup")
+ public Map hanaVectorStoreSearch(@RequestParam(value = "message") String message) {
+ var documents = this.hanaCloudVectorStore.similaritySearch(message);
+ var inlined = documents.stream().map(Document::getContent).collect(Collectors.joining(System.lineSeparator()));
+ var similarDocsMessage = new SystemPromptTemplate("Based on the following: {documents}")
+ .createMessage(Map.of("documents", inlined));
+
+ var userMessage = new UserMessage(message);
+ Prompt prompt = new Prompt(List.of(similarDocsMessage, userMessage));
+ String generation = chatClient.call(prompt).getResult().getOutput().getContent();
+ logger.info("Generation: {}", generation);
+ return Map.of("generation", generation);
+ }
+
+}
diff --git a/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCupRepository.java b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCupRepository.java
new file mode 100644
index 00000000000..397ea39be64
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/CricketWorldCupRepository.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.transaction.Transactional;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+@Repository
+public class CricketWorldCupRepository implements HanaVectorRepository {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Override
+ @Transactional
+ public void save(String tableName, String id, String embedding, String content) {
+ String sql = String.format("""
+ INSERT INTO %s (_ID, EMBEDDING, CONTENT)
+ VALUES(:_id, TO_REAL_VECTOR(:embedding), :content)
+ """, tableName);
+
+ entityManager.createNativeQuery(sql)
+ .setParameter("_id", id)
+ .setParameter("embedding", embedding)
+ .setParameter("content", content)
+ .executeUpdate();
+ }
+
+ @Override
+ @Transactional
+ public int deleteEmbeddingsById(String tableName, List idList) {
+ String sql = String.format("""
+ DELETE FROM %s WHERE _ID IN (:ids)
+ """, tableName);
+
+ return entityManager.createNativeQuery(sql).setParameter("ids", idList).executeUpdate();
+ }
+
+ @Override
+ @Transactional
+ public int deleteAllEmbeddings(String tableName) {
+ String sql = String.format("""
+ DELETE FROM %s
+ """, tableName);
+
+ return entityManager.createNativeQuery(sql).executeUpdate();
+ }
+
+ @Override
+ public List cosineSimilaritySearch(String tableName, int topK, String queryEmbedding) {
+ String sql = String.format("""
+ SELECT TOP :topK * FROM %s
+ ORDER BY COSINE_SIMILARITY(EMBEDDING, TO_REAL_VECTOR(:queryEmbedding)) DESC
+ """, tableName);
+
+ return entityManager.createNativeQuery(sql, CricketWorldCup.class)
+ .setParameter("topK", topK)
+ .setParameter("queryEmbedding", queryEmbedding)
+ .getResultList();
+ }
+
+}
diff --git a/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/HanaCloudVectorStoreIT.java b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/HanaCloudVectorStoreIT.java
new file mode 100644
index 00000000000..463d2fe1086
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/HanaCloudVectorStoreIT.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2023 - 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.ai.vectorstore;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingClient;
+import org.springframework.ai.openai.OpenAiEmbeddingClient;
+import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
+import org.springframework.ai.transformer.splitter.TokenTextSplitter;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.jpa.JpaVendorAdapter;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import javax.sql.DataSource;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * @author Rahul Mittal
+ * @since 1.0.0
+ */
+@Testcontainers
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_URL", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_USERNAME", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_PASSWORD", matches = ".+")
+public class HanaCloudVectorStoreIT {
+
+ private static final Logger logger = LoggerFactory.getLogger(HanaCloudVectorStoreIT.class);
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withUserConfiguration(HanaTestApplication.class);
+
+ @Test
+ public void vectorStoreTest() {
+ contextRunner.run(context -> {
+
+ VectorStore vectorStore = context.getBean(HanaCloudVectorStore.class);
+ int deleteCount = ((HanaCloudVectorStore) vectorStore).purgeEmbeddings();
+ logger.info("Purged all embeddings: count={}", deleteCount);
+
+ Supplier> reader = new PagePdfDocumentReader("classpath:Cricket_World_Cup.pdf");
+ Function, List> splitter = new TokenTextSplitter();
+ List documents = splitter.apply(reader.get());
+ vectorStore.accept(documents);
+
+ List results = vectorStore.similaritySearch("Who won the 2023 cricket world cup finals?");
+ Assertions.assertEquals(1, results.size());
+ Assertions.assertTrue(results.get(0).getContent().contains("Australia"));
+
+ // Remove all documents from the store
+ vectorStore.delete(documents.stream().map(Document::getId).toList());
+ List results2 = vectorStore.similaritySearch("Who won the 2023 cricket world cup finals?");
+ Assertions.assertEquals(0, results2.size());
+ });
+ }
+
+ @SpringBootConfiguration
+ @EnableAutoConfiguration
+ public static class HanaTestApplication {
+
+ @Bean
+ public VectorStore hanaCloudVectorStore(CricketWorldCupRepository cricketWorldCupRepository,
+ EmbeddingClient embeddingClient) {
+ return new HanaCloudVectorStore(cricketWorldCupRepository, embeddingClient,
+ HanaCloudVectorStoreConfig.builder().tableName("CRICKET_WORLD_CUP").topK(1).build());
+ }
+
+ @Bean
+ public CricketWorldCupRepository cricketWorldCupRepository() {
+ return new CricketWorldCupRepository();
+ }
+
+ @Bean
+ public DataSource dataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+
+ dataSource.setDriverClassName("com.sap.db.jdbc.Driver");
+ dataSource.setUrl(System.getenv("HANA_DATASOURCE_URL"));
+ dataSource.setUsername(System.getenv("HANA_DATASOURCE_USERNAME"));
+ dataSource.setPassword(System.getenv("HANA_DATASOURCE_PASSWORD"));
+
+ return dataSource;
+ }
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
+ LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
+ em.setDataSource(dataSource());
+ em.setPackagesToScan("org.springframework.ai.vectorstore");
+
+ JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ em.setJpaVendorAdapter(vendorAdapter);
+
+ return em;
+ }
+
+ @Bean
+ public EmbeddingClient embeddingClient() {
+ return new OpenAiEmbeddingClient(new OpenAiApi(System.getenv("OPENAI_API_KEY")));
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/vector-stores/spring-ai-hanadb-store/src/test/resources/Cricket_World_Cup.pdf b/vector-stores/spring-ai-hanadb-store/src/test/resources/Cricket_World_Cup.pdf
new file mode 100644
index 00000000000..1d8ffae5f77
Binary files /dev/null and b/vector-stores/spring-ai-hanadb-store/src/test/resources/Cricket_World_Cup.pdf differ
diff --git a/vector-stores/spring-ai-hanadb-store/src/test/resources/application.properties b/vector-stores/spring-ai-hanadb-store/src/test/resources/application.properties
new file mode 100644
index 00000000000..5f9e15ec539
--- /dev/null
+++ b/vector-stores/spring-ai-hanadb-store/src/test/resources/application.properties
@@ -0,0 +1,10 @@
+spring.ai.openai.api-key=${OPENAI_API_KEY}
+spring.ai.openai.embedding.options.model=text-embedding-ada-002
+
+spring.datasource.driver-class-name=com.sap.db.jdbc.Driver
+spring.datasource.url=${HANA_DATASOURCE_URL}
+spring.datasource.username=${HANA_DATASOURCE_USERNAME}
+spring.datasource.password=${HANA_DATASOURCE_PASSWORD}
+
+spring.ai.vectorstore.hanadb.tableName=CRICKET_WORLD_CUP
+spring.ai.vectorstore.hanadb.topK=3
\ No newline at end of file