Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.ai.chat.memory.jdbc.JdbcChatMemoryDialect;
import org.springframework.ai.chat.memory.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration;
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.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)
Expand All @@ -51,9 +51,19 @@ JdbcChatMemoryRepository jdbcChatMemoryRepository(JdbcTemplate jdbcTemplate, Dat

@Bean
@ConditionalOnMissingBean
@Conditional(OnJdbcChatMemoryRepositoryDatasourceInitializationCondition.class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should merge this part into main

JdbcChatMemoryRepositorySchemaInitializer jdbcChatMemoryScriptDatabaseInitializer(DataSource dataSource,
JdbcChatMemoryRepositoryProperties properties) {
return new JdbcChatMemoryRepositorySchemaInitializer(dataSource, properties);
}

static class OnJdbcChatMemoryRepositoryDatasourceInitializationCondition extends OnDatabaseInitializationCondition {

OnJdbcChatMemoryRepositoryDatasourceInitializationCondition() {
super("Jdbc Chat Memory Repository",
JdbcChatMemoryRepositoryProperties.CONFIG_PREFIX + ".initialize-schema");
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@
package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have decided that the core of Spring AI will not depend on spring boot, for the chat repository work, we created our own enum for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, my bad, I mean thtat for the non autoconfig code.


/**
* @author Jonathan Leijendekker
* @author Thomas Vitale
* @author Yanming Zhou
* @since 1.0.0
*/
@ConfigurationProperties(JdbcChatMemoryRepositoryProperties.CONFIG_PREFIX)
public class JdbcChatMemoryRepositoryProperties {

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";

/**
* Whether to initialize the schema on startup. Values: embedded, always, never.
* Default is embedded.
Expand All @@ -38,7 +42,13 @@ public class JdbcChatMemoryRepositoryProperties {
* Locations of schema (DDL) scripts. Supports comma-separated list. Default is
* classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql
*/
private String schema = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql";
private String schema = DEFAULT_SCHEMA_LOCATION;

/**
* Platform to use in initialization scripts if the @@platform@@ placeholder is used.
* Auto-detected by default.
*/
private String platform;

public DatabaseInitializationMode getInitializeSchema() {
return this.initializeSchema;
Expand All @@ -48,6 +58,14 @@ public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
this.initializeSchema = initializeSchema;
}

public String getPlatform() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch.

return platform;
}

public void setPlatform(String platform) {
this.platform = platform;
}

public String getSchema() {
return this.schema;
}
Expand All @@ -56,23 +74,4 @@ public void setSchema(String schema) {
this.schema = schema;
}

public enum DatabaseInitializationMode {

/**
* Always initialize the database.
*/
ALWAYS,

/**
* Only initialize an embedded database.
*/
EMBEDDED,

/**
* Never initialize the database.
*/
NEVER

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,38 @@

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 Mark Pollack
* @author Yanming Zhou
* @since 1.0.0
*/
class JdbcChatMemoryRepositorySchemaInitializer extends DataSourceScriptDatabaseInitializer {

private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql";

JdbcChatMemoryRepositorySchemaInitializer(DataSource dataSource, JdbcChatMemoryRepositoryProperties properties) {
super(dataSource, getSettings(dataSource, properties));
}

static DatabaseInitializationSettings getSettings(DataSource dataSource,
JdbcChatMemoryRepositoryProperties properties) {
var settings = new DatabaseInitializationSettings();

// Determine schema locations
String schemaProp = properties.getSchema();
List<String> schemaLocations;
PlatformPlaceholderDatabaseDriverResolver resolver = new PlatformPlaceholderDatabaseDriverResolver();
try {
String url = dataSource.getConnection().getMetaData().getURL().toLowerCase();
if (url.contains("hsqldb")) {
schemaLocations = List.of("classpath:org/springframework/ai/chat/memory/jdbc/schema-hsqldb.sql");
}
else if (StringUtils.hasText(schemaProp)) {
schemaLocations = resolver.resolveAll(dataSource, schemaProp);
}
else {
schemaLocations = resolver.resolveAll(dataSource, DEFAULT_SCHEMA_LOCATION);
}
}
catch (Exception e) {
// fallback to default
if (StringUtils.hasText(schemaProp)) {
schemaLocations = resolver.resolveAll(dataSource, schemaProp);
}
else {
schemaLocations = resolver.resolveAll(dataSource, DEFAULT_SCHEMA_LOCATION);
}
}
settings.setSchemaLocations(schemaLocations);

// Determine initialization mode
JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode init = properties.getInitializeSchema();
DatabaseInitializationMode mode;
if (JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.ALWAYS.equals(init)) {
mode = DatabaseInitializationMode.ALWAYS;
}
else if (JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER.equals(init)) {
mode = DatabaseInitializationMode.NEVER;
}
else {
// embedded or default
mode = DatabaseInitializationMode.EMBEDDED;
}
settings.setMode(mode);
settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not able to get the hsqldb autoconfiguration working with this, which is why there is all this code testing the URL. I agree this code in main needs to be improved, but without some more work on a better test that shows the hsqldb can automagically be configured, the current hack works.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what I did wrong, as I had added all this extra code. Your PR is working, though i can't figure out what I did wrong ;)

settings.setMode(properties.getInitializeSchema());
settings.setContinueOnError(true);
return settings;
}

private static List<String> resolveSchemaLocations(DataSource dataSource,
JdbcChatMemoryRepositoryProperties properties) {
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
if (StringUtils.hasText(properties.getPlatform())) {
return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema());
}
return platformResolver.resolveAll(dataSource, properties.getSchema());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
* @author Jonathan Leijendekker
* @author Thomas Vitale
* @author Linar Abzaltdinov
* @author Yanming Zhou
*/
class JdbcChatMemoryPostgresqlAutoConfigurationIT {

Expand All @@ -49,14 +50,14 @@ class JdbcChatMemoryPostgresqlAutoConfigurationIT {
@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldBeLoaded() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}

@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldNotRunSchemaInit() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=never")
.run(context -> {
assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue();
assertThat(context).doesNotHaveBean("jdbcChatMemoryScriptDatabaseInitializer");
// Optionally, check that the schema is not initialized (could check table
// absence if needed)
});
Expand All @@ -65,7 +66,7 @@ void jdbcChatMemoryScriptDatabaseInitializer_shouldNotRunSchemaInit() {
@Test
void initializeSchemaEmbeddedDefault() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import org.junit.jupiter.api.Test;

import org.springframework.boot.sql.init.DatabaseInitializationMode;

import static org.assertj.core.api.Assertions.assertThat;

/**
Expand All @@ -28,16 +30,14 @@ class JdbcChatMemoryRepositoryPropertiesTests {
@Test
void defaultValues() {
var props = new JdbcChatMemoryRepositoryProperties();
assertThat(props.getInitializeSchema())
.isEqualTo(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.EMBEDDED);
assertThat(props.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.EMBEDDED);
}

@Test
void customValues() {
var props = new JdbcChatMemoryRepositoryProperties();
props.setInitializeSchema(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER);
assertThat(props.getInitializeSchema())
.isEqualTo(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER);
props.setInitializeSchema(DatabaseInitializationMode.NEVER);
assertThat(props.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.NEVER);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,21 @@ class JdbcChatMemorySqlServerAutoConfigurationIT {
@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldBeLoaded() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}

@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldNotRunSchemaInit() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=never")
.run(context -> {
assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue();
assertThat(context).doesNotHaveBean("jdbcChatMemoryScriptDatabaseInitializer");
});
}

@Test
void initializeSchemaEmbeddedDefault() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}

@Test
Expand Down