groonga 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/AUTHORS +4 -0
  2. data/NEWS.ja.rdoc +10 -0
  3. data/NEWS.rdoc +10 -0
  4. data/README.ja.rdoc +9 -3
  5. data/README.rdoc +10 -4
  6. data/Rakefile +1 -1
  7. data/TUTORIAL.ja.rdoc +3 -6
  8. data/example/bookmark.rb +1 -1
  9. data/example/search/config.ru +52 -28
  10. data/ext/rb-grn-column.c +24 -18
  11. data/ext/rb-grn-context.c +165 -17
  12. data/ext/rb-grn-encoding.c +37 -0
  13. data/ext/rb-grn-expression.c +286 -51
  14. data/ext/rb-grn-object.c +27 -8
  15. data/ext/rb-grn-operation.c +128 -22
  16. data/ext/rb-grn-patricia-trie.c +62 -0
  17. data/ext/rb-grn-snippet.c +7 -17
  18. data/ext/rb-grn-table.c +101 -31
  19. data/ext/rb-grn-utils.c +87 -22
  20. data/ext/rb-grn-variable-size-column.c +1 -1
  21. data/ext/rb-grn.h +27 -4
  22. data/ext/rb-groonga.c +12 -2
  23. data/extconf.rb +2 -1
  24. data/html/index.html +2 -2
  25. data/lib/groonga.rb +1 -0
  26. data/lib/groonga/expression-builder.rb +47 -12
  27. data/lib/groonga/patricia-trie.rb +40 -0
  28. data/lib/groonga/record.rb +17 -13
  29. data/misc/grnop2ruby.rb +49 -0
  30. data/pkg-config.rb +1 -1
  31. data/test-unit/lib/test/unit/assertions.rb +5 -2
  32. data/test-unit/lib/test/unit/autorunner.rb +19 -4
  33. data/test-unit/lib/test/unit/collector/load.rb +3 -1
  34. data/test-unit/lib/test/unit/color-scheme.rb +5 -1
  35. data/test-unit/lib/test/unit/error.rb +7 -5
  36. data/test-unit/lib/test/unit/runner/tap.rb +8 -0
  37. data/test-unit/lib/test/unit/ui/console/testrunner.rb +63 -8
  38. data/test-unit/lib/test/unit/ui/tap/testrunner.rb +92 -0
  39. data/test-unit/test/collector/test-load.rb +1 -5
  40. data/test-unit/test/test-color-scheme.rb +4 -0
  41. data/test/groonga-test-utils.rb +10 -0
  42. data/test/run-test.rb +5 -1
  43. data/test/test-column.rb +58 -0
  44. data/test/test-database.rb +8 -1
  45. data/test/test-expression.rb +48 -6
  46. data/test/test-hash.rb +7 -0
  47. data/test/test-patricia-trie.rb +39 -0
  48. data/test/test-record.rb +2 -2
  49. data/test/test-remote.rb +52 -0
  50. data/test/test-schema.rb +1 -1
  51. data/test/test-table-select-normalize.rb +48 -0
  52. data/test/test-table-select.rb +101 -0
  53. data/test/test-table.rb +0 -9
  54. data/test/test-variable-size-column.rb +28 -0
  55. metadata +16 -5
@@ -240,11 +240,7 @@ EOT
240
240
  [:test, {:name => "test1_2"}]]],
241
241
  @test_case1.to_s)
242
242
 
243
- assert_collect([:suite, {:name => @no_load_sub_test_case5.basename.to_s},
244
- [:suite, {:name => _test_case_name("NoLoadSubTestCase5")},
245
- [:test, {:name => "test5_1"}],
246
- [:test, {:name => "test5_2"}]]],
247
- @no_load_sub_test_case5.to_s)
243
+ assert_collect(nil, @no_load_sub_test_case5.to_s)
248
244
  end
249
245
 
250
246
  def test_nil_pattern
@@ -8,6 +8,10 @@ class TestUnitColorScheme < Test::Unit::TestCase
8
8
  "notification" => color("cyan", :bold => true),
9
9
  "error" => color("yellow", :bold => true) +
10
10
  color("black", :foreground => false),
11
+ "case" => color("white", :bold => true) +
12
+ color("blue", :foreground => false),
13
+ "suite" => color("white", :bold => true) +
14
+ color("green", :foreground => false),
11
15
  },
12
16
  Test::Unit::ColorScheme.default.to_hash)
13
17
  end
@@ -15,6 +15,9 @@
15
15
 
16
16
  require 'fileutils'
17
17
  require 'pathname'
18
+ require 'time'
19
+ require 'json'
20
+ require 'pkg-config'
18
21
 
19
22
  require 'groonga'
20
23
 
@@ -88,4 +91,11 @@ module GroongaTestUtils
88
91
  def teardown_tmp_directory
89
92
  FileUtils.rm_rf(@tmp_dir.to_s)
90
93
  end
94
+
95
+ private
96
+ def assert_equal_select_result(expected, actual)
97
+ assert_equal(expected,
98
+ actual.collect {|record| record.key},
99
+ actual.expression.inspect)
100
+ end
91
101
  end
@@ -43,11 +43,15 @@ ARGV.unshift("--priority-mode")
43
43
 
44
44
  $LOAD_PATH.unshift(ext_dir)
45
45
  $LOAD_PATH.unshift(lib_dir)
46
+ $LOAD_PATH.unshift(base_dir)
46
47
 
47
48
  $LOAD_PATH.unshift(test_dir)
48
49
  require 'groonga-test-utils'
49
50
 
50
- Dir.glob("test/**/test{_,-}*.rb") do |file|
51
+ pkg_config = File.join(base_dir, "vendor", "local", "lib", "pkgconfig")
52
+ PackageConfig.prepend_default_path(pkg_config)
53
+
54
+ Dir.glob("#{base_dir}/test/**/test{_,-}*.rb") do |file|
51
55
  require file.sub(/\.rb$/, '')
52
56
  end
53
57
 
@@ -174,6 +174,64 @@ class ColumnTest < Test::Unit::TestCase
174
174
  assert_equal("title", title.local_name)
175
175
  end
176
176
 
177
+ def test_select
178
+ posts = Groonga::Hash.create(:name => "<posts>", :key_type => "<shorttext>")
179
+ body = posts.define_column("body", "<text>")
180
+
181
+ index = Groonga::PatriciaTrie.create(:name => "<terms>",
182
+ :key_type => "<shorttext>",
183
+ :key_normalize => true)
184
+ index.default_tokenizer = "<token:bigram>"
185
+ body_index = index.define_index_column("body_index", posts,
186
+ :with_position => true,
187
+ :source => body)
188
+
189
+ first_post = posts.add("Hello!", :body => "World")
190
+ hobby = posts.add("My Hobby", :body => "Drive and Eat")
191
+
192
+ result = body.select("drive")
193
+ assert_equal(["Drive and Eat"],
194
+ result.records.collect do |record|
195
+ record["body"]
196
+ end)
197
+ end
198
+
199
+ def test_select_with_block
200
+ posts = Groonga::Hash.create(:name => "<posts>", :key_type => "<shorttext>")
201
+ body = posts.define_column("body", "<text>")
202
+
203
+ index = Groonga::PatriciaTrie.create(:name => "<terms>",
204
+ :key_type => "<shorttext>",
205
+ :key_normalize => true)
206
+ index.default_tokenizer = "<token:bigram>"
207
+ body_index = index.define_index_column("body_index", posts,
208
+ :with_position => true,
209
+ :source => body)
210
+
211
+ first_post = posts.add("Hello!", :body => "World")
212
+ hobby = posts.add("My Hobby", :body => "Drive and Eat")
213
+
214
+ result = body.select do |column|
215
+ column =~ "drive"
216
+ end
217
+ assert_equal(["Drive and Eat"],
218
+ result.records.collect do |record|
219
+ record["body"]
220
+ end)
221
+ end
222
+
223
+ def test_set_time
224
+ posts = Groonga::Hash.create(:name => "<posts>", :key_type => "<shorttext>")
225
+ body = posts.define_column("issued", "<time>")
226
+
227
+ post = posts.add("hello", :issued => 123456)
228
+ assert_equal(Time.at(123456), post[".issued"])
229
+ post = posts.add("groonga", :issued => 1251380635)
230
+ assert_equal(Time.parse("2009-08-27 22:43:55"), post[".issued"])
231
+ post = posts.add("mroonga", :issued => 1251380635.1234567)
232
+ assert_in_delta(Time.at(1251380635.1234567).usec, post[".issued"].usec, 10)
233
+ end
234
+
177
235
  private
178
236
  def assert_content_search(expected_records, term)
179
237
  records = @bookmarks_index_content.search(term).records
@@ -79,19 +79,26 @@ class DatabaseTest < Test::Unit::TestCase
79
79
  "TokenMecab",
80
80
  "TokenTrigram",
81
81
  "TokenUnigram",
82
+ "TokyoGeoPoint",
82
83
  "UInt16",
83
84
  "UInt32",
84
85
  "UInt64",
85
86
  "UInt8",
87
+ "WGS84GeoPoint",
86
88
  "column_create",
87
89
  "column_list",
88
90
  "define_selector",
89
91
  "expr_missing",
90
92
  "load",
93
+ "now",
94
+ "quit",
95
+ "rand",
91
96
  "select",
97
+ "shutdown",
92
98
  "status",
93
99
  "table_create",
94
- "table_list"],
100
+ "table_list",
101
+ "view_add"],
95
102
  database.collect {|object| object.name}.sort)
96
103
  end
97
104
 
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
2
3
  #
3
4
  # This library is free software; you can redistribute it and/or
@@ -27,9 +28,10 @@ class ExpressionTest < Test::Unit::TestCase
27
28
  expression = Groonga::Expression.new
28
29
  expression.append_constant(morita)
29
30
  expression.append_constant("name")
30
- expression.append_operation(Groonga::Operation::OBJECT_GET_VALUE, 2)
31
+ expression.append_operation(Groonga::Operation::GET_VALUE, 2)
31
32
  expression.compile
32
- assert_equal("mori daijiro", expression.execute)
33
+ expression.execute
34
+ assert_equal("mori daijiro", context.pop)
33
35
  end
34
36
 
35
37
  def test_get_value_with_variable
@@ -44,12 +46,52 @@ class ExpressionTest < Test::Unit::TestCase
44
46
  variable.value = morita
45
47
  expression.append_object(variable)
46
48
  expression.append_constant("name")
47
- expression.append_operation(Groonga::Operation::OBJECT_GET_VALUE, 2)
49
+ expression.append_operation(Groonga::Operation::GET_VALUE, 2)
48
50
  expression.compile
49
-
50
- assert_equal("mori daijiro", expression.execute)
51
+ expression.execute
52
+ assert_equal("mori daijiro", context.pop)
51
53
 
52
54
  variable.value = gunyara_kun.id
53
- assert_equal("Tasuku SUENAGA", expression.execute)
55
+ expression.execute
56
+ assert_equal("Tasuku SUENAGA", context.pop)
57
+ end
58
+
59
+ def test_inspect
60
+ expression = Groonga::Expression.new
61
+ expression.append_constant(1)
62
+ expression.append_constant(1)
63
+ expression.append_operation(Groonga::Operation::PLUS, 2)
64
+ expression.compile
65
+
66
+ assert_equal("#<Groonga::Expression noname(){1 1}>", expression.inspect)
67
+ end
68
+
69
+ def test_snippet
70
+ users = Groonga::Array.create(:name => "users")
71
+ name = users.define_column("name", "ShortText")
72
+ terms = Groonga::Hash.create(:name => "terms",
73
+ :key_type => "ShortText",
74
+ :default_tokenizer => "TokenBigram")
75
+ users.define_index_column("user_name", users,
76
+ :source => "users.name",
77
+ :with_position => true)
78
+
79
+ expression = Groonga::Expression.new
80
+ variable = expression.define_variable(:domain => users)
81
+ expression.append_object(variable)
82
+ expression.parse("ラングバ OR Ruby OR groonga", :default_column => name)
83
+ expression.compile
84
+
85
+ snippet = expression.snippet([["[[", "]]"], ["<", ">"]],
86
+ :width => 30)
87
+ assert_equal(["[[ラングバ]]プロジェクト",
88
+ "ン[[groonga]]の機能を<Ruby>か",
89
+ "。[[groonga]]の機能を<Ruby>ら"],
90
+ snippet.execute("ラングバプロジェクトはカラムストア機能も" +
91
+ "備える高速・高機能な全文検索エンジンgroonga" +
92
+ "の機能をRubyから利用するためのライブラリを" +
93
+ "提供するプロジェクトです。groongaの機能を" +
94
+ "Rubyらしい読み書きしやすい構文で利用できる" +
95
+ "ことが利点です。"))
54
96
  end
55
97
  end
@@ -203,4 +203,11 @@ class HashTest < Test::Unit::TestCase
203
203
  users.add("morita")
204
204
  assert_true(users.has_key?("morita"))
205
205
  end
206
+
207
+ def test_big_key
208
+ hash = Groonga::Hash.create(:key_type => "UInt64")
209
+ assert_nothing_raised do
210
+ hash.add(1 << 63)
211
+ end
212
+ end
206
213
  end
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
2
3
  #
3
4
  # This library is free software; you can redistribute it and/or
@@ -88,4 +89,42 @@ class PatriciaTrieTest < Test::Unit::TestCase
88
89
  users.add("morita")
89
90
  assert_true(users.has_key?("morita"))
90
91
  end
92
+
93
+ def test_scan
94
+ Groonga::Context.default_options = {:encoding => "utf-8"}
95
+ words = Groonga::PatriciaTrie.create(:key_type => "ShortText",
96
+ :key_normalize => true)
97
+ words.add("リンク")
98
+ adventure_of_link = words.add('リンクの冒険')
99
+ words.add('冒険')
100
+ gaxtu = words.add('ガッ')
101
+ muteki = words.add('MUTEKI')
102
+ assert_equal([[muteki, "muTEki", 0, 6],
103
+ [adventure_of_link, "リンクの冒険", 7, 18],
104
+ [gaxtu, "ガッ", 42, 6]],
105
+ words.scan('muTEki リンクの冒険 ミリバール ガッ'))
106
+ end
107
+
108
+ def test_tag_keys
109
+ Groonga::Context.default_options = {:encoding => "utf-8"}
110
+ words = Groonga::PatriciaTrie.create(:key_type => "ShortText",
111
+ :key_normalize => true)
112
+ words.add("リンク")
113
+ words.add('リンクの冒険')
114
+ words.add('冒険')
115
+ words.add('㍊')
116
+ words.add('ガッ')
117
+ words.add('MUTEKI')
118
+
119
+ text = 'muTEki リンクの冒険 マッチしない ミリバール ガッ'
120
+ actual = words.tag_keys(text) do |record, word|
121
+ "<#{word}(#{record.key})>"
122
+ end
123
+ assert_equal("<muTEki(muteki)> " +
124
+ "<リンクの冒険(リンクの冒険)> " +
125
+ "マッチしない " +
126
+ "<ミリバール(ミリバール)> " +
127
+ "<ガッ(ガッ)>",
128
+ actual)
129
+ end
91
130
  end
@@ -122,14 +122,14 @@ class RecordTest < Test::Unit::TestCase
122
122
 
123
123
  def test_get_nonexistent_column
124
124
  groonga = @bookmarks.add
125
- assert_raise(Groonga::Error) do
125
+ assert_raise(Groonga::InvalidArgument) do
126
126
  groonga["nonexistent"]
127
127
  end
128
128
  end
129
129
 
130
130
  def test_set_nonexistent_column
131
131
  groonga = @bookmarks.add
132
- assert_raise(Groonga::Error) do
132
+ assert_raise(Groonga::InvalidArgument) do
133
133
  groonga["nonexistent"] = "value"
134
134
  end
135
135
  end
@@ -0,0 +1,52 @@
1
+ # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ class RemoteTest < Test::Unit::TestCase
17
+ include GroongaTestUtils
18
+
19
+ setup :before => :append
20
+ def setup_remote_connection
21
+ @process_id = nil
22
+
23
+ package_config = PKGConfig.package_config("groonga")
24
+ groonga = package_config.variable("groonga")
25
+
26
+ @host = "127.0.0.1"
27
+ @port = 12345
28
+ @remote_database_path = @tmp_dir + "remote-database"
29
+ @process_id = Process.fork do
30
+ exec(groonga,
31
+ "-i", @host,
32
+ "-p", @port.to_s,
33
+ "-s", "-n", @remote_database_path.to_s)
34
+ end
35
+ sleep(1)
36
+ end
37
+
38
+ teardown
39
+ def teardown_remote_connection
40
+ Process.kill(:TERM, @process_id) if @process_id
41
+ end
42
+
43
+ def test_send
44
+ _context = Groonga::Context.new
45
+ _context.connect(:host => @host, :port => @port)
46
+ assert_equal(0, _context.send("status"))
47
+ id, result = _context.receive
48
+ assert_equal(0, id)
49
+ assert_equal(["alloc_count", "starttime", "uptime"],
50
+ JSON.load(result).keys.sort)
51
+ end
52
+ end
@@ -125,7 +125,7 @@ class SchemaTest < Test::Unit::TestCase
125
125
  "id: <#{table.id}>, " +
126
126
  "name: <<posts>>, " +
127
127
  "path: <#{path}>, " +
128
- "domain: <nil>, " +
128
+ "domain: <#{type.inspect}>, " +
129
129
  "range: <#{type.inspect}>, " +
130
130
  "flags: <>, " +
131
131
  "size: <0>>",
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2009 SHIDARA Yoji <dara@shidara.net>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ class TableTestSelectNormalize < Test::Unit::TestCase
19
+ include GroongaTestUtils
20
+
21
+ setup :setup_database
22
+
23
+ setup
24
+ def setup_comments
25
+ @comments = Groonga::Array.create(:name => "comments")
26
+ @comments.define_column("content", "Text")
27
+ @comments.define_column("created_at", "Time")
28
+ terms = Groonga::PatriciaTrie.create(:name => "terms",
29
+ :default_tokenizer => "TokenBigram",
30
+ :key_normalize => true)
31
+ terms.define_index_column("comment_content", @comments,
32
+ :with_section => true,
33
+ :source => "comments.content")
34
+ @japanese_comment =
35
+ @comments.add(:content => "うちのボロTVはまだ現役です",
36
+ :created_at => Time.parse("2009-06-09"))
37
+ end
38
+
39
+ def test_select_query_with_japanese
40
+ result = @comments.select("content:%ボロTV")
41
+ assert_equal_select_result([@japanese_comment], result)
42
+ end
43
+
44
+ def test_select_query_only_in_japanese
45
+ result = @comments.select("content:%うちの")
46
+ assert_equal_select_result([@japanese_comment], result)
47
+ end
48
+ end
@@ -0,0 +1,101 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2009 Kouhei Sutou <kou@clear-code.com>
3
+ #
4
+ # This library is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License version 2.1 as published by the Free Software Foundation.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
+
17
+ class TableTestSelect < Test::Unit::TestCase
18
+ include GroongaTestUtils
19
+
20
+ setup :setup_database
21
+
22
+ setup
23
+ def setup_comments
24
+ @comments = Groonga::Array.create(:name => "comments")
25
+ @comments.define_column("content", "Text")
26
+ @comments.define_column("created_at", "Time")
27
+ terms = Groonga::PatriciaTrie.create(:name => "terms",
28
+ :default_tokenizer => "TokenBigram")
29
+ terms.define_index_column("comment_content", @comments,
30
+ :with_section => true,
31
+ :source => "comments.content")
32
+ @comment1 = @comments.add(:content => "Hello Good-bye!",
33
+ :created_at => Time.parse("2009-08-09"))
34
+ @comment2 = @comments.add(:content => "Hello World",
35
+ :created_at => Time.parse("2009-07-09"))
36
+ @comment3 = @comments.add(:content => "test",
37
+ :created_at => Time.parse("2009-06-09"))
38
+ @japanese_comment =
39
+ @comments.add(:content => "うちのボロTVはまだ現役です",
40
+ :created_at => Time.parse("2009-06-09"))
41
+ end
42
+
43
+ def test_select_sub_expression
44
+ result = @comments.select do |record|
45
+ record.match("Hello", "content") &
46
+ (record["created_at"] < Time.parse("2009-08-01"))
47
+ end
48
+ assert_equal_select_result([@comment2], result)
49
+ end
50
+
51
+ def test_select_query
52
+ result = @comments.select("content:%Hello")
53
+ assert_equal_select_result([@comment1, @comment2], result)
54
+ end
55
+
56
+ def test_select_query_with_block
57
+ result = @comments.select("content:%Hello") do |record|
58
+ record["created_at"] < Time.parse("2009-08-01")
59
+ end
60
+ assert_equal_select_result([@comment2], result)
61
+ end
62
+
63
+ def test_select_query_with_block_match
64
+ result = @comments.select("content:%Hello") do |record|
65
+ record.match("World", "content")
66
+ end
67
+ assert_equal_select_result([@comment2], result)
68
+ end
69
+
70
+ def test_select_without_block
71
+ assert_equal_select_result([@comment1, @comment2,
72
+ @comment3, @japanese_comment],
73
+ @comments.select)
74
+ end
75
+
76
+ def test_select_query_japanese
77
+ result = @comments.select("content:%ボロTV")
78
+ assert_equal_select_result([@japanese_comment], result)
79
+ end
80
+
81
+ def test_select_but_query
82
+ result = @comments.select do |record|
83
+ record["content"].match "Hello -World"
84
+ end
85
+ assert_equal_select_result([@comment1], result)
86
+ end
87
+
88
+ def test_select_query_with_three_terms
89
+ result = @comments.select do |record|
90
+ record["content"].match "Say Hello World"
91
+ end
92
+ assert_equal_select_result([], result)
93
+ end
94
+
95
+ def test_select_query_with_brackets
96
+ result = @comments.select do |record|
97
+ record["content"].match "Say (Hello World)"
98
+ end
99
+ assert_equal_select_result([], result)
100
+ end
101
+ end