From ebfddd797c26524952be4f3462f87f941a3dbeba Mon Sep 17 00:00:00 2001 From: psilberk Date: Mon, 25 May 2026 20:45:06 -0700 Subject: [PATCH 1/5] Oracle Database support for Chat Memory Signed-off-by: psilberk --- .../README.md | 29 +++ .../pom.xml | 66 ++++++ .../oracle/OracleChatMemoryRepository.java | 99 ++++++++ .../repository/oracle/package-info.java | 4 + .../oracle/OracleChatMemoryRepositoryIT.java | 216 ++++++++++++++++++ pom.xml | 1 + spring-ai-bom/pom.xml | 6 + 7 files changed, 421 insertions(+) create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-oracle/pom.xml create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md b/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md new file mode 100644 index 0000000000..c92648bc3d --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md @@ -0,0 +1,29 @@ +# Spring AI Oracle Chat Memory Repository + +[Chat Memory Documentation](https://docs.spring.io/spring-ai/reference/api/chat-memory.html#_chat_memory) + +## Test Notes + +### Use Existing Oracle Database + +If `ORACLE_DATABASE_URL` is set, tests use that database instead of Testcontainers. + +Optional credentials: +- `ORACLE_DATABASE_USERNAME` +- `ORACLE_DATABASE_PASSWORD` + +### Rancher Desktop + Testcontainers + +With Rancher Desktop, Ryuk can fail to start unless you set: + +- `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock` + +Example: + +```bash +TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock \ +mvn -pl memory/repository/spring-ai-model-chat-memory-repository-oracle \ + -Denforcer.skip=true \ + -Dmaven.build.cache.enabled=false \ + -Dtest=OracleChatMemoryRepositoryIT test +``` diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-oracle/pom.xml new file mode 100644 index 0000000000..8039f50242 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + org.springframework.ai + spring-ai-parent + 2.0.0-SNAPSHOT + ../../../pom.xml + + + spring-ai-model-chat-memory-repository-oracle + Spring AI Oracle Chat Memory + Spring AI Oracle Chat Memory implementation + + https://github.com/spring-projects/spring-ai + + + https://github.com/spring-projects/spring-ai + scm:git:git://github.com/spring-projects/spring-ai.git + scm:git:ssh://git@github.com/spring-projects/spring-ai.git + + + + + org.springframework.ai + spring-ai-model-chat-memory-repository-jdbc + ${project.version} + + + + org.springframework.boot + spring-boot-starter-jdbc + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.testcontainers + testcontainers + test + + + + org.testcontainers + testcontainers-oracle-free + test + + + + com.oracle.database.jdbc + ojdbc11 + 23.4.0.24.05 + test + true + + + + diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java new file mode 100644 index 0000000000..9a693c21a9 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java @@ -0,0 +1,99 @@ +package org.springframework.ai.chat.memory.repository.oracle; + +import java.util.List; + +import javax.sql.DataSource; + +import org.jspecify.annotations.Nullable; + +import org.springframework.ai.chat.memory.ChatMemoryRepository; +import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; +import org.springframework.ai.chat.memory.repository.jdbc.OracleChatMemoryRepositoryDialect; +import org.springframework.ai.chat.messages.Message; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * An implementation of {@link ChatMemoryRepository} for Oracle. + * + * @since 2.0.0 + */ +public final class OracleChatMemoryRepository implements ChatMemoryRepository { + + private final JdbcChatMemoryRepository delegate; + + private OracleChatMemoryRepository(JdbcChatMemoryRepository delegate) { + this.delegate = delegate; + } + + @Override + public List findConversationIds() { + return this.delegate.findConversationIds(); + } + + @Override + public List findByConversationId(String conversationId) { + return this.delegate.findByConversationId(conversationId); + } + + @Override + public void saveAll(String conversationId, List messages) { + this.delegate.saveAll(conversationId, messages); + } + + @Override + public void deleteByConversationId(String conversationId) { + this.delegate.deleteByConversationId(conversationId); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private @Nullable DataSource dataSource; + + private @Nullable JdbcTemplate jdbcTemplate; + + private @Nullable PlatformTransactionManager transactionManager; + + private Builder() { + } + + public Builder dataSource(DataSource dataSource) { + this.dataSource = dataSource; + return this; + } + + public Builder jdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + return this; + } + + public Builder transactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + return this; + } + + public OracleChatMemoryRepository build() { + var repository = JdbcChatMemoryRepository.builder().dialect(new OracleChatMemoryRepositoryDialect()); + + if (this.dataSource != null) { + repository.dataSource(this.dataSource); + } + + if (this.jdbcTemplate != null) { + repository.jdbcTemplate(this.jdbcTemplate); + } + + if (this.transactionManager != null) { + repository.transactionManager(this.transactionManager); + } + + return new OracleChatMemoryRepository(repository.build()); + } + + } + +} diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java new file mode 100644 index 0000000000..6a36d41e19 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.springframework.ai.chat.memory.repository.oracle; + +import org.jspecify.annotations.NullMarked; diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java new file mode 100644 index 0000000000..00c07da3a8 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java @@ -0,0 +1,216 @@ +package org.springframework.ai.chat.memory.repository.oracle; + +import java.util.List; +import java.util.UUID; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.testcontainers.oracle.OracleContainer; + +import org.springframework.ai.chat.memory.ChatMemoryRepository; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link OracleChatMemoryRepository}. + */ +@SpringBootTest(classes = OracleChatMemoryRepositoryIT.TestConfiguration.class) +class OracleChatMemoryRepositoryIT { + + private static final String ORACLE_DATABASE_URL = System.getenv("ORACLE_DATABASE_URL"); + + private static final String ORACLE_DATABASE_USERNAME = System.getenv("ORACLE_DATABASE_USERNAME"); + + private static final String ORACLE_DATABASE_PASSWORD = System.getenv("ORACLE_DATABASE_PASSWORD"); + + private static final boolean USE_EXISTING_DATABASE = StringUtils.hasText(ORACLE_DATABASE_URL); + + private static final OracleContainer ORACLE_CONTAINER = USE_EXISTING_DATABASE ? null + : new OracleContainer("gvenzl/oracle-free:slim-faststart"); + + static { + if (!USE_EXISTING_DATABASE) { + ORACLE_CONTAINER.start(); + } + } + + @Autowired + private ChatMemoryRepository chatMemoryRepository; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @DynamicPropertySource + static void configureDatasourceProperties(DynamicPropertyRegistry registry) { + if (USE_EXISTING_DATABASE) { + registry.add("spring.datasource.url", () -> ORACLE_DATABASE_URL); + registry.add("spring.datasource.username", + () -> StringUtils.hasText(ORACLE_DATABASE_USERNAME) ? ORACLE_DATABASE_USERNAME : "test"); + registry.add("spring.datasource.password", + () -> StringUtils.hasText(ORACLE_DATABASE_PASSWORD) ? ORACLE_DATABASE_PASSWORD : "test"); + } + else { + registry.add("spring.datasource.url", ORACLE_CONTAINER::getJdbcUrl); + registry.add("spring.datasource.username", ORACLE_CONTAINER::getUsername); + registry.add("spring.datasource.password", ORACLE_CONTAINER::getPassword); + } + } + + @AfterAll + static void stopContainer() { + if (ORACLE_CONTAINER != null) { + ORACLE_CONTAINER.stop(); + } + } + + @BeforeEach + void resetSchema() { + this.jdbcTemplate.execute(""" + BEGIN + EXECUTE IMMEDIATE 'DROP TABLE SPRING_AI_CHAT_MEMORY'; + EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN + RAISE; + END IF; + END; + """); + this.jdbcTemplate.execute(""" + CREATE TABLE SPRING_AI_CHAT_MEMORY ( + CONVERSATION_ID VARCHAR2(36 CHAR) NOT NULL, + CONTENT CLOB NOT NULL, + "TYPE" VARCHAR2(10 CHAR) NOT NULL CHECK ("TYPE" IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL')), + "TIMESTAMP" TIMESTAMP NOT NULL + ) + """); + this.jdbcTemplate.execute(""" + CREATE INDEX SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX + ON SPRING_AI_CHAT_MEMORY(CONVERSATION_ID, "TIMESTAMP") + """); + } + + @Test + void saveAndReadConversation() { + var conversationId = UUID.randomUUID().toString(); + var messages = List.of(new UserMessage("user message"), new AssistantMessage("assistant message"), + new SystemMessage("system message")); + + this.chatMemoryRepository.saveAll(conversationId, messages); + + assertThat(this.chatMemoryRepository.findConversationIds()).contains(conversationId); + assertThat(this.chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(messages); + } + + @ParameterizedTest + @CsvSource({ "Message from assistant,ASSISTANT", "Message from user,USER", "Message from system,SYSTEM" }) + void saveSingleMessage(String content, MessageType messageType) { + var conversationId = UUID.randomUUID().toString(); + var message = switch (messageType) { + case ASSISTANT -> new AssistantMessage(content + " - " + conversationId); + case USER -> new UserMessage(content + " - " + conversationId); + case SYSTEM -> new SystemMessage(content + " - " + conversationId); + case TOOL -> throw new IllegalArgumentException("TOOL message type not supported in this test"); + }; + + this.chatMemoryRepository.saveAll(conversationId, List.of(message)); + + assertThat(this.chatMemoryRepository.findConversationIds()).contains(conversationId); + assertThat(this.chatMemoryRepository.findByConversationId(conversationId)).containsExactly(message); + } + + @Test + void saveMultipleMessagesAndOverwriteConversation() { + var conversationId = UUID.randomUUID().toString(); + var messages = List.of(new AssistantMessage("assistant - " + conversationId), + new UserMessage("user - " + conversationId), new SystemMessage("system - " + conversationId)); + + this.chatMemoryRepository.saveAll(conversationId, messages); + assertThat(this.chatMemoryRepository.findByConversationId(conversationId)).containsExactlyElementsOf(messages); + + var replacement = List.of(new UserMessage("replacement")); + this.chatMemoryRepository.saveAll(conversationId, replacement); + assertThat(this.chatMemoryRepository.findByConversationId(conversationId)).containsExactlyElementsOf(replacement); + } + + @Test + void findConversationIdsAcrossConversations() { + var conversationA = UUID.randomUUID().toString(); + var conversationB = UUID.randomUUID().toString(); + + this.chatMemoryRepository.saveAll(conversationA, List.of(new UserMessage("a1"))); + this.chatMemoryRepository.saveAll(conversationB, List.of(new UserMessage("b1"))); + + assertThat(this.chatMemoryRepository.findConversationIds()).contains(conversationA, conversationB); + } + + @Test + void messagesAreReturnedInChronologicalOrder() { + var conversationId = UUID.randomUUID().toString(); + var messages = List.of(new UserMessage("1-first"), new AssistantMessage("2-second"), + new UserMessage("3-third"), new SystemMessage("4-fourth")); + + this.chatMemoryRepository.saveAll(conversationId, messages); + + assertThat(this.chatMemoryRepository.findByConversationId(conversationId)).containsExactlyElementsOf(messages); + } + + @Test + void messagesAreReturnedInOrderForLargeBatch() { + var conversationId = UUID.randomUUID().toString(); + List messages = new java.util.ArrayList<>(); + for (int i = 0; i < 50; i++) { + messages.add(new UserMessage("Message " + i)); + } + + this.chatMemoryRepository.saveAll(conversationId, messages); + var retrieved = this.chatMemoryRepository.findByConversationId(conversationId); + + assertThat(retrieved).hasSize(50); + for (int i = 0; i < 50; i++) { + assertThat(retrieved.get(i).getText()).isEqualTo("Message " + i); + } + } + + @Test + void deleteConversation() { + var conversationId = UUID.randomUUID().toString(); + this.chatMemoryRepository.saveAll(conversationId, List.of(new UserMessage("hello"))); + + this.chatMemoryRepository.deleteByConversationId(conversationId); + + assertThat(this.chatMemoryRepository.findByConversationId(conversationId)).isEmpty(); + } + + @SpringBootConfiguration + @ImportAutoConfiguration({ DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class }) + static class TestConfiguration { + + @Bean + ChatMemoryRepository chatMemoryRepository(DataSource dataSource) { + return OracleChatMemoryRepository.builder().dataSource(dataSource).build(); + } + + } + +} diff --git a/pom.xml b/pom.xml index 09171a9134..848aba2fd3 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,7 @@ memory/repository/spring-ai-model-chat-memory-repository-jdbc memory/repository/spring-ai-model-chat-memory-repository-mongodb memory/repository/spring-ai-model-chat-memory-repository-neo4j + memory/repository/spring-ai-model-chat-memory-repository-oracle memory/repository/spring-ai-model-chat-memory-repository-redis spring-ai-retry diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml index c2da486052..b1d04b2c08 100644 --- a/spring-ai-bom/pom.xml +++ b/spring-ai-bom/pom.xml @@ -213,6 +213,12 @@ ${project.version} + + org.springframework.ai + spring-ai-model-chat-memory-repository-oracle + ${project.version} + + org.springframework.ai spring-ai-model-chat-memory-repository-redis From 4efa8ac8fdee62ed914528b2f917c0b55d595ab8 Mon Sep 17 00:00:00 2001 From: psilberk Date: Mon, 25 May 2026 21:48:46 -0700 Subject: [PATCH 2/5] Fixed format Signed-off-by: psilberk --- .../memory/repository/oracle/OracleChatMemoryRepositoryIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java index 00c07da3a8..2b1211e16a 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java @@ -150,7 +150,8 @@ void saveMultipleMessagesAndOverwriteConversation() { var replacement = List.of(new UserMessage("replacement")); this.chatMemoryRepository.saveAll(conversationId, replacement); - assertThat(this.chatMemoryRepository.findByConversationId(conversationId)).containsExactlyElementsOf(replacement); + assertThat(this.chatMemoryRepository.findByConversationId(conversationId)) + .containsExactlyElementsOf(replacement); } @Test From 687eb0b0683e23146593722ae4a6ef123ee5f3b5 Mon Sep 17 00:00:00 2001 From: psilberk Date: Tue, 26 May 2026 15:07:22 -0700 Subject: [PATCH 3/5] Fixed styles Signed-off-by: psilberk --- .../README.md | 1 + .../oracle/OracleChatMemoryRepository.java | 200 +++++++++++------- .../repository/oracle/package-info.java | 19 ++ .../oracle/OracleChatMemoryRepositoryIT.java | 2 - 4 files changed, 145 insertions(+), 77 deletions(-) diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md b/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md index c92648bc3d..f3cee964ec 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md @@ -9,6 +9,7 @@ If `ORACLE_DATABASE_URL` is set, tests use that database instead of Testcontainers. Optional credentials: + - `ORACLE_DATABASE_USERNAME` - `ORACLE_DATABASE_PASSWORD` diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java index 9a693c21a9..a47bb5195d 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java @@ -1,3 +1,19 @@ +/* + * Copyright 2023-present 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.chat.memory.repository.oracle; import java.util.List; @@ -20,80 +36,114 @@ */ public final class OracleChatMemoryRepository implements ChatMemoryRepository { - private final JdbcChatMemoryRepository delegate; - - private OracleChatMemoryRepository(JdbcChatMemoryRepository delegate) { - this.delegate = delegate; - } - - @Override - public List findConversationIds() { - return this.delegate.findConversationIds(); - } - - @Override - public List findByConversationId(String conversationId) { - return this.delegate.findByConversationId(conversationId); - } - - @Override - public void saveAll(String conversationId, List messages) { - this.delegate.saveAll(conversationId, messages); - } - - @Override - public void deleteByConversationId(String conversationId) { - this.delegate.deleteByConversationId(conversationId); - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - - private @Nullable DataSource dataSource; - - private @Nullable JdbcTemplate jdbcTemplate; - - private @Nullable PlatformTransactionManager transactionManager; - - private Builder() { - } - - public Builder dataSource(DataSource dataSource) { - this.dataSource = dataSource; - return this; - } - - public Builder jdbcTemplate(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - return this; - } - - public Builder transactionManager(PlatformTransactionManager transactionManager) { - this.transactionManager = transactionManager; - return this; - } - - public OracleChatMemoryRepository build() { - var repository = JdbcChatMemoryRepository.builder().dialect(new OracleChatMemoryRepositoryDialect()); - - if (this.dataSource != null) { - repository.dataSource(this.dataSource); - } - - if (this.jdbcTemplate != null) { - repository.jdbcTemplate(this.jdbcTemplate); - } - - if (this.transactionManager != null) { - repository.transactionManager(this.transactionManager); - } - - return new OracleChatMemoryRepository(repository.build()); - } - - } + /** Delegate repository implementation. */ + private final JdbcChatMemoryRepository delegate; + + private OracleChatMemoryRepository( + final JdbcChatMemoryRepository delegateRepository) { + this.delegate = delegateRepository; + } + + @Override + public List findConversationIds() { + return this.delegate.findConversationIds(); + } + + @Override + public List findByConversationId(final String conversationId) { + return this.delegate.findByConversationId(conversationId); + } + + @Override + public void saveAll(final String conversationId, + final List messages) { + this.delegate.saveAll(conversationId, messages); + } + + @Override + public void deleteByConversationId(final String conversationId) { + this.delegate.deleteByConversationId(conversationId); + } + + /** + * Create a builder for {@link OracleChatMemoryRepository}. + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link OracleChatMemoryRepository}. + */ + public static final class Builder { + + /** Data source used by the underlying JDBC repository. */ + private @Nullable DataSource dataSource; + + /** JDBC template used by the underlying JDBC repository. */ + private @Nullable JdbcTemplate jdbcTemplate; + + /** Optional transaction manager used by the repository. */ + private @Nullable PlatformTransactionManager transactionManager; + + private Builder() { + } + + /** + * Set the data source. + * @param dataSourceToUse data source to use + * @return this builder + */ + public Builder dataSource(final DataSource dataSourceToUse) { + this.dataSource = dataSourceToUse; + return this; + } + + /** + * Set the JDBC template. + * @param jdbcTemplateToUse JDBC template to use + * @return this builder + */ + public Builder jdbcTemplate(final JdbcTemplate jdbcTemplateToUse) { + this.jdbcTemplate = jdbcTemplateToUse; + return this; + } + + /** + * Set the transaction manager. + * @param transactionManagerToUse transaction manager to use + * @return this builder + */ + public Builder transactionManager( + final PlatformTransactionManager transactionManagerToUse) { + this.transactionManager = transactionManagerToUse; + return this; + } + + /** + * Build the Oracle chat memory repository. + * @return a new Oracle chat memory repository + */ + public OracleChatMemoryRepository build() { + var repository = JdbcChatMemoryRepository.builder() + .dialect(new OracleChatMemoryRepositoryDialect()); + + if (this.dataSource != null) { + repository.dataSource(this.dataSource); + } + + if (this.jdbcTemplate != null) { + repository.jdbcTemplate(this.jdbcTemplate); + } + + if (this.transactionManager != null) { + repository.transactionManager(this.transactionManager); + } + + return new OracleChatMemoryRepository(repository.build()); + } + + } } diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java index 6a36d41e19..5ebe133259 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/package-info.java @@ -1,3 +1,22 @@ +/* + * Copyright 2023-present 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. + */ + +/** + * Oracle chat memory repository support. + */ @NullMarked package org.springframework.ai.chat.memory.repository.oracle; diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java index 2b1211e16a..2bb2816fd3 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java @@ -211,7 +211,5 @@ static class TestConfiguration { ChatMemoryRepository chatMemoryRepository(DataSource dataSource) { return OracleChatMemoryRepository.builder().dataSource(dataSource).build(); } - } - } From f61e5739bb6947a6b78c65fbfcc0ba8386ba7bce Mon Sep 17 00:00:00 2001 From: psilberk Date: Tue, 26 May 2026 15:20:09 -0700 Subject: [PATCH 4/5] Fixed styles-2 Signed-off-by: psilberk --- .../oracle/OracleChatMemoryRepository.java | 214 +++++++++--------- .../oracle/OracleChatMemoryRepositoryIT.java | 18 ++ 2 files changed, 123 insertions(+), 109 deletions(-) diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java index a47bb5195d..434c9a0a53 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java @@ -36,114 +36,110 @@ */ public final class OracleChatMemoryRepository implements ChatMemoryRepository { - /** Delegate repository implementation. */ - private final JdbcChatMemoryRepository delegate; - - private OracleChatMemoryRepository( - final JdbcChatMemoryRepository delegateRepository) { - this.delegate = delegateRepository; - } - - @Override - public List findConversationIds() { - return this.delegate.findConversationIds(); - } - - @Override - public List findByConversationId(final String conversationId) { - return this.delegate.findByConversationId(conversationId); - } - - @Override - public void saveAll(final String conversationId, - final List messages) { - this.delegate.saveAll(conversationId, messages); - } - - @Override - public void deleteByConversationId(final String conversationId) { - this.delegate.deleteByConversationId(conversationId); - } - - /** - * Create a builder for {@link OracleChatMemoryRepository}. - * @return a new builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Builder for {@link OracleChatMemoryRepository}. - */ - public static final class Builder { - - /** Data source used by the underlying JDBC repository. */ - private @Nullable DataSource dataSource; - - /** JDBC template used by the underlying JDBC repository. */ - private @Nullable JdbcTemplate jdbcTemplate; - - /** Optional transaction manager used by the repository. */ - private @Nullable PlatformTransactionManager transactionManager; - - private Builder() { - } - - /** - * Set the data source. - * @param dataSourceToUse data source to use - * @return this builder - */ - public Builder dataSource(final DataSource dataSourceToUse) { - this.dataSource = dataSourceToUse; - return this; - } - - /** - * Set the JDBC template. - * @param jdbcTemplateToUse JDBC template to use - * @return this builder - */ - public Builder jdbcTemplate(final JdbcTemplate jdbcTemplateToUse) { - this.jdbcTemplate = jdbcTemplateToUse; - return this; - } - - /** - * Set the transaction manager. - * @param transactionManagerToUse transaction manager to use - * @return this builder - */ - public Builder transactionManager( - final PlatformTransactionManager transactionManagerToUse) { - this.transactionManager = transactionManagerToUse; - return this; - } - - /** - * Build the Oracle chat memory repository. - * @return a new Oracle chat memory repository - */ - public OracleChatMemoryRepository build() { - var repository = JdbcChatMemoryRepository.builder() - .dialect(new OracleChatMemoryRepositoryDialect()); - - if (this.dataSource != null) { - repository.dataSource(this.dataSource); - } - - if (this.jdbcTemplate != null) { - repository.jdbcTemplate(this.jdbcTemplate); - } - - if (this.transactionManager != null) { - repository.transactionManager(this.transactionManager); - } - - return new OracleChatMemoryRepository(repository.build()); - } - - } + /** Delegate repository implementation. */ + private final JdbcChatMemoryRepository delegate; + + private OracleChatMemoryRepository(final JdbcChatMemoryRepository delegateRepository) { + this.delegate = delegateRepository; + } + + @Override + public List findConversationIds() { + return this.delegate.findConversationIds(); + } + + @Override + public List findByConversationId(final String conversationId) { + return this.delegate.findByConversationId(conversationId); + } + + @Override + public void saveAll(final String conversationId, final List messages) { + this.delegate.saveAll(conversationId, messages); + } + + @Override + public void deleteByConversationId(final String conversationId) { + this.delegate.deleteByConversationId(conversationId); + } + + /** + * Create a builder for {@link OracleChatMemoryRepository}. + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link OracleChatMemoryRepository}. + */ + public static final class Builder { + + /** Data source used by the underlying JDBC repository. */ + private @Nullable DataSource dataSource; + + /** JDBC template used by the underlying JDBC repository. */ + private @Nullable JdbcTemplate jdbcTemplate; + + /** Optional transaction manager used by the repository. */ + private @Nullable PlatformTransactionManager transactionManager; + + private Builder() { + } + + /** + * Set the data source. + * @param dataSourceToUse data source to use + * @return this builder + */ + public Builder dataSource(final DataSource dataSourceToUse) { + this.dataSource = dataSourceToUse; + return this; + } + + /** + * Set the JDBC template. + * @param jdbcTemplateToUse JDBC template to use + * @return this builder + */ + public Builder jdbcTemplate(final JdbcTemplate jdbcTemplateToUse) { + this.jdbcTemplate = jdbcTemplateToUse; + return this; + } + + /** + * Set the transaction manager. + * @param transactionManagerToUse transaction manager to use + * @return this builder + */ + public Builder transactionManager(final PlatformTransactionManager transactionManagerToUse) { + this.transactionManager = transactionManagerToUse; + return this; + } + + /** + * Build the Oracle chat memory repository. + * @return a new Oracle chat memory repository + */ + public OracleChatMemoryRepository build() { + var repository = JdbcChatMemoryRepository.builder().dialect(new OracleChatMemoryRepositoryDialect()); + + if (this.dataSource != null) { + repository.dataSource(this.dataSource); + } + + if (this.jdbcTemplate != null) { + repository.jdbcTemplate(this.jdbcTemplate); + } + + if (this.transactionManager != null) { + repository.transactionManager(this.transactionManager); + } + + return new OracleChatMemoryRepository(repository.build()); + } + + } } diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java index 2bb2816fd3..8da7014936 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java @@ -1,3 +1,19 @@ +/* + * Copyright 2023-present 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.chat.memory.repository.oracle; import java.util.List; @@ -211,5 +227,7 @@ static class TestConfiguration { ChatMemoryRepository chatMemoryRepository(DataSource dataSource) { return OracleChatMemoryRepository.builder().dataSource(dataSource).build(); } + } + } From 937b2c4656d1118d17058e1250da61e677555bdd Mon Sep 17 00:00:00 2001 From: psilberk Date: Fri, 29 May 2026 13:59:16 -0700 Subject: [PATCH 5/5] Adding options for schema and table name Signed-off-by: psilberk --- .../oracle/OracleChatMemoryRepository.java | 39 ++++++++---- .../oracle/OracleRepositoryDialect.java | 63 +++++++++++++++++++ .../oracle/OracleChatMemoryRepositoryIT.java | 18 +++--- .../oracle/OracleRepositoryDialectTests.java | 56 +++++++++++++++++ 4 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialect.java create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialectTests.java diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java index 434c9a0a53..afa5170fa8 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepository.java @@ -24,10 +24,10 @@ import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; -import org.springframework.ai.chat.memory.repository.jdbc.OracleChatMemoryRepositoryDialect; import org.springframework.ai.chat.messages.Message; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.Assert; /** * An implementation of {@link ChatMemoryRepository} for Oracle. @@ -76,6 +76,8 @@ public static Builder builder() { */ public static final class Builder { + private static final String DEFAULT_TABLE_NAME = "SPRING_AI_CHAT_MEMORY"; + /** Data source used by the underlying JDBC repository. */ private @Nullable DataSource dataSource; @@ -85,36 +87,50 @@ public static final class Builder { /** Optional transaction manager used by the repository. */ private @Nullable PlatformTransactionManager transactionManager; + /** Table name used by the repository. */ + private String tableName = DEFAULT_TABLE_NAME; + private Builder() { } /** * Set the data source. - * @param dataSourceToUse data source to use + * @param dataSource data source to use * @return this builder */ - public Builder dataSource(final DataSource dataSourceToUse) { - this.dataSource = dataSourceToUse; + public Builder dataSource(final DataSource dataSource) { + this.dataSource = dataSource; return this; } /** * Set the JDBC template. - * @param jdbcTemplateToUse JDBC template to use + * @param jdbcTemplate JDBC template to use * @return this builder */ - public Builder jdbcTemplate(final JdbcTemplate jdbcTemplateToUse) { - this.jdbcTemplate = jdbcTemplateToUse; + public Builder jdbcTemplate(final JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; return this; } /** * Set the transaction manager. - * @param transactionManagerToUse transaction manager to use + * @param transactionManager transaction manager to use + * @return this builder + */ + public Builder transactionManager(final PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + return this; + } + + /** + * Set the table name used to store chat memory. + * @param tableName table name to use * @return this builder */ - public Builder transactionManager(final PlatformTransactionManager transactionManagerToUse) { - this.transactionManager = transactionManagerToUse; + public Builder tableName(final String tableName) { + Assert.hasText(tableName, "tableName cannot be null or empty"); + this.tableName = tableName; return this; } @@ -123,7 +139,8 @@ public Builder transactionManager(final PlatformTransactionManager transactionMa * @return a new Oracle chat memory repository */ public OracleChatMemoryRepository build() { - var repository = JdbcChatMemoryRepository.builder().dialect(new OracleChatMemoryRepositoryDialect()); + var dialect = new OracleRepositoryDialect(this.tableName); + var repository = JdbcChatMemoryRepository.builder().dialect(dialect); if (this.dataSource != null) { repository.dataSource(this.dataSource); diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialect.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialect.java new file mode 100644 index 0000000000..5438015400 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/main/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialect.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023-present 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.chat.memory.repository.oracle; + +import java.util.regex.Pattern; + +import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepositoryDialect; +import org.springframework.util.Assert; + +/** + * Oracle SQL dialect for chat memory repository. + * + * @since 2.0.0 + */ +final class OracleRepositoryDialect implements JdbcChatMemoryRepositoryDialect { + + private static final Pattern TABLE_NAME_PATTERN = Pattern + .compile("^[A-Za-z][A-Za-z0-9_$#]*(\\.[A-Za-z][A-Za-z0-9_$#]*)?$"); + + private final String tableName; + + OracleRepositoryDialect(final String tableName) { + Assert.hasText(tableName, "tableName cannot be null or empty"); + Assert.isTrue(TABLE_NAME_PATTERN.matcher(tableName).matches(), + "tableName must be an Oracle identifier or schema-qualified identifier"); + this.tableName = tableName; + } + + @Override + public String getSelectMessagesSql() { + return "SELECT content, type FROM " + this.tableName + " WHERE CONVERSATION_ID = ? ORDER BY \"TIMESTAMP\""; + } + + @Override + public String getInsertMessageSql() { + return "INSERT INTO " + this.tableName + " (CONVERSATION_ID, CONTENT, TYPE, \"TIMESTAMP\") VALUES (?, ?, ?, ?)"; + } + + @Override + public String getSelectConversationIdsSql() { + return "SELECT DISTINCT conversation_id FROM " + this.tableName; + } + + @Override + public String getDeleteMessagesSql() { + return "DELETE FROM " + this.tableName + " WHERE CONVERSATION_ID = ?"; + } + +} diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java index 8da7014936..e6ad240063 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleChatMemoryRepositoryIT.java @@ -54,6 +54,8 @@ @SpringBootTest(classes = OracleChatMemoryRepositoryIT.TestConfiguration.class) class OracleChatMemoryRepositoryIT { + private static final String CHAT_MEMORY_TABLE = "SPRING_AI_CHAT_MEMORY_CUSTOM"; + private static final String ORACLE_DATABASE_URL = System.getenv("ORACLE_DATABASE_URL"); private static final String ORACLE_DATABASE_USERNAME = System.getenv("ORACLE_DATABASE_USERNAME"); @@ -104,26 +106,24 @@ static void stopContainer() { void resetSchema() { this.jdbcTemplate.execute(""" BEGIN - EXECUTE IMMEDIATE 'DROP TABLE SPRING_AI_CHAT_MEMORY'; + EXECUTE IMMEDIATE 'DROP TABLE %s'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END; - """); + """.formatted(CHAT_MEMORY_TABLE)); this.jdbcTemplate.execute(""" - CREATE TABLE SPRING_AI_CHAT_MEMORY ( + CREATE TABLE %s ( CONVERSATION_ID VARCHAR2(36 CHAR) NOT NULL, CONTENT CLOB NOT NULL, "TYPE" VARCHAR2(10 CHAR) NOT NULL CHECK ("TYPE" IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL')), "TIMESTAMP" TIMESTAMP NOT NULL ) - """); - this.jdbcTemplate.execute(""" - CREATE INDEX SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX - ON SPRING_AI_CHAT_MEMORY(CONVERSATION_ID, "TIMESTAMP") - """); + """.formatted(CHAT_MEMORY_TABLE)); + this.jdbcTemplate.execute(("CREATE INDEX " + CHAT_MEMORY_TABLE + "_CONVERSATION_ID_TIMESTAMP_IDX ON " + + CHAT_MEMORY_TABLE + "(CONVERSATION_ID, \"TIMESTAMP\")")); } @Test @@ -225,7 +225,7 @@ static class TestConfiguration { @Bean ChatMemoryRepository chatMemoryRepository(DataSource dataSource) { - return OracleChatMemoryRepository.builder().dataSource(dataSource).build(); + return OracleChatMemoryRepository.builder().dataSource(dataSource).tableName(CHAT_MEMORY_TABLE).build(); } } diff --git a/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialectTests.java b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialectTests.java new file mode 100644 index 0000000000..8986ae19f7 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/src/test/java/org/springframework/ai/chat/memory/repository/oracle/OracleRepositoryDialectTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023-present 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.chat.memory.repository.oracle; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link OracleRepositoryDialect}. + */ +class OracleRepositoryDialectTests { + + @Test + void sqlUsesConfiguredTableName() { + var dialect = new OracleRepositoryDialect("SPRING_AI_CHAT_MEMORY_CUSTOM"); + + assertThat(dialect.getSelectMessagesSql()).contains("SPRING_AI_CHAT_MEMORY_CUSTOM"); + assertThat(dialect.getInsertMessageSql()).contains("SPRING_AI_CHAT_MEMORY_CUSTOM"); + assertThat(dialect.getSelectConversationIdsSql()).contains("SPRING_AI_CHAT_MEMORY_CUSTOM"); + assertThat(dialect.getDeleteMessagesSql()).contains("SPRING_AI_CHAT_MEMORY_CUSTOM"); + } + + @Test + void acceptsSchemaQualifiedTableName() { + var dialect = new OracleRepositoryDialect("APP.SPRING_AI_CHAT_MEMORY"); + + assertThat(dialect.getSelectMessagesSql()).contains("APP.SPRING_AI_CHAT_MEMORY"); + } + + @Test + void rejectsBlankTableName() { + assertThatIllegalArgumentException().isThrownBy(() -> new OracleRepositoryDialect(" ")); + } + + @Test + void rejectsInvalidTableName() { + assertThatIllegalArgumentException().isThrownBy(() -> new OracleRepositoryDialect("bad-name")); + } + +}