> processResults(ResultSet resultSet, EmbeddingSearchRequest request)
+ throws SQLException;
+
+ /**
+ * Converts a filter to a WHERE clause.
+ * This is a hook that may be overridden by subclasses.
+ *
+ * @param filter The filter to convert
+ * @return The WHERE clause, or null if no filter is applied
+ */
+ protected String buildWhereClause(Filter filter) {
+ return null;
+ }
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/FilterVisitor.java b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/FilterVisitor.java
new file mode 100644
index 00000000..701f6be3
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/FilterVisitor.java
@@ -0,0 +1,109 @@
+package dev.langchain4j.community.store.embedding.oceanbase.sql;
+
+import dev.langchain4j.store.embedding.filter.comparison.IsEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsGreaterThan;
+import dev.langchain4j.store.embedding.filter.comparison.IsGreaterThanOrEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsIn;
+import dev.langchain4j.store.embedding.filter.comparison.IsLessThan;
+import dev.langchain4j.store.embedding.filter.comparison.IsLessThanOrEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsNotEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsNotIn;
+import dev.langchain4j.store.embedding.filter.logical.And;
+import dev.langchain4j.store.embedding.filter.logical.Not;
+import dev.langchain4j.store.embedding.filter.logical.Or;
+
+/**
+ * Visitor interface for Filter objects.
+ * Implements the Visitor pattern to handle different filter types
+ * without using instanceof checks and type casting.
+ */
+public interface FilterVisitor {
+
+ /**
+ * Visit an IsEqualTo filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsEqualTo filter);
+
+ /**
+ * Visit an IsNotEqualTo filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsNotEqualTo filter);
+
+ /**
+ * Visit an IsGreaterThan filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsGreaterThan filter);
+
+ /**
+ * Visit an IsGreaterThanOrEqualTo filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsGreaterThanOrEqualTo filter);
+
+ /**
+ * Visit an IsLessThan filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsLessThan filter);
+
+ /**
+ * Visit an IsLessThanOrEqualTo filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsLessThanOrEqualTo filter);
+
+ /**
+ * Visit an IsIn filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsIn filter);
+
+ /**
+ * Visit an IsNotIn filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(IsNotIn filter);
+
+ /**
+ * Visit an And filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(And filter);
+
+ /**
+ * Visit an Or filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(Or filter);
+
+ /**
+ * Visit a Not filter.
+ *
+ * @param filter The filter to visit
+ * @return The resulting SQL filter
+ */
+ SQLFilter visit(Not filter);
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilter.java b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilter.java
new file mode 100644
index 00000000..5451119c
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilter.java
@@ -0,0 +1,37 @@
+package dev.langchain4j.community.store.embedding.oceanbase.sql;
+
+import dev.langchain4j.store.embedding.filter.Filter;
+
+/**
+ * Transforms a metadata {@link Filter} into a SQL expression that can be used in a WHERE clause.
+ */
+public interface SQLFilter {
+
+ /**
+ * Returns a SQL expression that can be used in a WHERE clause to filter results based on metadata values.
+ * The returned SQL expression should evaluate to true when a row meets the criteria of the metadata filter,
+ * and false otherwise.
+ *
+ * @return A SQL expression, or null if this filter is equivalent to "always include" all rows.
+ */
+ String toSql();
+
+ /**
+ * Returns true if the SQL filter is guaranteed to match all rows, or false otherwise.
+ * When a SQL filter matches all rows (e.g., as if there were no filter), the result of
+ * {@link #toSql()} may be null or an expression which always evaluates to true, such as "1=1".
+ *
+ * When {@link #toSql()} is null, the SQL filter MUST match all rows, and this method MUST return true.
+ *
+ * @return True if this SQL filter matches all rows, or false if it might exclude some rows.
+ */
+ boolean matchesAllRows();
+
+ /**
+ * Returns true if this SQL filter is guaranteed to match no rows. When a SQL filter matches no rows,
+ * the result of {@link #toSql()} should be an expression which always evaluates to false, such as "1=0".
+ *
+ * @return True if this SQL filter matches no rows, or false if it might include some rows.
+ */
+ boolean matchesNoRows();
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilterFactory.java b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilterFactory.java
new file mode 100644
index 00000000..a9fbfb34
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilterFactory.java
@@ -0,0 +1,27 @@
+package dev.langchain4j.community.store.embedding.oceanbase.sql;
+
+import dev.langchain4j.store.embedding.filter.Filter;
+import java.util.function.BiFunction;
+
+/**
+ * Factory for creating SQL filters.
+ * Implements the Factory Method pattern.
+ */
+public class SQLFilterFactory {
+
+ /**
+ * Creates a SQLFilter from a Filter and key mapper.
+ *
+ * @param filter The filter to convert
+ * @param keyMapper Function that maps a key and value to a SQL column expression
+ * @return A SQLFilter representing the given filter
+ */
+ public static SQLFilter create(Filter filter, BiFunction keyMapper) {
+ if (filter == null) {
+ return SQLFilters.matchAllRows();
+ }
+
+ SQLFilterVisitor visitor = new SQLFilterVisitor(keyMapper);
+ return visitor.process(filter);
+ }
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilterVisitor.java b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilterVisitor.java
new file mode 100644
index 00000000..31a628c5
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilterVisitor.java
@@ -0,0 +1,245 @@
+package dev.langchain4j.community.store.embedding.oceanbase.sql;
+
+import dev.langchain4j.community.store.embedding.oceanbase.sql.SQLFilters.MatchAllSQLFilter;
+import dev.langchain4j.community.store.embedding.oceanbase.sql.SQLFilters.MatchNoSQLFilter;
+import dev.langchain4j.community.store.embedding.oceanbase.sql.SQLFilters.SimpleSQLFilter;
+import dev.langchain4j.store.embedding.filter.Filter;
+import dev.langchain4j.store.embedding.filter.comparison.IsEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsGreaterThan;
+import dev.langchain4j.store.embedding.filter.comparison.IsGreaterThanOrEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsIn;
+import dev.langchain4j.store.embedding.filter.comparison.IsLessThan;
+import dev.langchain4j.store.embedding.filter.comparison.IsLessThanOrEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsNotEqualTo;
+import dev.langchain4j.store.embedding.filter.comparison.IsNotIn;
+import dev.langchain4j.store.embedding.filter.logical.And;
+import dev.langchain4j.store.embedding.filter.logical.Not;
+import dev.langchain4j.store.embedding.filter.logical.Or;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/**
+ * Concrete visitor implementation that converts Filter objects to SQLFilter objects.
+ * Implements the Visitor pattern.
+ */
+public class SQLFilterVisitor implements FilterVisitor {
+ private final BiFunction keyMapper;
+
+ /**
+ * Creates a new SQLFilterVisitor with the given key mapper.
+ *
+ * @param keyMapper Function that maps a key and value to a SQL column expression
+ */
+ public SQLFilterVisitor(BiFunction keyMapper) {
+ this.keyMapper = keyMapper;
+ }
+
+ /**
+ * Helper method to convert a value to its SQL string representation.
+ *
+ * @param value The value to convert
+ * @return SQL string representation of the value
+ */
+ private String valueToSql(Object value) {
+ if (value == null) {
+ return "NULL";
+ } else if (value instanceof String) {
+ return "'" + ((String) value).replace("'", "''") + "'";
+ } else if (value instanceof Number || value instanceof Boolean) {
+ return value.toString();
+ } else {
+ return "'" + value.toString().replace("'", "''") + "'";
+ }
+ }
+
+ @Override
+ public SQLFilter visit(IsEqualTo filter) {
+ String expr = keyMapper.apply(filter.key(), filter.comparisonValue());
+ Object value = filter.comparisonValue();
+
+ if (value == null) {
+ return new SimpleSQLFilter(expr + " IS NULL");
+ } else {
+ return new SimpleSQLFilter(expr + " = " + valueToSql(value));
+ }
+ }
+
+ @Override
+ public SQLFilter visit(IsNotEqualTo filter) {
+ String expr = keyMapper.apply(filter.key(), filter.comparisonValue());
+ Object value = filter.comparisonValue();
+
+ if (value == null) {
+ return new SimpleSQLFilter(expr + " IS NOT NULL");
+ } else {
+ return new SimpleSQLFilter("(" + expr + " <> " + valueToSql(value) + " OR " + expr + " IS NULL)");
+ }
+ }
+
+ @Override
+ public SQLFilter visit(IsGreaterThan filter) {
+ String expr = keyMapper.apply(filter.key(), filter.comparisonValue());
+ return new SimpleSQLFilter(expr + " > " + valueToSql(filter.comparisonValue()));
+ }
+
+ @Override
+ public SQLFilter visit(IsGreaterThanOrEqualTo filter) {
+ String expr = keyMapper.apply(filter.key(), filter.comparisonValue());
+ return new SimpleSQLFilter(expr + " >= " + valueToSql(filter.comparisonValue()));
+ }
+
+ @Override
+ public SQLFilter visit(IsLessThan filter) {
+ String expr = keyMapper.apply(filter.key(), filter.comparisonValue());
+ return new SimpleSQLFilter(expr + " < " + valueToSql(filter.comparisonValue()));
+ }
+
+ @Override
+ public SQLFilter visit(IsLessThanOrEqualTo filter) {
+ String expr = keyMapper.apply(filter.key(), filter.comparisonValue());
+ return new SimpleSQLFilter(expr + " <= " + valueToSql(filter.comparisonValue()));
+ }
+
+ @Override
+ public SQLFilter visit(IsIn filter) {
+ Collection> values = filter.comparisonValues();
+ if (values == null || values.isEmpty()) {
+ return new MatchNoSQLFilter();
+ }
+
+ List valueStrings = new ArrayList<>(values.size());
+ for (Object value : values) {
+ valueStrings.add(valueToSql(value));
+ }
+
+ String expr = keyMapper.apply(filter.key(), values.iterator().next());
+ return new SimpleSQLFilter(expr + " IN (" + String.join(", ", valueStrings) + ")");
+ }
+
+ @Override
+ public SQLFilter visit(IsNotIn filter) {
+ Collection> values = filter.comparisonValues();
+ if (values == null || values.isEmpty()) {
+ return new MatchAllSQLFilter();
+ }
+
+ List valueStrings = new ArrayList<>(values.size());
+ for (Object value : values) {
+ valueStrings.add(valueToSql(value));
+ }
+
+ String expr = keyMapper.apply(filter.key(), values.iterator().next());
+ return new SimpleSQLFilter(
+ "(" + expr + " NOT IN (" + String.join(", ", valueStrings) + ") OR " + expr + " IS NULL)");
+ }
+
+ @Override
+ public SQLFilter visit(And filter) {
+ SQLFilter leftFilter = process(filter.left());
+
+ // If left side matches no rows, the entire AND also matches no rows
+ if (leftFilter.matchesNoRows()) {
+ return new MatchNoSQLFilter();
+ }
+
+ // If left side matches all rows, the result depends on the right side
+ if (leftFilter.matchesAllRows()) {
+ return process(filter.right());
+ }
+
+ SQLFilter rightFilter = process(filter.right());
+
+ // If right side matches no rows, the entire AND also matches no rows
+ if (rightFilter.matchesNoRows()) {
+ return new MatchNoSQLFilter();
+ }
+
+ // If right side matches all rows, the result depends on the left side
+ if (rightFilter.matchesAllRows()) {
+ return leftFilter;
+ }
+
+ // Both sides need to participate in the AND operation
+ return new SimpleSQLFilter("(" + leftFilter.toSql() + ") AND (" + rightFilter.toSql() + ")");
+ }
+
+ @Override
+ public SQLFilter visit(Or filter) {
+ SQLFilter leftFilter = process(filter.left());
+
+ // If left side matches all rows, the entire OR also matches all rows
+ if (leftFilter.matchesAllRows()) {
+ return new MatchAllSQLFilter();
+ }
+
+ // If left side matches no rows, the result depends on the right side
+ if (leftFilter.matchesNoRows()) {
+ return process(filter.right());
+ }
+
+ SQLFilter rightFilter = process(filter.right());
+
+ // If right side matches all rows, the entire OR also matches all rows
+ if (rightFilter.matchesAllRows()) {
+ return new MatchAllSQLFilter();
+ }
+
+ // If right side matches no rows, the result depends on the left side
+ if (rightFilter.matchesNoRows()) {
+ return leftFilter;
+ }
+
+ // Both sides need to participate in the OR operation
+ return new SimpleSQLFilter("(" + leftFilter.toSql() + ") OR (" + rightFilter.toSql() + ")");
+ }
+
+ @Override
+ public SQLFilter visit(Not filter) {
+ SQLFilter expressionFilter = process(filter.expression());
+
+ if (expressionFilter.matchesAllRows()) {
+ return new MatchNoSQLFilter();
+ } else if (expressionFilter.matchesNoRows()) {
+ return new MatchAllSQLFilter();
+ } else {
+ return new SimpleSQLFilter("NOT (" + expressionFilter.toSql() + ")");
+ }
+ }
+
+ /**
+ * Process any Filter object by making it accept this visitor.
+ *
+ * @param filter The filter to process
+ * @return The resulting SQLFilter
+ */
+ public SQLFilter process(Filter filter) {
+ if (filter instanceof IsEqualTo) {
+ return visit((IsEqualTo) filter);
+ } else if (filter instanceof IsNotEqualTo) {
+ return visit((IsNotEqualTo) filter);
+ } else if (filter instanceof IsGreaterThan) {
+ return visit((IsGreaterThan) filter);
+ } else if (filter instanceof IsGreaterThanOrEqualTo) {
+ return visit((IsGreaterThanOrEqualTo) filter);
+ } else if (filter instanceof IsLessThan) {
+ return visit((IsLessThan) filter);
+ } else if (filter instanceof IsLessThanOrEqualTo) {
+ return visit((IsLessThanOrEqualTo) filter);
+ } else if (filter instanceof IsIn) {
+ return visit((IsIn) filter);
+ } else if (filter instanceof IsNotIn) {
+ return visit((IsNotIn) filter);
+ } else if (filter instanceof And) {
+ return visit((And) filter);
+ } else if (filter instanceof Or) {
+ return visit((Or) filter);
+ } else if (filter instanceof Not) {
+ return visit((Not) filter);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported filter type: " + filter.getClass().getName());
+ }
+ }
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilters.java b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilters.java
new file mode 100644
index 00000000..1e2cafd3
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SQLFilters.java
@@ -0,0 +1,106 @@
+package dev.langchain4j.community.store.embedding.oceanbase.sql;
+
+/**
+ * Factory class for creating SQL filter instances.
+ */
+public class SQLFilters {
+
+ /**
+ * Creates a SQLFilter that matches all rows.
+ *
+ * @return A SQLFilter that matches all rows
+ */
+ public static SQLFilter matchAllRows() {
+ return new MatchAllSQLFilter();
+ }
+
+ /**
+ * Creates a SQLFilter that matches no rows.
+ *
+ * @return A SQLFilter that matches no rows
+ */
+ public static SQLFilter matchNoRows() {
+ return new MatchNoSQLFilter();
+ }
+
+ /**
+ * Creates a SQLFilter with a simple SQL expression.
+ *
+ * @param sql The SQL expression
+ * @return A SQLFilter with the given SQL expression
+ */
+ public static SQLFilter simple(String sql) {
+ return new SimpleSQLFilter(sql);
+ }
+
+ /**
+ * Implementation of SQLFilter that matches all rows.
+ */
+ public static class MatchAllSQLFilter implements SQLFilter {
+ @Override
+ public String toSql() {
+ return "1=1";
+ }
+
+ @Override
+ public boolean matchesAllRows() {
+ return true;
+ }
+
+ @Override
+ public boolean matchesNoRows() {
+ return false;
+ }
+ }
+
+ /**
+ * Implementation of SQLFilter that matches no rows.
+ */
+ public static class MatchNoSQLFilter implements SQLFilter {
+ @Override
+ public String toSql() {
+ return "1=0";
+ }
+
+ @Override
+ public boolean matchesAllRows() {
+ return false;
+ }
+
+ @Override
+ public boolean matchesNoRows() {
+ return true;
+ }
+ }
+
+ /**
+ * Implementation of SQLFilter with a simple SQL expression.
+ */
+ public static class SimpleSQLFilter implements SQLFilter {
+ private final String sql;
+
+ /**
+ * Creates a new SimpleSQLFilter with the given SQL expression.
+ *
+ * @param sql The SQL expression
+ */
+ public SimpleSQLFilter(String sql) {
+ this.sql = sql;
+ }
+
+ @Override
+ public String toSql() {
+ return sql;
+ }
+
+ @Override
+ public boolean matchesAllRows() {
+ return "1=1".equals(sql);
+ }
+
+ @Override
+ public boolean matchesNoRows() {
+ return "1=0".equals(sql);
+ }
+ }
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SqlQueryBuilder.java b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SqlQueryBuilder.java
new file mode 100644
index 00000000..140fb2b1
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/main/java/dev/langchain4j/community/store/embedding/oceanbase/sql/SqlQueryBuilder.java
@@ -0,0 +1,173 @@
+package dev.langchain4j.community.store.embedding.oceanbase.sql;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builder for SQL queries.
+ * Implements the Builder pattern to create SQL queries in a fluent, readable way.
+ */
+public class SqlQueryBuilder {
+ private StringBuilder queryBuilder = new StringBuilder();
+ private List selectColumns = new ArrayList<>();
+ private String fromTable;
+ private String whereClause;
+ private List orderByColumns = new ArrayList<>();
+ private boolean approximate = false;
+ private Integer limit;
+
+ /**
+ * Start building a SELECT query.
+ *
+ * @return this builder
+ */
+ public static SqlQueryBuilder select() {
+ return new SqlQueryBuilder();
+ }
+
+ /**
+ * Add columns to the SELECT clause.
+ *
+ * @param columns The columns to select
+ * @return this builder
+ */
+ public SqlQueryBuilder columns(String... columns) {
+ for (String column : columns) {
+ selectColumns.add(column);
+ }
+ return this;
+ }
+
+ /**
+ * Add a function-based column to the SELECT clause.
+ *
+ * @param functionName The name of the function
+ * @param args The function arguments
+ * @return this builder
+ */
+ public SqlQueryBuilder function(String functionName, String... args) {
+ String column = functionName + "(" + String.join(", ", args) + ")";
+ selectColumns.add(column);
+ return this;
+ }
+
+ /**
+ * Add an alias to the last added column.
+ *
+ * @param alias The alias name
+ * @return this builder
+ */
+ public SqlQueryBuilder as(String alias) {
+ if (!selectColumns.isEmpty()) {
+ int lastIndex = selectColumns.size() - 1;
+ String column = selectColumns.get(lastIndex) + " AS " + alias;
+ selectColumns.set(lastIndex, column);
+ }
+ return this;
+ }
+
+ /**
+ * Set the FROM clause.
+ *
+ * @param table The table name
+ * @return this builder
+ */
+ public SqlQueryBuilder from(String table) {
+ this.fromTable = table;
+ return this;
+ }
+
+ /**
+ * Set the WHERE clause.
+ *
+ * @param condition The WHERE condition
+ * @return this builder
+ */
+ public SqlQueryBuilder where(String condition) {
+ this.whereClause = condition;
+ return this;
+ }
+
+ /**
+ * Add an ORDER BY clause.
+ *
+ * @param columns The columns to order by
+ * @return this builder
+ */
+ public SqlQueryBuilder orderBy(String... columns) {
+ for (String column : columns) {
+ orderByColumns.add(column);
+ }
+ return this;
+ }
+
+ /**
+ * Add an ORDER BY clause with a function.
+ *
+ * @param functionName The name of the function
+ * @param args The function arguments
+ * @return this builder
+ */
+ public SqlQueryBuilder orderByFunction(String functionName, String... args) {
+ String column = functionName + "(" + String.join(", ", args) + ")";
+ orderByColumns.add(column);
+ return this;
+ }
+
+ /**
+ * Use approximate search (for vector search in OceanBase).
+ *
+ * @return this builder
+ */
+ public SqlQueryBuilder approximate() {
+ this.approximate = true;
+ return this;
+ }
+
+ /**
+ * Set the LIMIT clause.
+ *
+ * @param limit The maximum number of rows to return
+ * @return this builder
+ */
+ public SqlQueryBuilder limit(int limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ /**
+ * Build the SQL query string.
+ *
+ * @return The complete SQL query
+ */
+ public String build() {
+ if (selectColumns.isEmpty()) {
+ throw new IllegalStateException("No columns specified for SELECT");
+ }
+ if (fromTable == null) {
+ throw new IllegalStateException("No table specified for FROM");
+ }
+
+ queryBuilder.append("SELECT ").append(String.join(", ", selectColumns));
+ queryBuilder.append(" FROM ").append(fromTable);
+
+ if (whereClause != null && !whereClause.isEmpty()) {
+ queryBuilder.append(" WHERE ").append(whereClause);
+ }
+
+ if (!orderByColumns.isEmpty()) {
+ queryBuilder.append(" ORDER BY ").append(String.join(", ", orderByColumns));
+
+ // Add APPROXIMATE keyword if needed
+ if (approximate) {
+ queryBuilder.append(" APPROXIMATE");
+ }
+ }
+
+ if (limit != null) {
+ queryBuilder.append(" LIMIT ").append(limit);
+ }
+
+ return queryBuilder.toString();
+ }
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/CommonTestOperations.java b/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/CommonTestOperations.java
new file mode 100644
index 00000000..48107d83
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/CommonTestOperations.java
@@ -0,0 +1,187 @@
+package dev.langchain4j.community.store.embedding.oceanbase;
+
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.UUID;
+import javax.sql.DataSource;
+
+import dev.langchain4j.model.embedding.onnx.allminilml6v2q.AllMiniLmL6V2QuantizedEmbeddingModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+/**
+ * Common operations for tests.
+ */
+public class CommonTestOperations {
+
+ private static final Logger log = LoggerFactory.getLogger(CommonTestOperations.class);
+
+ private static final String TABLE_NAME =
+ "langchain4j_oceanbase_test_" + UUID.randomUUID().toString().replace("-", "");
+
+ // OceanBase container configuration
+ private static final int OCEANBASE_PORT = 2881; // Default OceanBase port
+ private static GenericContainer> oceanBaseContainer;
+ private static DataSource dataSource;
+ private static EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
+
+ static {
+ startOceanBaseContainer();
+ }
+
+ /**
+ * Starts the OceanBase container for tests.
+ */
+ private static void startOceanBaseContainer() {
+ try {
+ // Using OceanBase's official Docker image
+ oceanBaseContainer = new GenericContainer<>("oceanbase/oceanbase-ce:4.3.5-lts")
+ .withExposedPorts(OCEANBASE_PORT)
+ .withEnv("MODE", "standalone") // For single-node deployment
+ // Wait for boot success message
+ .waitingFor(Wait.forLogMessage(".*boot success!.*", 1));
+
+ // Start the container
+ oceanBaseContainer.start();
+
+ // Get the mapped port and host
+ String jdbcUrl = String.format(
+ "jdbc:oceanbase://%s:%d/test",
+ oceanBaseContainer.getHost(), oceanBaseContainer.getMappedPort(OCEANBASE_PORT));
+
+ log.info("OceanBase container started at {}", jdbcUrl);
+
+ // Create a data source with the container's connection info
+ dataSource = new SimpleDataSource(jdbcUrl, "root@test", "");
+
+ // Create test database and setup
+ try (Connection conn = dataSource.getConnection();
+ Statement stmt = conn.createStatement()) {
+ // Set memory limit for vector columns
+ stmt.execute("ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30;");
+ // Create a test database if needed
+ stmt.execute("CREATE DATABASE IF NOT EXISTS test_example");
+ stmt.execute("USE test_example");
+ // Add any other initialization SQL you need
+ } catch (SQLException e) {
+ log.error("Failed to initialize OceanBase container", e);
+ throw new RuntimeException("Failed to initialize OceanBase container", e);
+ }
+ } catch (Exception e) {
+ log.error("Failed to start OceanBase container", e);
+ throw new RuntimeException("Failed to start OceanBase container", e);
+ }
+ }
+
+ /**
+ * Creates a new embedding store for testing.
+ *
+ * @return A new embedding store.
+ */
+ public static OceanBaseEmbeddingStore newEmbeddingStore() {
+ return OceanBaseEmbeddingStore.builder(getDataSource())
+ .embeddingTable(EmbeddingTable.builder(TABLE_NAME)
+ .vectorDimension(embeddingModel.dimension())
+ .createOption(CreateOption.CREATE_OR_REPLACE)
+ .distanceMetric("cosine")
+ .build())
+ .build();
+ }
+
+ /**
+ * Returns the data source for connecting to OceanBase.
+ *
+ * @return The data source.
+ */
+ public static DataSource getDataSource() {
+ if (dataSource == null) {
+ throw new IllegalStateException(
+ "DataSource is not initialized. Container might not have started properly.");
+ }
+ return dataSource;
+ }
+
+ /**
+ * Simple DataSource implementation for tests.
+ */
+ private static class SimpleDataSource implements DataSource {
+ private final String url;
+ private final String user;
+ private final String password;
+
+ public SimpleDataSource(String url, String user, String password) {
+ this.url = url;
+ this.user = user;
+ this.password = password;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ return DriverManager.getConnection(url, user, password);
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ return DriverManager.getConnection(url, username, password);
+ }
+
+ @Override
+ public java.io.PrintWriter getLogWriter() throws SQLException {
+ return null;
+ }
+
+ @Override
+ public void setLogWriter(java.io.PrintWriter out) throws SQLException {}
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException {}
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return 0;
+ }
+
+ @Override
+ public java.util.logging.Logger getParentLogger() {
+ return null;
+ }
+
+ @Override
+ public T unwrap(Class iface) throws SQLException {
+ throw new SQLException("Unwrapping not supported");
+ }
+
+ @Override
+ public boolean isWrapperFor(Class> iface) throws SQLException {
+ return false;
+ }
+ }
+
+ /**
+ * Drops the test table.
+ *
+ * @throws SQLException If an error occurs.
+ */
+ public static void dropTable() throws SQLException {
+ try (Connection connection = getDataSource().getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute("DROP TABLE IF EXISTS " + TABLE_NAME);
+ log.info("Dropped table {}", TABLE_NAME);
+ }
+ }
+
+ /**
+ * Stops the OceanBase container.
+ */
+ public static void stopContainer() {
+ if (oceanBaseContainer != null && oceanBaseContainer.isRunning()) {
+ oceanBaseContainer.stop();
+ log.info("OceanBase container stopped");
+ }
+ }
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/OceanBaseEmbeddingStoreIT.java b/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/OceanBaseEmbeddingStoreIT.java
new file mode 100644
index 00000000..b17bb048
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/OceanBaseEmbeddingStoreIT.java
@@ -0,0 +1,54 @@
+package dev.langchain4j.community.store.embedding.oceanbase;
+
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.embedding.onnx.allminilml6v2q.AllMiniLmL6V2QuantizedEmbeddingModel;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import dev.langchain4j.store.embedding.EmbeddingStoreWithFilteringIT;
+import java.sql.SQLException;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+
+/**
+ * Integration tests for {@link OceanBaseEmbeddingStore}.
+ */
+public class OceanBaseEmbeddingStoreIT extends EmbeddingStoreWithFilteringIT {
+
+ private OceanBaseEmbeddingStore embeddingStore = CommonTestOperations.newEmbeddingStore();
+
+ EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
+
+ @AfterAll
+ static void cleanUp() throws SQLException {
+ try {
+ CommonTestOperations.dropTable();
+ } finally {
+ CommonTestOperations.stopContainer();
+ }
+ }
+
+ @AfterEach
+ void tearDown() {
+ clearStore();
+ }
+
+ @Override
+ protected EmbeddingStore embeddingStore() {
+ return embeddingStore;
+ }
+
+ @Override
+ protected EmbeddingModel embeddingModel() {
+ return embeddingModel;
+ }
+
+ @Override
+ protected void clearStore() {
+ embeddingStore.removeAll();
+ }
+
+ @Override
+ protected boolean supportsContains() {
+ return true;
+ }
+}
diff --git a/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/OceanBaseWithRemovalIT.java b/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/OceanBaseWithRemovalIT.java
new file mode 100644
index 00000000..9772d79d
--- /dev/null
+++ b/embedding-stores/langchain4j-community-oceanbase/src/test/java/dev/langchain4j/community/store/embedding/oceanbase/OceanBaseWithRemovalIT.java
@@ -0,0 +1,51 @@
+package dev.langchain4j.community.store.embedding.oceanbase;
+
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.embedding.onnx.allminilml6v2q.AllMiniLmL6V2QuantizedEmbeddingModel;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import dev.langchain4j.store.embedding.EmbeddingStoreWithRemovalIT;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+import java.sql.SQLException;
+
+/**
+ * Tests for {@link OceanBaseEmbeddingStore} with removal.
+ */
+public class OceanBaseWithRemovalIT extends EmbeddingStoreWithRemovalIT {
+
+ private OceanBaseEmbeddingStore embeddingStore;
+
+ EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
+
+ @BeforeEach
+ void setUp() {
+ embeddingStore = CommonTestOperations.newEmbeddingStore();
+ }
+
+ @AfterAll
+ static void cleanUp() throws SQLException {
+ try {
+ CommonTestOperations.dropTable();
+ } finally {
+ CommonTestOperations.stopContainer();
+ }
+ }
+
+ @AfterEach
+ void tearDown() {
+ embeddingStore.removeAll();
+ }
+
+ @Override
+ protected EmbeddingStore embeddingStore() {
+ return embeddingStore;
+ }
+
+ @Override
+ protected EmbeddingModel embeddingModel() {
+ return embeddingModel;
+ }
+}
diff --git a/langchain4j-community-bom/pom.xml b/langchain4j-community-bom/pom.xml
index 6dc580ba..8daad7eb 100644
--- a/langchain4j-community-bom/pom.xml
+++ b/langchain4j-community-bom/pom.xml
@@ -109,6 +109,12 @@
${project.version}
+
+ dev.langchain4j
+ langchain4j-community-oceanbase
+ ${project.version}
+
+
dev.langchain4j
diff --git a/pom.xml b/pom.xml
index 3dfb5423..b4ade995 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
embedding-stores/langchain4j-community-alloydb-pg
embedding-stores/langchain4j-community-cloud-sql-pg
embedding-stores/langchain4j-community-neo4j
+ embedding-stores/langchain4j-community-oceanbase
content-retrievers/langchain4j-community-lucene