diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryAutoConfiguration.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryAutoConfiguration.java index a6942b07d87..4bc2d8e1668 100644 --- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryAutoConfiguration.java +++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryAutoConfiguration.java @@ -26,15 +26,17 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.jdbc.core.JdbcTemplate; /** * @author Jonathan Leijendekker * @author Thomas Vitale + * @author Yanming Zhou * @since 1.0.0 */ @AutoConfiguration(after = JdbcTemplateAutoConfiguration.class, before = ChatMemoryAutoConfiguration.class) @@ -52,11 +54,19 @@ JdbcChatMemoryRepository chatMemoryRepository(JdbcTemplate jdbcTemplate) { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = JdbcChatMemoryProperties.CONFIG_PREFIX, name = "initialize-schema", - havingValue = "true", matchIfMissing = true) - JdbcChatMemoryDataSourceScriptDatabaseInitializer jdbcChatMemoryScriptDatabaseInitializer(DataSource dataSource) { + @Conditional(OnJdbcChatMemoryDatasourceInitializationCondition.class) + JdbcChatMemoryDataSourceScriptDatabaseInitializer jdbcChatMemoryScriptDatabaseInitializer(DataSource dataSource, + JdbcChatMemoryProperties properties) { logger.debug("Initializing schema for JdbcChatMemoryRepository"); - return new JdbcChatMemoryDataSourceScriptDatabaseInitializer(dataSource); + return new JdbcChatMemoryDataSourceScriptDatabaseInitializer(dataSource, properties); + } + + static class OnJdbcChatMemoryDatasourceInitializationCondition extends OnDatabaseInitializationCondition { + + OnJdbcChatMemoryDatasourceInitializationCondition() { + super("Jdbc Chat Memory", JdbcChatMemoryProperties.CONFIG_PREFIX + ".initialize-schema"); + } + } } diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializer.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializer.java index 546b272d367..1f0440768d3 100644 --- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializer.java +++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializer.java @@ -22,35 +22,36 @@ import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; import org.springframework.boot.jdbc.init.PlatformPlaceholderDatabaseDriverResolver; -import org.springframework.boot.sql.init.DatabaseInitializationMode; import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.util.StringUtils; /** * Performs database initialization for the JDBC Chat Memory Repository. * + * @author Jonathan Leijendekker + * @author Yanming Zhou * @since 1.0.0 */ class JdbcChatMemoryDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer { - private static final String SCHEMA_LOCATION = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql"; - - JdbcChatMemoryDataSourceScriptDatabaseInitializer(DataSource dataSource) { - super(dataSource, getSettings(dataSource)); + JdbcChatMemoryDataSourceScriptDatabaseInitializer(DataSource dataSource, JdbcChatMemoryProperties properties) { + super(dataSource, getSettings(dataSource, properties)); } - static DatabaseInitializationSettings getSettings(DataSource dataSource) { + static DatabaseInitializationSettings getSettings(DataSource dataSource, JdbcChatMemoryProperties properties) { var settings = new DatabaseInitializationSettings(); - settings.setSchemaLocations(resolveSchemaLocations(dataSource)); - settings.setMode(DatabaseInitializationMode.ALWAYS); + settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties)); + settings.setMode(properties.getInitializeSchema()); settings.setContinueOnError(true); - return settings; } - static List resolveSchemaLocations(DataSource dataSource) { + static List resolveSchemaLocations(DataSource dataSource, JdbcChatMemoryProperties properties) { var platformResolver = new PlatformPlaceholderDatabaseDriverResolver(); - - return platformResolver.resolveAll(dataSource, SCHEMA_LOCATION); + if (StringUtils.hasText(properties.getPlatform())) { + return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema()); + } + return platformResolver.resolveAll(dataSource, properties.getSchema()); } } diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryProperties.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryProperties.java index 6ff1a2a18ba..74a6b2dfd2b 100644 --- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryProperties.java +++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryProperties.java @@ -17,10 +17,12 @@ package org.springframework.ai.model.chat.memory.jdbc.autoconfigure; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.sql.init.DatabaseInitializationMode; /** * @author Jonathan Leijendekker * @author Thomas Vitale + * @author Yanming Zhou * @since 1.0.0 */ @ConfigurationProperties(JdbcChatMemoryProperties.CONFIG_PREFIX) @@ -28,16 +30,45 @@ public class JdbcChatMemoryProperties { public static final String CONFIG_PREFIX = "spring.ai.chat.memory.repository.jdbc"; + private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql"; + + /** + * Path to the SQL file to use to initialize the database schema. + */ + private String schema = DEFAULT_SCHEMA_LOCATION; + /** - * Whether to initialize the schema on startup. + * Platform to use in initialization scripts if the @@platform@@ placeholder is used. + * Auto-detected by default. */ - private boolean initializeSchema = true; + private String platform; + + /** + * Database schema initialization mode. + */ + private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED; + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } - public boolean isInitializeSchema() { + public DatabaseInitializationMode getInitializeSchema() { return this.initializeSchema; } - public void setInitializeSchema(boolean initializeSchema) { + public void setInitializeSchema(DatabaseInitializationMode initializeSchema) { this.initializeSchema = initializeSchema; } diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests.java index cd53f2bd8dd..d18b6faa60f 100644 --- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests.java +++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests.java @@ -33,6 +33,7 @@ /** * @author Jonathan Leijendekker + * @author Yanming Zhou */ @Testcontainers class JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests { @@ -57,7 +58,8 @@ class JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests { void getSettings_shouldHaveSchemaLocations() { this.contextRunner.run(context -> { var dataSource = context.getBean(DataSource.class); - var settings = JdbcChatMemoryDataSourceScriptDatabaseInitializer.getSettings(dataSource); + var properties = context.getBean(JdbcChatMemoryProperties.class); + var settings = JdbcChatMemoryDataSourceScriptDatabaseInitializer.getSettings(dataSource, properties); assertThat(settings.getSchemaLocations()) .containsOnly("classpath:org/springframework/ai/chat/memory/jdbc/schema-postgresql.sql"); diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java index 5961f25ee6e..9d4ee31e441 100644 --- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java +++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java @@ -38,6 +38,7 @@ * @author Jonathan Leijendekker * @author Thomas Vitale * @author Linar Abzaltdinov + * @author Yanming Zhou */ class JdbcChatMemoryPostgresqlAutoConfigurationIT { @@ -48,84 +49,85 @@ class JdbcChatMemoryPostgresqlAutoConfigurationIT { @Test void jdbcChatMemoryScriptDatabaseInitializer_shouldBeLoaded() { - this.contextRunner.withPropertyValues("spring.ai.chat.memory.jdbc.initialize-schema=true") - .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue()); - this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=true") + this.contextRunner.withPropertyValues(JdbcChatMemoryProperties.CONFIG_PREFIX + ".initialize-schema=always") .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue()); } @Test void jdbcChatMemoryScriptDatabaseInitializer_shouldNotBeLoaded() { - this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=false") + this.contextRunner.withPropertyValues(JdbcChatMemoryProperties.CONFIG_PREFIX + ".initialize-schema=never") .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isFalse()); } @Test void initializeSchemaEnabledWithProperty() { - this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=true") + this.contextRunner.withPropertyValues(JdbcChatMemoryProperties.CONFIG_PREFIX + ".initialize-schema=always") .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue()); } @Test void useAutoConfiguredJdbcChatMemoryRepository() { - this.contextRunner.run(context -> { - var chatMemoryRepository = context.getBean(JdbcChatMemoryRepository.class); - var conversationId = UUID.randomUUID().toString(); - var userMessage = new UserMessage("Message from the user"); + this.contextRunner.withPropertyValues(JdbcChatMemoryProperties.CONFIG_PREFIX + ".initialize-schema=always") + .run(context -> { + var chatMemoryRepository = context.getBean(JdbcChatMemoryRepository.class); + var conversationId = UUID.randomUUID().toString(); + var userMessage = new UserMessage("Message from the user"); - chatMemoryRepository.saveAll(conversationId, List.of(userMessage)); + chatMemoryRepository.saveAll(conversationId, List.of(userMessage)); - assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(1); - assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(List.of(userMessage)); + assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(1); + assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(List.of(userMessage)); - chatMemoryRepository.deleteByConversationId(conversationId); + chatMemoryRepository.deleteByConversationId(conversationId); - assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEmpty(); + assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEmpty(); - var multipleMessages = List.of(new UserMessage("Message from the user 1"), - new AssistantMessage("Message from the assistant 1")); + var multipleMessages = List.of(new UserMessage("Message from the user 1"), + new AssistantMessage("Message from the assistant 1")); - chatMemoryRepository.saveAll(conversationId, multipleMessages); + chatMemoryRepository.saveAll(conversationId, multipleMessages); - assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(multipleMessages.size()); - assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(multipleMessages); - }); + assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(multipleMessages.size()); + assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(multipleMessages); + }); } @Test void useAutoConfiguredChatMemoryWithJdbc() { - this.contextRunner.withConfiguration(AutoConfigurations.of(ChatMemoryAutoConfiguration.class)).run(context -> { - assertThat(context).hasSingleBean(ChatMemory.class); - assertThat(context).hasSingleBean(JdbcChatMemoryRepository.class); + this.contextRunner.withPropertyValues(JdbcChatMemoryProperties.CONFIG_PREFIX + ".initialize-schema=always") + .withConfiguration(AutoConfigurations.of(ChatMemoryAutoConfiguration.class)) + .run(context -> { + assertThat(context).hasSingleBean(ChatMemory.class); + assertThat(context).hasSingleBean(JdbcChatMemoryRepository.class); - var chatMemory = context.getBean(ChatMemory.class); - var conversationId = UUID.randomUUID().toString(); - var userMessage = new UserMessage("Message from the user"); + var chatMemory = context.getBean(ChatMemory.class); + var conversationId = UUID.randomUUID().toString(); + var userMessage = new UserMessage("Message from the user"); - chatMemory.add(conversationId, userMessage); + chatMemory.add(conversationId, userMessage); - assertThat(chatMemory.get(conversationId)).hasSize(1); - assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage)); + assertThat(chatMemory.get(conversationId)).hasSize(1); + assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage)); - var assistantMessage = new AssistantMessage("Message from the assistant"); + var assistantMessage = new AssistantMessage("Message from the assistant"); - chatMemory.add(conversationId, List.of(assistantMessage)); + chatMemory.add(conversationId, List.of(assistantMessage)); - assertThat(chatMemory.get(conversationId)).hasSize(2); - assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage, assistantMessage)); + assertThat(chatMemory.get(conversationId)).hasSize(2); + assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage, assistantMessage)); - chatMemory.clear(conversationId); + chatMemory.clear(conversationId); - assertThat(chatMemory.get(conversationId)).isEmpty(); + assertThat(chatMemory.get(conversationId)).isEmpty(); - var multipleMessages = List.of(new UserMessage("Message from the user 1"), - new AssistantMessage("Message from the assistant 1")); + var multipleMessages = List.of(new UserMessage("Message from the user 1"), + new AssistantMessage("Message from the assistant 1")); - chatMemory.add(conversationId, multipleMessages); + chatMemory.add(conversationId, multipleMessages); - assertThat(chatMemory.get(conversationId)).hasSize(multipleMessages.size()); - assertThat(chatMemory.get(conversationId)).isEqualTo(multipleMessages); - }); + assertThat(chatMemory.get(conversationId)).hasSize(multipleMessages.size()); + assertThat(chatMemory.get(conversationId)).isEqualTo(multipleMessages); + }); } } diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPropertiesTests.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPropertiesTests.java index 196176149ff..5c29f137fca 100644 --- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPropertiesTests.java +++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPropertiesTests.java @@ -17,11 +17,13 @@ package org.springframework.ai.model.chat.memory.jdbc.autoconfigure; import org.junit.jupiter.api.Test; +import org.springframework.boot.sql.init.DatabaseInitializationMode; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jonathan Leijendekker + * @author Yanming Zhou */ class JdbcChatMemoryPropertiesTests { @@ -29,15 +31,15 @@ class JdbcChatMemoryPropertiesTests { void defaultValues() { var props = new JdbcChatMemoryProperties(); - assertThat(props.isInitializeSchema()).isTrue(); + assertThat(props.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.EMBEDDED); } @Test void customValues() { var props = new JdbcChatMemoryProperties(); - props.setInitializeSchema(false); + props.setInitializeSchema(DatabaseInitializationMode.ALWAYS); - assertThat(props.isInitializeSchema()).isFalse(); + assertThat(props.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.ALWAYS); } }