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..f3cee964ec
--- /dev/null
+++ b/memory/repository/spring-ai-model-chat-memory-repository-oracle/README.md
@@ -0,0 +1,30 @@
+# 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..afa5170fa8
--- /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,162 @@
+/*
+ * 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;
+
+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.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.
+ *
+ * @since 2.0.0
+ */
+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 {
+
+ private static final String DEFAULT_TABLE_NAME = "SPRING_AI_CHAT_MEMORY";
+
+ /** 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;
+
+ /** Table name used by the repository. */
+ private String tableName = DEFAULT_TABLE_NAME;
+
+ private Builder() {
+ }
+
+ /**
+ * Set the data source.
+ * @param dataSource data source to use
+ * @return this builder
+ */
+ public Builder dataSource(final DataSource dataSource) {
+ this.dataSource = dataSource;
+ return this;
+ }
+
+ /**
+ * Set the JDBC template.
+ * @param jdbcTemplate JDBC template to use
+ * @return this builder
+ */
+ public Builder jdbcTemplate(final JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ return this;
+ }
+
+ /**
+ * Set the transaction manager.
+ * @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 tableName(final String tableName) {
+ Assert.hasText(tableName, "tableName cannot be null or empty");
+ this.tableName = tableName;
+ return this;
+ }
+
+ /**
+ * Build the Oracle chat memory repository.
+ * @return a new Oracle chat memory repository
+ */
+ public OracleChatMemoryRepository build() {
+ var dialect = new OracleRepositoryDialect(this.tableName);
+ var repository = JdbcChatMemoryRepository.builder().dialect(dialect);
+
+ 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/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/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..5ebe133259
--- /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,23 @@
+/*
+ * 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;
+
+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..e6ad240063
--- /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,233 @@
+/*
+ * 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;
+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 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");
+
+ 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 %s';
+ EXCEPTION
+ WHEN OTHERS THEN
+ IF SQLCODE != -942 THEN
+ RAISE;
+ END IF;
+ END;
+ """.formatted(CHAT_MEMORY_TABLE));
+ this.jdbcTemplate.execute("""
+ 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
+ )
+ """.formatted(CHAT_MEMORY_TABLE));
+ this.jdbcTemplate.execute(("CREATE INDEX " + CHAT_MEMORY_TABLE + "_CONVERSATION_ID_TIMESTAMP_IDX ON "
+ + CHAT_MEMORY_TABLE + "(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).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"));
+ }
+
+}
diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml
index b4a2ae0ea9..a8ee7243d3 100644
--- a/spring-ai-bom/pom.xml
+++ b/spring-ai-bom/pom.xml
@@ -247,6 +247,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