diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml index 4eea9995759..38d51b12103 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml @@ -50,13 +50,7 @@ spring-jdbc - - com.zaxxer - HikariCP - - - org.postgresql postgresql @@ -85,6 +79,20 @@ true + + + com.oracle.database.jdbc + ojdbc11 + 23.9.0.25.07 + test + + + + org.springframework.boot + spring-boot-starter-jdbc + test + + org.xerial sqlite-jdbc @@ -111,6 +119,14 @@ test + + + org.testcontainers + oracle-xe + 1.19.0 + test + + org.testcontainers postgresql diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java index 0b9cf1b73ce..22a1d8cf6c2 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java @@ -74,6 +74,7 @@ static JdbcChatMemoryRepositoryDialect from(DataSource dataSource) { case "MySQL", "MariaDB" -> new MysqlChatMemoryRepositoryDialect(); case "Microsoft SQL Server" -> new SqlServerChatMemoryRepositoryDialect(); case "HSQL Database Engine" -> new HsqldbChatMemoryRepositoryDialect(); + case "Oracle" -> new OracleChatMemoryRepositoryDialect(); case "SQLite" -> new SqliteChatMemoryRepositoryDialect(); case "H2" -> new H2ChatMemoryRepositoryDialect(); default -> // Add more as needed diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/OracleChatMemoryRepositoryDialect.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/OracleChatMemoryRepositoryDialect.java new file mode 100644 index 00000000000..a3784509c6e --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/OracleChatMemoryRepositoryDialect.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024-2025 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.jdbc; + +/** + * Dialect for Oracle. + * + * @author Xiaotong Fan + * @since 1.1.0 + */ + +public class OracleChatMemoryRepositoryDialect implements JdbcChatMemoryRepositoryDialect { + + @Override + public String getSelectMessagesSql() { + return "SELECT content, type FROM SPRING_AI_CHAT_MEMORY WHERE CONVERSATION_ID = ? ORDER BY \"TIMESTAMP\""; + } + + @Override + public String getInsertMessageSql() { + return "INSERT INTO SPRING_AI_CHAT_MEMORY (CONVERSATION_ID, CONTENT, TYPE, \"TIMESTAMP\") VALUES (?, ?, ?, ?)"; + } + + @Override + public String getSelectConversationIdsSql() { + return "SELECT DISTINCT conversation_id FROM SPRING_AI_CHAT_MEMORY"; + } + + @Override + public String getDeleteMessagesSql() { + return "DELETE FROM SPRING_AI_CHAT_MEMORY WHERE CONVERSATION_ID = ?"; + } + +} diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/resources/org/springframework/ai/chat/memory/repository/jdbc/schema-oracle.sql b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/resources/org/springframework/ai/chat/memory/repository/jdbc/schema-oracle.sql new file mode 100644 index 00000000000..47a411299b5 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/resources/org/springframework/ai/chat/memory/repository/jdbc/schema-oracle.sql @@ -0,0 +1,21 @@ +DROP TABLE SPRING_AI_CHAT_MEMORY; + +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 +); + +CREATE INDEX SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX + ON SPRING_AI_CHAT_MEMORY( + CONVERSATION_ID, + 'TIMESTAMP' + ); \ No newline at end of file diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryBuilderTests.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryBuilderTests.java index 87dbfa5f03b..80f6a97f156 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryBuilderTests.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryBuilderTests.java @@ -37,6 +37,7 @@ * * @author Mark Pollack * @author Yanming Zhou + * @author Xiaotong Fan */ public class JdbcChatMemoryRepositoryBuilderTests { @@ -136,6 +137,23 @@ void testBuilderWithHsqldbDialectFromDataSource() throws SQLException { assertThat(repository).isNotNull(); } + @Test + void testBuilderWithOracleDialectFromDataSource() throws SQLException { + // Setup mocks for Oracle + DataSource dataSource = mock(DataSource.class); + Connection connection = mock(Connection.class); + DatabaseMetaData metaData = mock(DatabaseMetaData.class); + + when(dataSource.getConnection()).thenReturn(connection); + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn("jdbc:oracle:thin:@//192.168.19.129:1521/ORCL"); + + // Test with dialect from datasource + JdbcChatMemoryRepository repository = JdbcChatMemoryRepository.builder().dataSource(dataSource).build(); + + assertThat(repository).isNotNull(); + } + @Test void testBuilderWithUnknownDialectFromDataSource() throws SQLException { // Setup mocks for unknown database diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryOracleIT.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryOracleIT.java new file mode 100644 index 00000000000..511967d9ee8 --- /dev/null +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryOracleIT.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023-2025 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.jdbc; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.jdbc.Sql; + +/** + * Integration tests for {@link JdbcChatMemoryRepository} with Oracle. + * + * @author Xiaotong Fan + */ + +@SpringBootTest +@TestPropertySource(properties = { "spring.datasource.url=jdbc:oracle:thin:@localhost:1521/FREEPDB1", + "spring.datasource.username=scott", "spring.datasource.password=tiger" }) +@Sql(scripts = "classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-oracle.sql") +public class JdbcChatMemoryRepositoryOracleIT extends AbstractJdbcChatMemoryRepositoryIT { + +} diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc index 4231a2dbcb6..18b28a15969 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc @@ -128,6 +128,7 @@ Spring AI supports multiple relational databases via a dialect abstraction. The - MySQL / MariaDB - SQL Server - HSQLDB +- Oracle Database The correct dialect can be auto-detected from the JDBC URL when using `JdbcChatMemoryRepositoryDialect.from(DataSource)`. You can extend support for other databases by implementing the `JdbcChatMemoryRepositoryDialect` interface.