DynamoDb IN operator in Java - java

I have a working DynamoDb query, but I'm trying to add an IN operator and can't figure out the syntax for the FilterExpression.
pages is a List<Integer> and I have an attribute in my table which is an Integer, so I'm doing something like this
attrValues.put(":pages", AttributeValue.fromL(pages.stream()
.map(i->AttributeValue.fromN(i.toString()))
.collect(Collectors.toList())));
String filterExpression = "in(originalPageNumber, :pages)"
final QueryRequest.Builder queryBuilder = QueryRequest.builder()
.tableName(Parameters.addPrefix(TABLE_NAME))
.keyConditionExpression(keyConditionExpression)
.expressionAttributeNames(attrNameAlias)
.expressionAttributeValues(attrValues)
.filterExpression(filterExpression);
(this is not the complete code. Just the relevant parts)
But it doesn't like my syntax at all. I've tried originalPageNumber in :pages and other variations as well.
Update
Other things I tried
Tried setting :pages to
final String pagesStr = pages.stream().map(i -> i.toString()).collect(Collectors.joining(", ", "(", ")"));
final AttributeValue pagesAttr = AttributeValue.fromS(pagesStr);
attrValues.put(":pages", pagesAttr);
or AttributeValue(S=(1, 2))
I then got rid of :pages altogether and just set filterExpression to in(originalPageNumber, (1, 2)). Still no luck.
Also tried originalPageNumber in (1, 2)

Try it like this:
String filterExpression = "originalPageNumber IN (:pages)"
:pages would need to be a comma separated list of values. Which should essentially map to:
String filterExpression = "originalPageNumber IN (1,2,3,7,9)"

Related

PatternReplaceCharFilterFactory arguments problem in Lucene (java)

I am doing a practice in Java using Lucene. I want to remove "{", "}" and ";" using a CharFilter in a CustomAnalyzer but I don't know how to call the "PatternReplaceCharFilterFactory". I have tried to call it passing it "map" but it doesn't work and it returns an exception. I have also tried with pattern "p" but it's the same.
public static ArrayList<String> analyzer_codigo(String texto)throws IOException{
Map<String, String> map = new HashMap<String, String>();
map.put("{", "");
map.put("}", "");
map.put(";", "");
Pattern p = Pattern.compile("([^a-z])");
boolean replaceAll = Boolean.TRUE;
Reader r = new Reader(texto);
Analyzer ana = CustomAnalyzer.builder(Paths.get("."))
.addCharFilter(PatternReplaceCharFilterFactory.class,p,"",r)
.withTokenizer(StandardTokenizerFactory.class)
.addTokenFilter(LowerCaseFilterFactory.class)
.build();
return muestraTexto(ana, texto);
}
You can pass a Map to the PatternReplaceCharFilterFactory - but the keys you use for the map are those defined in the JavaDoc for the factory class:
pattern="([^a-z])" replacement=""
This uses Solr documentation to define the keys (pattern and replacement) together with their Solr default values.
Using these keys, your map becomes:
Map<String, String> map = new HashMap<>();
map.put("pattern", "\\{|\\}|;");
map.put("replacement", "");
The regular expression \\{|\\}|; needs to escape the { and } characters because they have special meanings, and then the regex backslashes also need to be escaped in the Java string.
So, the above regular expression means { and } and ; will all be replaced by the empty string.
Your custom analyzer then becomes:
Analyzer analyzer = CustomAnalyzer.builder()
.withTokenizer(StandardTokenizerFactory.NAME)
.addCharFilter(PatternReplaceCharFilterFactory.NAME, map)
.addTokenFilter(LowerCaseFilterFactory.NAME)
.build();
If you use this to index the following input string:
foo{bar}baz;bat
Then the indexed value will be stored as:
foobarbazbat
Very minor point: I prefer to use PatternReplaceCharFilterFactory.NAME instead of PatternReplaceCharFilterFactory.class or even just "patternReplace" - but these all work.
Update
Just for completeness:
The CustomAnalyzer.Builder supports different ways to add a CharFilter. See its addCharFilter methods.
As well as the approach shown above, using a Map...
.addCharFilter(PatternReplaceCharFilterFactory.NAME, map)
...you can also use Java varargs:
"key1", "value1", "key2", "value2", ...
So, in our case, this would be:
.addCharFilter(PatternReplaceCharFilterFactory.NAME
"pattern", "\\{|\\}|;", "replacement", "")

How transform a list of objects to a list of strings based on the value of a property in java?

Is there a way how to transform a list of objects to a list of strings based on the value of a property? I have an entity Tag
public class Tag {
private int tagID;
private String description;
}
I get a list of tags with their ids and descriptions:
[Tag [tagID=1, description=121], Tag [tagID=1, description=244], Tag [tagID=1, description=331], Tag [tagID=2, description=244], Tag [tagID=2, description=122]]
And what I need is:
List<String> output = ["121,244,331", "244,122"]
So far I put together this:
String description = tags.stream().map(Tag::getDescription).collect(Collectors.joining( ";" ));
outputting a result for one tag
String description = "121,244,331"
Of course, I could run it through a loop and append the result to an array, but I wondered if there is a more ellegant way - even a one-liner?
You can use Collectors.groupingBy to group by tag id and then join description using Collectors.joining
List<String> res = new ArrayList<>(tagList.stream().collect(
Collectors.groupingBy(Tag::getTagID,
Collectors.mapping(Tag::getDescription, Collectors.joining(",")))).values());
I think you are looking to:
List<String> result = tags.stream()
.collect(Collectors.groupingBy(Tag::getTagID))
.values()
.stream()
.map(t -> t.stream().map(Tag::getDescription).collect(Collectors.joining( ";" )))
.collect(Collectors.toList());
Output
[121;244;331, 244;122]

String Utils join in not working as expected

I have a list of strings which is returned from a query in List A.
I am trying to use String Utils.join to combine the values in the list to a string separated by comma and in quotes. But it is not working as expected.
Values in abcList - [abc, cde, fgh]
abcList.addAll(jdbcTemplate.queryForList(abcSql, String.class));
String abc= StringUtils.join(abcList, "','");
abc = "'" +abc+ "'";
Expected output - 'abc', 'cde', 'fgh'
Actual output - 'abc, cde, fgh'
I am not sure what I am doing wrong here as I want to pass the values form the string abc into query with "IN" condition.
As alternative you can also use stream.Collectors.joining
List<String> myList = Arrays.asList("abc","def","ghi");
String joined = myList.stream().collect(Collectors.joining("','", "'", "'"));
System.out.println(joined);
If you are using Java 8 you can use the native method for joining strings.
List<String> list = <get a list of strings somehow>;
String joinedString = String.join("','", list);
See the String.join javadoc
Just as a hint for JDBC queries...you should use named parameters for inserting the values in your query instead of manually constructing the query string.
See this SO post for an example.

jOOQ - query builder with IN predicate

I am trying to build a query like this:
List<Integer> ids = ...
String query = DSL.select(TABLE.SOMETHING).from(TABLE).where(TABLE.ID.in(ids)).
getSQL();
But I am not able to get the generated query with the values, just the placeholders.
I tried DSL.inline(ids) but it doesnt' work.
How can I do this?
I am using jOOQ 3.4.2.
Thanks for the help.
UPDATE:
Seems I can do this with:
Configuration configuration = new DefaultConfiguration();
configuration.set(SQLDialect.DERBY);
Settings settings = new Settings()
.withStatementType(StatementType.STATIC_STATEMENT);
configuration.set(settings);
DSLContext create = DSL.using(configuration);
String query = create.select(TABLE.SOMETHING).from(TABLE).where(TABLE.ID.in(ids)).getSQL();
If someone can confirm that is th right way, thanks.
You cannot inline a list with jOOQ's DSL.inline() because if you could, the semantics of such a value would be that of a list/array literal in the database, not of a list of individual values.
Correct way to use DSL.inline():
Here's one correct way to pass a list of inlined values to the Field.in(Field<?>...):
List<Integer> ids = ...
String query = DSL.using(configuration) // Use a Configuration or at least a SQLDialect!
.select(TABLE.SOMETHING)
.from(TABLE)
.where(TABLE.ID.in(ids.stream().map(DSL::inline).collect(toList())))
.getSQL();
Inline all bind values on a per-getSQL() basis:
Use Query.getSQL(ParamType)
List<Integer> ids = ...
String query = DSL.using(configuration)
.select(TABLE.SOMETHING)
.from(TABLE)
.where(TABLE.ID.in(ids))
.getSQL(ParamType.INLINED);
Inline all bind values on a per-Configuration basis:
The solution you've mentioned in your question edit is valid as well, of course:
List<Integer> ids = ...
Configuration configuration = new DefaultConfiguration();
configuration.set(new Settings().withStatementType(StatementType.STATIC_STATEMENT));
String query = DSL.using(configuration)
.select(TABLE.SOMETHING)
.from(TABLE)
.where(TABLE.ID.in(ids))
.getSQL();

AWS DynamoDB multiple "NE" string filters?

I'm trying to scan/query an AWS DynamoDB table for a list of items where id (a single string) is not equal to any of strings A, B, C, D, etc.
I've tried something like this:
for (String itemString : items) {
scanExpression.addFilterCondition("id",
new Condition().withComparisonOperator(ComparisonOperator.NE)
.withAttributeValueList(new AttributeValue().withS(itemString)));
}
PaginatedScanList<Items> result = mapper.scan(Item.class, scanExpression);
What appears to happen is that each time I add filter, it overwrites the previous values, so that the scan only checks against one itemString, not several. Am I missing something, or is this a limitation of DynamoDB?
Note: if it matters, in my example, I listed four values (A, B, C, D) but in production, this may be a list of hundreds of values.
You are correct in saying that the way you are doing it "overwrites the previous values". Here is the relevant code from the 1.9.23 SDK on DynamoDBScanExpression:
public void addFilterCondition(String attributeName, Condition condition) {
if ( scanFilter == null )
scanFilter = new HashMap<String, Condition>();
scanFilter.put(attributeName, condition);
}
Since your attributeName will be id for both puts, the last value will win in this case.
In my opinion, this is poor behavior from DynamoDBScanExpression, and I would even lean more towards saying it is a bug that should be reported. The documentation does not state anything about when a duplicate attributeName is added and the method name makes it seem like this is unexpected behavior.
I don't see a good way to work around this other than building out the entire filter expression.
Another Note: On the documentation, I don't see a length constraint for how long a filter expression can be as well as how many ExpressionAttributeNames and ExpressionAttributeValues are allowed on a request. That may come into account if you are trying to filter out a ton of attribute values, but I haven't found any documentation of what that limit might be or what behavior you should expect.
StringJoiner filterExpression = new StringJoiner(" AND ");
Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
int itemAttributeSuffix = 0;
for (String itemString : items) {
StringBuilder expression = new StringBuilder("#idname")
.append(" ")
.append("<>")
.append(" ")
.append(":idval")
.append(itemAttributeSuffix);
filterExpression.add(expression);
expressionAttributeValues.put(":idval" + itemAttributeSuffix++,
new AttributeValue().withS(itemString));
}
Map<String, String> expressionAttributeNames = Collections.singletonMap("#idname", "id");
scanExpression.setFilterExpression(filterExpression.toString());
scanExpression.setExpressionAttributeNames(expressionAttributeNames);
scanExpression.setExpressionAttributeValues(expressionAttributeValues);
PaginatedScanList<Items> result = mapper.scan(Item.class, scanExpression);

Categories

Resources