-
Notifications
You must be signed in to change notification settings - Fork 36
Support Metadata filtering with Neo4J #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6722ae5
46cdbb4
3cc1a24
979dfc0
1084341
6e5dff9
d6a1aa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package dev.langchain4j.community.store.embedding.neo4j; | ||
|
||
import static org.neo4j.cypherdsl.support.schema_name.SchemaNames.sanitize; | ||
|
||
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.AbstractMap; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
public class Neo4jFilterMapper { | ||
|
||
public static final String UNSUPPORTED_FILTER_TYPE_ERROR = "Unsupported filter type: "; | ||
|
||
public static class IncrementalKeyMap { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a list might be better |
||
private final Map<String, Object> map = new ConcurrentHashMap<>(); | ||
|
||
private final AtomicInteger integer = new AtomicInteger(); | ||
|
||
public String put(Object value) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like it should use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed to |
||
String key = "param_" + integer.incrementAndGet(); | ||
map.put(key, value); | ||
return key; | ||
} | ||
|
||
public Map<String, Object> getMap() { | ||
return map; | ||
} | ||
} | ||
|
||
public Neo4jFilterMapper() {} | ||
|
||
final IncrementalKeyMap map = new IncrementalKeyMap(); | ||
|
||
AbstractMap.SimpleEntry<String, Map<String, Object>> map(Filter filter) { | ||
final String stringMapPair = getStringMapping(filter); | ||
return new AbstractMap.SimpleEntry<>(stringMapPair, map.getMap()); | ||
} | ||
|
||
private String getStringMapping(Filter filter) { | ||
if (filter instanceof IsEqualTo item) { | ||
return getOperation(item.key(), "=", item.comparisonValue()); | ||
} else if (filter instanceof IsNotEqualTo item) { | ||
return getOperation(item.key(), "<>", item.comparisonValue()); | ||
} else if (filter instanceof IsGreaterThan item) { | ||
return getOperation(item.key(), ">", item.comparisonValue()); | ||
} else if (filter instanceof IsGreaterThanOrEqualTo item) { | ||
return getOperation(item.key(), ">=", item.comparisonValue()); | ||
} else if (filter instanceof IsLessThan item) { | ||
return getOperation(item.key(), "<", item.comparisonValue()); | ||
} else if (filter instanceof IsLessThanOrEqualTo item) { | ||
return getOperation(item.key(), "<=", item.comparisonValue()); | ||
} else if (filter instanceof IsIn item) { | ||
return mapIn(item); | ||
} else if (filter instanceof IsNotIn item) { | ||
return mapNotIn(item); | ||
} else if (filter instanceof And item) { | ||
return mapAnd(item); | ||
} else if (filter instanceof Not item) { | ||
return mapNot(item); | ||
} else if (filter instanceof Or item) { | ||
return mapOr(item); | ||
} else { | ||
throw new UnsupportedOperationException( | ||
UNSUPPORTED_FILTER_TYPE_ERROR + filter.getClass().getName()); | ||
} | ||
} | ||
|
||
private String getOperation(String key, String operator, Object value) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. usually this is an enum of operators which is then also able to format it |
||
// put ($param_N, <value>) entry map | ||
final String param = map.put(value); | ||
|
||
String sanitizedKey = sanitize(key).orElseThrow(() -> { | ||
String invalidSanitizeValue = String.format( | ||
"The key %s, to assign to the operator %s and value %s, cannot be safely quoted", | ||
key, operator, value); | ||
return new RuntimeException(invalidSanitizeValue); | ||
}); | ||
|
||
return String.format("n.%s %s $%s", sanitizedKey, operator, param); | ||
} | ||
|
||
public String mapIn(IsIn filter) { | ||
return getOperation(filter.key(), "IN", filter.comparisonValues()); | ||
} | ||
|
||
public String mapNotIn(IsNotIn filter) { | ||
final String inOperation = getOperation(filter.key(), "IN", filter.comparisonValues()); | ||
return String.format("NOT (%s)", inOperation); | ||
} | ||
|
||
private String mapAnd(And filter) { | ||
return String.format("(%s) AND (%s)", getStringMapping(filter.left()), getStringMapping(filter.right())); | ||
} | ||
|
||
private String mapOr(Or filter) { | ||
return String.format("(%s) OR (%s)", getStringMapping(filter.left()), getStringMapping(filter.right())); | ||
} | ||
|
||
private String mapNot(Not filter) { | ||
return String.format("NOT (%s)", getStringMapping(filter.expression())); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesn't seem to apply the filter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The filter is applied via
filterEntry.getKey()
in the String.format andfilterEntry.getValue()
in the params.For example, if the Filter is
IsEqualTo(key=type, comparisonValue=a)
, thefilterEntry.getKey()
is "n.type = $param_1" and thefilterEntry.getValue()
is Map.of("param_1", "a") .Therefore the result is:
so that we can handle any neo4j data type