From e85c614cf883146ce1f753edd8eb0e68f1b96512 Mon Sep 17 00:00:00 2001 From: Yegor Bugayenko Date: Mon, 14 Mar 2016 17:43:44 -0700 Subject: [PATCH] #73 items counting implemented --- src/main/java/com/jcabi/dynamo/AwsFrame.java | 12 +++++-- .../java/com/jcabi/dynamo/QueryValve.java | 32 +++++++++++++++++++ src/main/java/com/jcabi/dynamo/ScanValve.java | 19 ++++++++++- src/main/java/com/jcabi/dynamo/Valve.java | 11 +++++++ .../java/com/jcabi/dynamo/retry/ReValve.java | 7 ++++ .../java/com/jcabi/dynamo/AwsFrameITCase.java | 29 +++++++++++++++-- 6 files changed, 104 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/jcabi/dynamo/AwsFrame.java b/src/main/java/com/jcabi/dynamo/AwsFrame.java index 6eb6cb6..6acdda5 100644 --- a/src/main/java/com/jcabi/dynamo/AwsFrame.java +++ b/src/main/java/com/jcabi/dynamo/AwsFrame.java @@ -30,7 +30,6 @@ package com.jcabi.dynamo; import com.amazonaws.services.dynamodbv2.model.Condition; -import com.google.common.collect.Iterators; import com.jcabi.aspects.Immutable; import com.jcabi.aspects.Loggable; import java.io.IOException; @@ -130,7 +129,16 @@ public Iterator iterator() { @Override public int size() { - return Iterators.size(this.iterator()); + try { + return this.valve.count( + this.credentials, this.name, this.conditions + ); + } catch (final IOException ex) { + throw new IllegalStateException( + String.format("can't count items in \"%s\"", this.name), + ex + ); + } } @Override diff --git a/src/main/java/com/jcabi/dynamo/QueryValve.java b/src/main/java/com/jcabi/dynamo/QueryValve.java index b3c70ac..04e9f0c 100644 --- a/src/main/java/com/jcabi/dynamo/QueryValve.java +++ b/src/main/java/com/jcabi/dynamo/QueryValve.java @@ -177,6 +177,38 @@ public Dosage fetch(final Credentials credentials, final String table, } } + @Override + public int count(final Credentials credentials, final String table, + final Map conditions) throws IOException { + final AmazonDynamoDB aws = credentials.aws(); + try { + QueryRequest request = new QueryRequest() + .withTableName(table) + .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + .withKeyConditions(conditions) + .withConsistentRead(true) + .withSelect(Select.COUNT) + .withLimit(Integer.MAX_VALUE); + if (!this.index.isEmpty()) { + request = request.withIndexName(this.index); + } + final long start = System.currentTimeMillis(); + final QueryResult rslt = aws.query(request); + final int count = rslt.getCount(); + Logger.info( + this, + // @checkstyle LineLength (1 line) + "#total(): COUNT=%d in '%s' using %s, %s, in %[ms]s", + count, request.getTableName(), request.getQueryFilter(), + AwsTable.print(rslt.getConsumedCapacity()), + System.currentTimeMillis() - start + ); + return count; + } finally { + aws.shutdown(); + } + } + /** * With consistent read. * @param cnst Consistent read diff --git a/src/main/java/com/jcabi/dynamo/ScanValve.java b/src/main/java/com/jcabi/dynamo/ScanValve.java index 1e1b563..3ed982e 100644 --- a/src/main/java/com/jcabi/dynamo/ScanValve.java +++ b/src/main/java/com/jcabi/dynamo/ScanValve.java @@ -62,7 +62,7 @@ @Immutable @ToString @Loggable(Loggable.DEBUG) -@EqualsAndHashCode(of = { "limit" }) +@EqualsAndHashCode(of = { "limit", "attributes" }) public final class ScanValve implements Valve { /** @@ -133,6 +133,23 @@ public Dosage fetch(final Credentials credentials, } } + @Override + public int count(final Credentials credentials, final String table, + final Map conditions) throws IOException { + Dosage dosage = this.fetch( + credentials, table, conditions, Collections.emptyList() + ); + int count = 0; + while (true) { + count += dosage.items().size(); + if (!dosage.hasNext()) { + break; + } + dosage = dosage.next(); + } + return count; + } + /** * With given limit. * @param lmt Limit to use diff --git a/src/main/java/com/jcabi/dynamo/Valve.java b/src/main/java/com/jcabi/dynamo/Valve.java index e6e5c8a..5cfadfc 100644 --- a/src/main/java/com/jcabi/dynamo/Valve.java +++ b/src/main/java/com/jcabi/dynamo/Valve.java @@ -59,4 +59,15 @@ Dosage fetch(Credentials credentials, String table, Map conditions, Collection keys) throws IOException; + /** + * Count items. + * @param credentials Credentials to AWS + * @param table Table name + * @param conditions Conditions + * @return Total count of the + * @throws IOException In case of DynamoDB failure + */ + int count(Credentials credentials, String table, + Map conditions) throws IOException; + } diff --git a/src/main/java/com/jcabi/dynamo/retry/ReValve.java b/src/main/java/com/jcabi/dynamo/retry/ReValve.java index 8ec73c1..f2d8cc2 100644 --- a/src/main/java/com/jcabi/dynamo/retry/ReValve.java +++ b/src/main/java/com/jcabi/dynamo/retry/ReValve.java @@ -81,4 +81,11 @@ public Dosage fetch(final Credentials credentials, final String table, ); } + @Override + @RetryOnFailure(verbose = false, delay = Tv.FIVE, unit = TimeUnit.SECONDS) + public int count(final Credentials credentials, final String table, + final Map conditions) throws IOException { + return this.origin.count(credentials, table, conditions); + } + } diff --git a/src/test/java/com/jcabi/dynamo/AwsFrameITCase.java b/src/test/java/com/jcabi/dynamo/AwsFrameITCase.java index 53df07d..937e896 100644 --- a/src/test/java/com/jcabi/dynamo/AwsFrameITCase.java +++ b/src/test/java/com/jcabi/dynamo/AwsFrameITCase.java @@ -52,12 +52,35 @@ public void calculatesItems() throws Exception { final String name = RandomStringUtils.randomAlphabetic(Tv.EIGHT); final RegionMock mock = new RegionMock(); final Table tbl = mock.get(name).table(name); - final Attributes attrs = new Attributes().with(mock.range(), 1L); + final String hash = "hello"; + final Attributes attrs = new Attributes().with(mock.hash(), hash); for (int idx = 0; idx < Tv.TEN; ++idx) { - tbl.put(attrs.with(mock.hash(), String.format("i%d", idx))); + tbl.put(attrs.with(mock.range(), idx)); } MatcherAssert.assertThat( - tbl.frame().through(new ScanValve().withLimit(1)).size(), + tbl.frame() + .through(new ScanValve().withLimit(1)) + .size(), + Matchers.equalTo(Tv.TEN) + ); + MatcherAssert.assertThat( + tbl.frame() + .through(new ScanValve().withLimit(Tv.HUNDRED)) + .size(), + Matchers.equalTo(Tv.TEN) + ); + MatcherAssert.assertThat( + tbl.frame() + .through(new QueryValve().withLimit(1)) + .where(mock.hash(), hash) + .size(), + Matchers.equalTo(Tv.TEN) + ); + MatcherAssert.assertThat( + tbl.frame() + .through(new QueryValve().withLimit(Tv.HUNDRED)) + .where(mock.hash(), hash) + .size(), Matchers.equalTo(Tv.TEN) ); }