hbase-jruby 0.2.1-java → 0.2.2-java

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ 0.2.2
5
+ -----
6
+ - Added `HBase::Table#delete_row` method
7
+ - Dependency profiles as prefixes
8
+ - Supported prefixes: `cdh4.1`, `cdh3`, `0.94`, `0.92`
9
+ - e.g. `HBase.resolve_dependency! 'cdh4.1.3'`
10
+ - Advanced data access with `Scoped#with_java_scan` and `Scoped#with_java_get`
11
+
4
12
  0.2.1
5
13
  -----
6
14
  - Fix: NameError even when appropriate CLASSPATH is set
data/README.md CHANGED
@@ -1,11 +1,6 @@
1
1
  # hbase-jruby
2
2
 
3
- *hbase-jruby* is a Ruby-esque interface for accessing HBase from JRuby.
4
-
5
- With JRuby, you can of course just use the native Java APIs of HBase,
6
- but doing so requires lots of keystrokes even for the most basic operations and
7
- can lead to having overly verbose code that will be frowned upon by Rubyists.
8
- Anyhow, JRuby is Ruby, not Java, right?
3
+ *hbase-jruby* is a simple JRuby binding for HBase.
9
4
 
10
5
  *hbase-jruby* provides the followings:
11
6
  - Easy, Ruby-esque interface for the fundamental HBase operations
@@ -17,7 +12,7 @@ Anyhow, JRuby is Ruby, not Java, right?
17
12
  ```ruby
18
13
  require 'hbase-jruby'
19
14
 
20
- HBase.resolve_dependency! 'cdh4.1'
15
+ HBase.resolve_dependency! 'cdh4.1.3'
21
16
 
22
17
  hbase = HBase.new
23
18
  table = hbase[:test_table]
@@ -56,7 +51,7 @@ table.delete(:rowkey9)
56
51
  git clone -b devel https://github.com/junegunn/hbase-jruby.git
57
52
  cd hbase-jruby
58
53
  rake build
59
- gem install pkg/hbase-jruby-0.2.1-java.gem
54
+ gem install pkg/hbase-jruby-0.2.2-java.gem
60
55
 
61
56
  ## Setting up
62
57
 
@@ -69,27 +64,29 @@ or by `require`ing relevant JAR files after launching JRuby.
69
64
  ### `HBase.resolve_dependency!`
70
65
 
71
66
  Well, there's an easier way.
72
- You can call `HBase.resolve_dependency!` helper method passing one of the arguments listed below.
67
+ Call `HBase.resolve_dependency!` helper method passing one of the arguments listed below.
73
68
 
74
- | Argument | Description | Required executable |
75
- |------------|----------------------------------------------------------|---------------------|
76
- | 'cdh4.1' | Predefined Maven profile for Cloudera CDH4.1 | mvn |
77
- | 'cdh3' | Predefined Maven profile for Cloudera CDH3 | mvn |
78
- | '0.94' | Predefined Maven profile for Apache HBase 0.94 | mvn |
79
- | '0.92' | Predefined Maven profile for Apache HBase 0.92 | mvn |
80
- | *POM PATH* | Follow dependency described in the given POM file | mvn |
81
- | *:local* | Resolve HBase dependency using `hbase classpath` command | hbase |
69
+ | Argument | Dependency | Required executable |
70
+ |------------|--------------------------|---------------------|
71
+ | cdh4.1[.*] | Cloudera CDH4.1 | mvn |
72
+ | cdh3[u*] | Cloudera CDH3 | mvn |
73
+ | 0.94[.*] | Apache HBase 0.94 | mvn |
74
+ | 0.92[.*] | Apache HBase 0.92 | mvn |
75
+ | *POM PATH* | Custom Maven POM file | mvn |
76
+ | `:local` | Local HBase installation | hbase |
82
77
 
83
- ```ruby
84
- # Examples
78
+ #### Examples
85
79
 
86
- # Load JAR files from CDH4.1 distribution of HBase using Maven
87
- HBase.resolve_dependency! 'cdh4.1'
80
+ ```ruby
81
+ # Load JAR files from CDH4.1.x using Maven
82
+ HBase.resolve_dependency! 'cdh4.1.3'
83
+ HBase.resolve_dependency! 'cdh4.1.1'
88
84
 
89
- # Load JAR files for HBase 0.94 using Maven
90
- HBase.resolve_dependency! '0.94', :verbose => true
85
+ # Load JAR files of HBase 0.94.x using Maven
86
+ HBase.resolve_dependency! '0.94.1'
87
+ HBase.resolve_dependency! '0.94.2', :verbose => true
91
88
 
92
- # Dependency resolution with your own POM file
89
+ # Dependency resolution with custom POM file
93
90
  HBase.resolve_dependency! '/path/to/my/pom.xml'
94
91
  HBase.resolve_dependency! '/path/to/my/pom.xml', :profile => 'trunk'
95
92
 
@@ -375,10 +372,16 @@ table.delete('rowkey1', 'cf1:col1', 1352978648642)
375
372
  table.delete('rowkey1', 'cf1:col1', 1352978648642, 1352978649642)
376
373
 
377
374
  # Batch delete
378
- table.delete(['rowkey1'], ['rowkey2'], ['rowkey3', 'cf1:col1'])
375
+ table.delete(['rowkey1'], ['rowkey2'], ['rowkey3', 'cf1:col1', 1352978648642, 135297864964])
376
+ ```
377
+
378
+ However, the last syntax seems a bit unwieldy when you just wish to delete a few rows.
379
+ In that case, use simpler `delete_row` method.
380
+
381
+ ```ruby
382
+ table.delete_row 'rowkey1'
379
383
 
380
- # Truncate table
381
- table.truncate!
384
+ table.delete_row 'rowkey1', 'rowkey2', 'rowkey3'
382
385
  ```
383
386
 
384
387
  ### Atomic increment of column values
@@ -409,18 +412,20 @@ end
409
412
  You can control how you retrieve data by chaining
410
413
  the following methods of `HBase::Table` (or `HBase::Scoped`).
411
414
 
412
- | Method | Description |
413
- |--------------|-----------------------------------------------------------------|
414
- | `range` | Specifies the rowkey range of scan |
415
- | `project` | To retrieve only a subset of columns |
416
- | `filter` | Filtering conditions of scan |
417
- | `while` | Allows early termination of scan (server-side) |
418
- | `at` | Only retrieve data with the specified timestamp |
419
- | `time_range` | Only retrieve data within the specified time range |
420
- | `limit` | Limits the number of rows |
421
- | `versions` | Limits the number of versions of each column |
422
- | `caching` | Sets the number of rows for caching during scan |
423
- | `batch` | Limits the maximum number of values returned for each iteration |
415
+ | Method | Description |
416
+ |------------------|-----------------------------------------------------------------|
417
+ | `range` | Specifies the rowkey range of scan |
418
+ | `project` | To retrieve only a subset of columns |
419
+ | `filter` | Filtering conditions of scan |
420
+ | `while` | Allows early termination of scan (server-side) |
421
+ | `at` | Only retrieve data with the specified timestamp |
422
+ | `time_range` | Only retrieve data within the specified time range |
423
+ | `limit` | Limits the number of rows |
424
+ | `versions` | Limits the number of versions of each column |
425
+ | `caching` | Sets the number of rows for caching during scan |
426
+ | `batch` | Limits the maximum number of values returned for each iteration |
427
+ | `with_java_scan` | *(ADVANCED)* Access Java Scan object in the given block |
428
+ | `with_java_get` | *(ADVANCED)* Access Java Get object in the given block |
424
429
 
425
430
  Each invocation to these methods returns an `HBase::Scoped` instance with which
426
431
  you can retrieve data with the following methods.
@@ -450,6 +455,9 @@ table.range('A'..'Z'). # Row key range,
450
455
  versions(2). # Only fetches 2 versions for each value
451
456
  batch(100). # Batch size for scan set to 100
452
457
  caching(1000). # Caching 1000 rows
458
+ with_java_scan { |scan| # Directly access Java Scan object
459
+ scan.setCacheBlocks false
460
+ }.
453
461
  to_a # To Array
454
462
  ```
455
463
 
data/hbase-jruby.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
8
8
  gem.version = HBase::JRuby::VERSION
9
9
  gem.authors = ["Junegunn Choi"]
10
10
  gem.email = ["junegunn.c@gmail.com"]
11
- gem.description = %q{Ruby-esque interface for accessing HBase from JRuby}
12
- gem.summary = %q{Ruby-esque interface for accessing HBase from JRuby}
11
+ gem.description = %q{A JRuby binding for HBase}
12
+ gem.summary = %q{A JRuby binding for HBase}
13
13
  gem.homepage = "https://github.com/junegunn/hbase-jruby"
14
14
  gem.platform = 'java'
15
15
  gem.license = 'MIT'
@@ -3,7 +3,7 @@ class << self
3
3
  # Shortcut method to HBase::ByteArray.new
4
4
  # @param [*Object] values
5
5
  def ByteArray *values
6
- ByteArray.new *values
6
+ ByteArray.new(*values)
7
7
  end
8
8
  end
9
9
  # Boxed class for Java byte arrays
@@ -1,9 +1,20 @@
1
1
  require 'java'
2
2
  require 'open-uri'
3
3
  require 'tempfile'
4
+ require 'erb'
4
5
 
5
6
  # HBase connection
6
7
  class HBase
8
+
9
+ # @private
10
+ SUPPORTED_PROFILES = {
11
+ # Prefix => Latest version
12
+ 'cdh4.1' => 'cdh4.1.3',
13
+ 'cdh3' => 'cdh3u5',
14
+ '0.94' => '0.94.3',
15
+ '0.92' => '0.92.1',
16
+ }
17
+
7
18
  class << self
8
19
  # @overload resolve_dependency!(dist, options)
9
20
  # Resolve Hadoop and HBase dependency with a predefined Maven profile
@@ -40,12 +51,25 @@ class HBase
40
51
  mvn = `which mvn`
41
52
  raise RuntimeError, "Cannot find `mvn` executable" if mvn.empty?
42
53
 
54
+ # POM file path given (with optional profile)
43
55
  if File.exists?(dist)
44
56
  path = dist
45
57
  profile = options[:profile] && "-P #{options[:profile]}"
58
+ # Predefined dependencies
46
59
  else
47
- path = File.expand_path("../pom/pom.xml", __FILE__)
48
- profile = "-P #{dist}"
60
+ matched_profiles = SUPPORTED_PROFILES.keys.select { |pf| dist.start_with? pf }
61
+ if matched_profiles.length != 1
62
+ raise ArgumentError, "Invalid profile: #{dist}"
63
+ end
64
+ matched_profile = matched_profiles.first
65
+ profiles = SUPPORTED_PROFILES.dup
66
+ profiles[matched_profile] = dist if dist != matched_profile
67
+ tempfiles << tf = Tempfile.new('hbase-jruby-pom')
68
+ erb = ERB.new(File.read File.expand_path("../pom/pom.xml.erb", __FILE__))
69
+ tf << erb.result(binding)
70
+ tf.close(false)
71
+ path = tf.path
72
+ profile = "-P #{matched_profile}"
49
73
  end
50
74
 
51
75
  # Download dependent JAR files and build classpath string
@@ -14,8 +14,8 @@
14
14
  <id>cdh4.1</id>
15
15
  <properties>
16
16
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
17
- <hadoop.version>2.0.0-cdh4.1.2</hadoop.version>
18
- <hbase.version>0.92.1-cdh4.1.2</hbase.version>
17
+ <hadoop.version>2.0.0-<%= profiles['cdh4.1'] %></hadoop.version>
18
+ <hbase.version>0.92.1-<%= profiles['cdh4.1'] %></hbase.version>
19
19
  </properties>
20
20
  <repositories>
21
21
  <repository>
@@ -51,8 +51,8 @@
51
51
  <id>cdh3</id>
52
52
  <properties>
53
53
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
54
- <hbase.version>0.90.6-cdh3u5</hbase.version>
55
- <hadoop.version>0.20.2-cdh3u5</hadoop.version>
54
+ <hbase.version>0.90.6-<%= profiles['cdh3'] %></hbase.version>
55
+ <hadoop.version>0.20.2-<%= profiles['cdh3'] %></hadoop.version>
56
56
  </properties>
57
57
 
58
58
  <repositories>
@@ -83,7 +83,7 @@
83
83
  <properties>
84
84
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
85
85
  <hadoop.version>1.1.1</hadoop.version>
86
- <hbase.version>0.92.2</hbase.version>
86
+ <hbase.version><%= profiles['0.92'] %></hbase.version>
87
87
  </properties>
88
88
 
89
89
  <dependencies>
@@ -108,7 +108,7 @@
108
108
  <properties>
109
109
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
110
110
  <hadoop.version>1.1.1</hadoop.version>
111
- <hbase.version>0.94.3</hbase.version>
111
+ <hbase.version><%= profiles['0.94'] %></hbase.version>
112
112
  </properties>
113
113
 
114
114
  <dependencies>
@@ -127,75 +127,6 @@
127
127
  </dependency>
128
128
  </dependencies>
129
129
  </profile>
130
-
131
- <!-- For backward compatibility -->
132
- <profile>
133
- <id>cdh4.1.2</id>
134
- <properties>
135
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
136
- <hadoop.version>2.0.0-cdh4.1.2</hadoop.version>
137
- <hbase.version>0.92.1-cdh4.1.2</hbase.version>
138
- </properties>
139
- <repositories>
140
- <repository>
141
- <id>cloudera-releases</id>
142
- <url>https://repository.cloudera.com/artifactory/cloudera-repos</url>
143
- <releases>
144
- <enabled>true</enabled>
145
- </releases>
146
- <snapshots>
147
- <enabled>false</enabled>
148
- </snapshots>
149
- </repository>
150
- </repositories>
151
-
152
- <dependencies>
153
- <dependency>
154
- <groupId>org.apache.hadoop</groupId>
155
- <artifactId>hadoop-common</artifactId>
156
- <version>${hadoop.version}</version>
157
- <scope>compile</scope>
158
- </dependency>
159
-
160
- <dependency>
161
- <groupId>org.apache.hbase</groupId>
162
- <artifactId>hbase</artifactId>
163
- <version>${hbase.version}</version>
164
- <scope>compile</scope>
165
- </dependency>
166
- </dependencies>
167
- </profile>
168
-
169
- <profile>
170
- <id>cdh3u5</id>
171
- <properties>
172
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
173
- <hbase.version>0.90.6-cdh3u5</hbase.version>
174
- <hadoop.version>0.20.2-cdh3u5</hadoop.version>
175
- </properties>
176
-
177
- <repositories>
178
- <repository>
179
- <id>cloudera-releases</id>
180
- <url>https://repository.cloudera.com/artifactory/cloudera-repos</url>
181
- <releases>
182
- <enabled>true</enabled>
183
- </releases>
184
- <snapshots>
185
- <enabled>false</enabled>
186
- </snapshots>
187
- </repository>
188
- </repositories>
189
-
190
- <dependencies>
191
- <dependency>
192
- <groupId>org.apache.hbase</groupId>
193
- <artifactId>hbase</artifactId>
194
- <version>${hbase.version}</version>
195
- <scope>compile</scope>
196
- </dependency>
197
- </dependencies>
198
- </profile>
199
130
  </profiles>
200
131
  </project>
201
132
 
@@ -217,6 +217,28 @@ class Scoped
217
217
  spawn :@batch, b
218
218
  end
219
219
 
220
+ # Returns an HBase::Scoped object with the Scan-customization block added.
221
+ # The given block will be evaluated just before an actual scan operation.
222
+ # With method-chaining, multiple blocks can be registered to be evaluated sequentially.
223
+ # @return [HBase::Scoped]
224
+ # @yield [org.apache.hadoop.hbase.client.Scan]
225
+ def with_java_scan &block
226
+ raise ArgumentError, "Block not given" if block.nil?
227
+ raise ArgumentError, "Invalid arity: should be 1" unless block.arity == 1
228
+ spawn :@scan_cbs, @scan_cbs + [block]
229
+ end
230
+
231
+ # Returns an HBase::Scoped object with the Get-customization block added
232
+ # The given block will be evaluated just before an actual get operation.
233
+ # With method-chaining, multiple blocks can be registered to be evaluated sequentially.
234
+ # @return [HBase::Scoped]
235
+ # @yield [org.apache.hadoop.hbase.client.Get]
236
+ def with_java_get &block
237
+ raise ArgumentError, "Block not given" if block.nil?
238
+ raise ArgumentError, "Invalid arity: should be 1" unless block.arity == 1
239
+ spawn :@get_cbs, @get_cbs + [block]
240
+ end
241
+
220
242
  private
221
243
  # @param [HBase::Table] table
222
244
  def initialize table
@@ -230,6 +252,8 @@ private
230
252
  @caching = nil
231
253
  @limit = nil
232
254
  @trange = nil
255
+ @scan_cbs = []
256
+ @get_cbs = []
233
257
  end
234
258
 
235
259
  def spawn *args
@@ -365,6 +389,11 @@ private
365
389
  when Time, Fixnum
366
390
  get.setTimeStamp @trange
367
391
  end
392
+
393
+ # Customization
394
+ @get_cbs.each do |prc|
395
+ prc.call get
396
+ end
368
397
  }
369
398
  end
370
399
 
@@ -494,6 +523,11 @@ private
494
523
 
495
524
  # Batch
496
525
  scan.setBatch @batch if @batch
526
+
527
+ # Customization
528
+ @scan_cbs.each do |prc|
529
+ prc.call scan
530
+ end
497
531
  }
498
532
  end
499
533
 
@@ -40,6 +40,14 @@ class Table
40
40
  end
41
41
  end
42
42
 
43
+ def with_java_scan &block
44
+ self.each.with_java_scan(&block)
45
+ end
46
+
47
+ def with_java_get &block
48
+ self.each.with_java_get(&block)
49
+ end
50
+
43
51
  # Performs PUT operations
44
52
  # @overload put(rowkey, data)
45
53
  # Put operation on a rowkey
@@ -119,6 +127,13 @@ class Table
119
127
  }
120
128
  end
121
129
 
130
+ # Delete rows.
131
+ # @param [*Object] rowkeys List of rowkeys of rows to delete
132
+ # @return [nil]
133
+ def delete_row *rowkeys
134
+ htable.delete rowkeys.map { |rk| Delete.new(Util.to_bytes rk) }
135
+ end
136
+
122
137
  # Atomically increase numeric values
123
138
  # @overload increment(rowkey, column, by)
124
139
  # Atomically increase column value by the specified amount
@@ -1,5 +1,5 @@
1
1
  class HBase
2
2
  module JRuby
3
- VERSION = "0.2.1"
3
+ VERSION = "0.2.2"
4
4
  end
5
5
  end
data/test/test_scoped.rb CHANGED
@@ -390,6 +390,8 @@ class TestScoped < TestHBaseJRubyBase
390
390
  @table.put :rowkey3 => { 'cf1:a' => { t3 => 3 } }
391
391
  @table.put :rowkey4 => { 'cf1:a' => { t4 => 4 } }
392
392
 
393
+ assert_equal 4, @table.count
394
+
393
395
  assert_equal 1, @table.time_range(t2, t3).count
394
396
  assert_equal 2, @table.time_range(t2, t3 + 1).count
395
397
  assert_equal 2, @table.time_range(t2, t4).count
@@ -413,5 +415,43 @@ class TestScoped < TestHBaseJRubyBase
413
415
  # according to current hbase impl, later call overrides the previous time ranges. but, why do this?
414
416
  assert_equal 2, @table.time_range(t2, t3).at(t1).count
415
417
  end
418
+
419
+ def test_with_java_scan
420
+ ('a'..'z').each do |rk|
421
+ @table.put rk, 'cf1:a' => 1
422
+ end
423
+
424
+ assert_equal 2, @table.with_java_scan { |scan|
425
+ scan.setStartRow HBase::Util.to_bytes 'a'
426
+ scan.setStopRow HBase::Util.to_bytes 'd'
427
+ }.with_java_scan { |scan|
428
+ scan.setStartRow HBase::Util.to_bytes 'b'
429
+ }.count
430
+ end
431
+
432
+ def test_with_java_get
433
+ t1, t2, t3, t4 =
434
+ Time.now - 4000,
435
+ Time.now - 3000,
436
+ Time.now - 2000,
437
+ Time.now - 1000
438
+ @table.put :r1 => { 'cf1:a' => { t1 => 1 } }
439
+ @table.put :r2 => { 'cf1:a' => { t2 => 2 } }
440
+ @table.put :r3 => { 'cf1:a' => { t3 => 3 } }
441
+ @table.put :r4 => { 'cf1:a' => { t4 => 4 } }
442
+
443
+ assert_equal 4, @table.count
444
+
445
+ rks = [:r1, :r2, :r3, :r4]
446
+ assert_equal 4, @table.get(rks).compact.count
447
+
448
+ scoped = @table.with_java_get { |get|
449
+ get.setTimeRange(t1.to_i * 1000, t4.to_i * 1000)
450
+ }
451
+ assert_equal 3, scoped.get(rks).compact.count
452
+ assert_equal 2, scoped.with_java_get { |get|
453
+ get.setTimeRange(t2.to_i * 1000, t4.to_i * 1000)
454
+ }.get(rks).compact.count
455
+ end
416
456
  end
417
457
 
data/test/test_table.rb CHANGED
@@ -266,5 +266,21 @@ class TestTable < TestHBaseJRubyBase
266
266
  assert_nil @table.get('row3').to_hash['cf1:a']
267
267
  assert_equal 2, @table.get('row3').fixnum('cf1:b')
268
268
  end
269
+
270
+ def test_delete_row
271
+ @table.put(1 => { 'cf1:a' => 1 }, 's' => { 'cf1:a' => 2 }, { :short => 3 } => { 'cf1:a' => 3 })
272
+
273
+ assert_equal 1, @table.get(1).fixnum('cf1:a')
274
+ assert_equal 2, @table.get('s').fixnum('cf1:a')
275
+ assert_equal 3, @table.get({ :short => 3 }).fixnum('cf1:a')
276
+ assert_equal 3, @table.count
277
+
278
+ @table.delete_row 1, { :short => 3 }
279
+
280
+ assert_equal nil, @table.get(1)
281
+ assert_equal 2, @table.get('s').fixnum('cf1:a')
282
+ assert_equal nil, @table.get({ :short => 3 })
283
+ assert_equal 1, @table.count
284
+ end
269
285
  end
270
286
 
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: hbase-jruby
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.2.1
5
+ version: 0.2.2
6
6
  platform: java
7
7
  authors:
8
8
  - Junegunn Choi
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-29 00:00:00.000000000 Z
12
+ date: 2013-02-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-unit
@@ -47,7 +47,7 @@ dependencies:
47
47
  none: false
48
48
  prerelease: false
49
49
  type: :development
50
- description: Ruby-esque interface for accessing HBase from JRuby
50
+ description: A JRuby binding for HBase
51
51
  email:
52
52
  - junegunn.c@gmail.com
53
53
  executables: []
@@ -68,7 +68,7 @@ files:
68
68
  - lib/hbase-jruby/column_key.rb
69
69
  - lib/hbase-jruby/dependency.rb
70
70
  - lib/hbase-jruby/hbase.rb
71
- - lib/hbase-jruby/pom/pom.xml
71
+ - lib/hbase-jruby/pom/pom.xml.erb
72
72
  - lib/hbase-jruby/result.rb
73
73
  - lib/hbase-jruby/scoped.rb
74
74
  - lib/hbase-jruby/scoped/aggregation.rb
@@ -113,7 +113,7 @@ rubyforge_project:
113
113
  rubygems_version: 1.8.24
114
114
  signing_key:
115
115
  specification_version: 3
116
- summary: Ruby-esque interface for accessing HBase from JRuby
116
+ summary: A JRuby binding for HBase
117
117
  test_files:
118
118
  - test/helper.rb
119
119
  - test/test_aggregation.rb