rroonga 0.9.2-x86-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. data/AUTHORS +5 -0
  2. data/NEWS.ja.rdoc +114 -0
  3. data/NEWS.rdoc +116 -0
  4. data/README.ja.rdoc +65 -0
  5. data/README.rdoc +66 -0
  6. data/Rakefile +206 -0
  7. data/benchmark/common.rb +49 -0
  8. data/benchmark/read-write-many-small-items.rb +144 -0
  9. data/benchmark/write-many-small-items.rb +135 -0
  10. data/example/bookmark.rb +161 -0
  11. data/example/index-html.rb +89 -0
  12. data/example/search/config.ru +230 -0
  13. data/example/search/public/css/groonga.css +122 -0
  14. data/ext/.gitignore +2 -0
  15. data/ext/groonga/extconf.rb +93 -0
  16. data/ext/groonga/rb-grn-accessor.c +52 -0
  17. data/ext/groonga/rb-grn-array-cursor.c +36 -0
  18. data/ext/groonga/rb-grn-array.c +210 -0
  19. data/ext/groonga/rb-grn-column.c +573 -0
  20. data/ext/groonga/rb-grn-context.c +662 -0
  21. data/ext/groonga/rb-grn-database.c +472 -0
  22. data/ext/groonga/rb-grn-encoding-support.c +64 -0
  23. data/ext/groonga/rb-grn-encoding.c +257 -0
  24. data/ext/groonga/rb-grn-exception.c +1110 -0
  25. data/ext/groonga/rb-grn-expression-builder.c +75 -0
  26. data/ext/groonga/rb-grn-expression.c +731 -0
  27. data/ext/groonga/rb-grn-fix-size-column.c +166 -0
  28. data/ext/groonga/rb-grn-hash-cursor.c +38 -0
  29. data/ext/groonga/rb-grn-hash.c +294 -0
  30. data/ext/groonga/rb-grn-index-column.c +488 -0
  31. data/ext/groonga/rb-grn-logger.c +504 -0
  32. data/ext/groonga/rb-grn-object.c +1369 -0
  33. data/ext/groonga/rb-grn-operation.c +198 -0
  34. data/ext/groonga/rb-grn-patricia-trie-cursor.c +39 -0
  35. data/ext/groonga/rb-grn-patricia-trie.c +488 -0
  36. data/ext/groonga/rb-grn-procedure.c +52 -0
  37. data/ext/groonga/rb-grn-query.c +260 -0
  38. data/ext/groonga/rb-grn-record.c +40 -0
  39. data/ext/groonga/rb-grn-snippet.c +334 -0
  40. data/ext/groonga/rb-grn-table-cursor-key-support.c +69 -0
  41. data/ext/groonga/rb-grn-table-cursor.c +247 -0
  42. data/ext/groonga/rb-grn-table-key-support.c +714 -0
  43. data/ext/groonga/rb-grn-table.c +1977 -0
  44. data/ext/groonga/rb-grn-type.c +181 -0
  45. data/ext/groonga/rb-grn-utils.c +769 -0
  46. data/ext/groonga/rb-grn-variable-size-column.c +36 -0
  47. data/ext/groonga/rb-grn-variable.c +108 -0
  48. data/ext/groonga/rb-grn-view-accessor.c +53 -0
  49. data/ext/groonga/rb-grn-view-cursor.c +35 -0
  50. data/ext/groonga/rb-grn-view-record.c +41 -0
  51. data/ext/groonga/rb-grn-view.c +421 -0
  52. data/ext/groonga/rb-grn.h +698 -0
  53. data/ext/groonga/rb-groonga.c +107 -0
  54. data/extconf.rb +130 -0
  55. data/html/bar.svg +153 -0
  56. data/html/developer.html +117 -0
  57. data/html/developer.svg +469 -0
  58. data/html/download.svg +253 -0
  59. data/html/favicon.ico +0 -0
  60. data/html/favicon.xcf +0 -0
  61. data/html/footer.html.erb +28 -0
  62. data/html/head.html.erb +4 -0
  63. data/html/header.html.erb +17 -0
  64. data/html/index.html +147 -0
  65. data/html/install.svg +636 -0
  66. data/html/logo.xcf +0 -0
  67. data/html/ranguba.css +250 -0
  68. data/html/tutorial.svg +559 -0
  69. data/lib/1.8/groonga.so +0 -0
  70. data/lib/1.9/groonga.so +0 -0
  71. data/lib/groonga.rb +90 -0
  72. data/lib/groonga/context.rb +184 -0
  73. data/lib/groonga/expression-builder.rb +324 -0
  74. data/lib/groonga/patricia-trie.rb +85 -0
  75. data/lib/groonga/record.rb +311 -0
  76. data/lib/groonga/schema.rb +1191 -0
  77. data/lib/groonga/view-record.rb +56 -0
  78. data/license/GPL +340 -0
  79. data/license/LGPL +504 -0
  80. data/license/RUBY +59 -0
  81. data/misc/grnop2ruby.rb +49 -0
  82. data/pkg-config.rb +333 -0
  83. data/rroonga-build.rb +57 -0
  84. data/test-unit/Rakefile +40 -0
  85. data/test-unit/TODO +5 -0
  86. data/test-unit/bin/testrb +5 -0
  87. data/test-unit/html/classic.html +15 -0
  88. data/test-unit/html/index.html +25 -0
  89. data/test-unit/html/index.html.ja +27 -0
  90. data/test-unit/lib/test/unit.rb +323 -0
  91. data/test-unit/lib/test/unit/assertionfailederror.rb +25 -0
  92. data/test-unit/lib/test/unit/assertions.rb +1230 -0
  93. data/test-unit/lib/test/unit/attribute.rb +125 -0
  94. data/test-unit/lib/test/unit/autorunner.rb +360 -0
  95. data/test-unit/lib/test/unit/collector.rb +36 -0
  96. data/test-unit/lib/test/unit/collector/descendant.rb +23 -0
  97. data/test-unit/lib/test/unit/collector/dir.rb +108 -0
  98. data/test-unit/lib/test/unit/collector/load.rb +144 -0
  99. data/test-unit/lib/test/unit/collector/objectspace.rb +34 -0
  100. data/test-unit/lib/test/unit/color-scheme.rb +102 -0
  101. data/test-unit/lib/test/unit/color.rb +96 -0
  102. data/test-unit/lib/test/unit/diff.rb +724 -0
  103. data/test-unit/lib/test/unit/error.rb +130 -0
  104. data/test-unit/lib/test/unit/exceptionhandler.rb +39 -0
  105. data/test-unit/lib/test/unit/failure.rb +136 -0
  106. data/test-unit/lib/test/unit/fixture.rb +176 -0
  107. data/test-unit/lib/test/unit/notification.rb +129 -0
  108. data/test-unit/lib/test/unit/omission.rb +191 -0
  109. data/test-unit/lib/test/unit/pending.rb +150 -0
  110. data/test-unit/lib/test/unit/priority.rb +180 -0
  111. data/test-unit/lib/test/unit/runner/console.rb +52 -0
  112. data/test-unit/lib/test/unit/runner/emacs.rb +8 -0
  113. data/test-unit/lib/test/unit/runner/tap.rb +8 -0
  114. data/test-unit/lib/test/unit/testcase.rb +476 -0
  115. data/test-unit/lib/test/unit/testresult.rb +89 -0
  116. data/test-unit/lib/test/unit/testsuite.rb +110 -0
  117. data/test-unit/lib/test/unit/ui/console/outputlevel.rb +14 -0
  118. data/test-unit/lib/test/unit/ui/console/testrunner.rb +466 -0
  119. data/test-unit/lib/test/unit/ui/emacs/testrunner.rb +63 -0
  120. data/test-unit/lib/test/unit/ui/tap/testrunner.rb +92 -0
  121. data/test-unit/lib/test/unit/ui/testrunner.rb +28 -0
  122. data/test-unit/lib/test/unit/ui/testrunnermediator.rb +77 -0
  123. data/test-unit/lib/test/unit/ui/testrunnerutilities.rb +41 -0
  124. data/test-unit/lib/test/unit/util/backtracefilter.rb +41 -0
  125. data/test-unit/lib/test/unit/util/method-owner-finder.rb +28 -0
  126. data/test-unit/lib/test/unit/util/observable.rb +90 -0
  127. data/test-unit/lib/test/unit/util/procwrapper.rb +48 -0
  128. data/test-unit/lib/test/unit/version.rb +7 -0
  129. data/test-unit/sample/adder.rb +13 -0
  130. data/test-unit/sample/subtracter.rb +12 -0
  131. data/test-unit/sample/test_adder.rb +20 -0
  132. data/test-unit/sample/test_subtracter.rb +20 -0
  133. data/test-unit/sample/test_user.rb +23 -0
  134. data/test-unit/test/collector/test-descendant.rb +133 -0
  135. data/test-unit/test/collector/test-load.rb +442 -0
  136. data/test-unit/test/collector/test_dir.rb +406 -0
  137. data/test-unit/test/collector/test_objectspace.rb +100 -0
  138. data/test-unit/test/run-test.rb +15 -0
  139. data/test-unit/test/test-attribute.rb +86 -0
  140. data/test-unit/test/test-color-scheme.rb +67 -0
  141. data/test-unit/test/test-color.rb +47 -0
  142. data/test-unit/test/test-diff.rb +518 -0
  143. data/test-unit/test/test-emacs-runner.rb +60 -0
  144. data/test-unit/test/test-fixture.rb +287 -0
  145. data/test-unit/test/test-notification.rb +33 -0
  146. data/test-unit/test/test-omission.rb +81 -0
  147. data/test-unit/test/test-pending.rb +70 -0
  148. data/test-unit/test/test-priority.rb +119 -0
  149. data/test-unit/test/test-testcase.rb +544 -0
  150. data/test-unit/test/test_assertions.rb +1151 -0
  151. data/test-unit/test/test_error.rb +26 -0
  152. data/test-unit/test/test_failure.rb +33 -0
  153. data/test-unit/test/test_testresult.rb +113 -0
  154. data/test-unit/test/test_testsuite.rb +129 -0
  155. data/test-unit/test/testunit-test-util.rb +14 -0
  156. data/test-unit/test/ui/test_testrunmediator.rb +20 -0
  157. data/test-unit/test/util/test-method-owner-finder.rb +38 -0
  158. data/test-unit/test/util/test_backtracefilter.rb +41 -0
  159. data/test-unit/test/util/test_observable.rb +102 -0
  160. data/test-unit/test/util/test_procwrapper.rb +36 -0
  161. data/test/.gitignore +1 -0
  162. data/test/groonga-test-utils.rb +134 -0
  163. data/test/run-test.rb +58 -0
  164. data/test/test-array.rb +90 -0
  165. data/test/test-column.rb +316 -0
  166. data/test/test-context-select.rb +93 -0
  167. data/test/test-context.rb +73 -0
  168. data/test/test-database.rb +113 -0
  169. data/test/test-encoding.rb +33 -0
  170. data/test/test-exception.rb +93 -0
  171. data/test/test-expression-builder.rb +217 -0
  172. data/test/test-expression.rb +134 -0
  173. data/test/test-fix-size-column.rb +65 -0
  174. data/test/test-gqtp.rb +72 -0
  175. data/test/test-hash.rb +305 -0
  176. data/test/test-index-column.rb +81 -0
  177. data/test/test-logger.rb +37 -0
  178. data/test/test-patricia-trie.rb +205 -0
  179. data/test/test-procedure.rb +37 -0
  180. data/test/test-query.rb +22 -0
  181. data/test/test-record.rb +243 -0
  182. data/test/test-remote.rb +54 -0
  183. data/test/test-schema-view.rb +90 -0
  184. data/test/test-schema.rb +459 -0
  185. data/test/test-snippet.rb +130 -0
  186. data/test/test-table-cursor.rb +153 -0
  187. data/test/test-table-offset-and-limit.rb +102 -0
  188. data/test/test-table-select-normalize.rb +53 -0
  189. data/test/test-table-select.rb +150 -0
  190. data/test/test-table.rb +594 -0
  191. data/test/test-type.rb +71 -0
  192. data/test/test-variable-size-column.rb +98 -0
  193. data/test/test-variable.rb +28 -0
  194. data/test/test-vector-column.rb +76 -0
  195. data/test/test-version.rb +31 -0
  196. data/test/test-view.rb +72 -0
  197. data/text/TUTORIAL.ja.rdoc +392 -0
  198. data/text/expression.rdoc +284 -0
  199. metadata +276 -0
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2009-2010 Kouhei Sutou <kou@clear-code.com>
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
+ module Groonga
19
+ class PatriciaTrie
20
+ # call-seq:
21
+ # patricia_trie.tag_keys(text, options={}) {|record, word| ...} -> String
22
+ #
23
+ # _text_を走査し、レコードのキーとマッチする部分文字列ごとに
24
+ # そのレコードが_record_として、その部分文字列が_word_として、
25
+ # ブロックが呼び出される。ブロックから返された文字列が元の部
26
+ # 分文字列と置換される。全てのヒットに対してのその置換処理が
27
+ # 行われた文字列が返される。
28
+ #
29
+ # _options_に指定可能な値は以下の通り。
30
+ #
31
+ # [+:other_text_handler+]
32
+ # マッチした部分文字列の前後の文字列を変換するProcを指
33
+ # 定する。
34
+ #
35
+ # 例:
36
+ # include ERB::Util
37
+ # Groonga::Context.default_options = {:encoding => "utf-8"}
38
+ # words = Groonga::PatriciaTrie.create(:key_type => "ShortText",
39
+ # :key_normalize => true)
40
+ # words.add('ガッ')
41
+ # words.add('MUTEKI')
42
+ #
43
+ # text = 'muTEki マッチしない <> ガッ'
44
+ # other_text_handler = Proc.new do |string|
45
+ # h(string)
46
+ # end
47
+ # words.tag_keys(text) do |record, word|
48
+ # "<span class=\"keyword\">#{h(word)}(#{h(record.key)})</span>\n"
49
+ # end
50
+ # # =>
51
+ # # "<span class=\"keyword\">muTEki(muteki)</span>\n" +
52
+ # # " マッチしない &lt;&gt; " +
53
+ # # "<span class=\"keyword\">ガッ(ガッ)</span>\n"
54
+ def tag_keys(text, options={})
55
+ options ||= {}
56
+ other_text_handler = options[:other_text_handler]
57
+ position = 0
58
+ result = ''
59
+ if text.respond_to?(:encoding)
60
+ encoding = text.encoding
61
+ bytes = text.dup.force_encoding("ascii-8bit")
62
+ else
63
+ encoding = nil
64
+ bytes = text
65
+ end
66
+ scan(text) do |record, word, start, length|
67
+ previous_text = bytes[position...start]
68
+ previous_text.force_encoding(encoding) if encoding
69
+ if other_text_handler
70
+ previous_text = other_text_handler.call(previous_text)
71
+ end
72
+ result << previous_text
73
+ result << yield(record, word)
74
+ position = start + length
75
+ end
76
+ last_text = bytes[position..-1]
77
+ unless last_text.empty?
78
+ last_text.force_encoding(encoding) if encoding
79
+ last_text = other_text_handler.call(last_text) if other_text_handler
80
+ result << last_text
81
+ end
82
+ result
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,311 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2009-2010 Kouhei Sutou <kou@clear-code.com>
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
+ module Groonga
19
+ class Record
20
+ # レコードが所属するテーブル
21
+ attr_reader :table
22
+ # レコードのID
23
+ attr_reader :id
24
+ # _table_の_id_に対応するレコードを作成する。_values_には各
25
+ # カラムに設定する値を以下のような形式で指定する。
26
+ #
27
+ # [
28
+ # ["カラム名", 値],
29
+ # ["カラム名", 値],
30
+ # ...,
31
+ # ]
32
+ def initialize(table, id, values=nil)
33
+ @table = table
34
+ @id = id
35
+ if values
36
+ values.each do |name, value|
37
+ self[name] = value
38
+ end
39
+ end
40
+ end
41
+
42
+ # call-seq:
43
+ # record == other -> true/false
44
+ #
45
+ # _record_と_other_が同じgroongaのレコードなら+true+を返し、
46
+ # そうでなければ+false+を返す。
47
+ def ==(other)
48
+ self.class == other.class and
49
+ [table, id] == [other.table, other.id]
50
+ end
51
+
52
+ # call-seq:
53
+ # record[column_name] -> 値
54
+ #
55
+ # このレコードの_column_name_で指定されたカラムの値を返す。
56
+ def [](column_name)
57
+ @table.column_value(@id, column_name, :id => true)
58
+ end
59
+
60
+ # call-seq:
61
+ # record[column_name] = 値
62
+ #
63
+ # このレコードの_column_name_で指定されたカラムの値を設定す
64
+ # る。
65
+ def []=(column_name, value)
66
+ @table.set_column_value(@id, column_name, value, :id => true)
67
+ end
68
+
69
+ # call-seq:
70
+ # record.append(column_name, value)
71
+ #
72
+ # このレコードの_column_name_で指定されたカラムの値の最後に
73
+ # _value_を追加する。
74
+ def append(column_name, value)
75
+ column(column_name).append(@id, value)
76
+ end
77
+
78
+ # call-seq:
79
+ # record.prepend(column_name, value)
80
+ #
81
+ # このレコードの_column_name_で指定されたカラムの値の最初に
82
+ # _value_を追加する。
83
+ def prepend(column_name, value)
84
+ column(column_name).prepend(@id, value)
85
+ end
86
+
87
+ # call-seq:
88
+ # record.have_column?(name) -> true/false
89
+ #
90
+ # 名前が_name_のカラムがレコードの所属するテーブルで定義され
91
+ # ているなら+true+を返す。
92
+ def have_column?(name)
93
+ column(name).is_a?(Groonga::Column)
94
+ rescue Groonga::NoSuchColumn
95
+ false
96
+ end
97
+
98
+ # call-seq:
99
+ # record.reference_column?(name) -> true/false
100
+ #
101
+ # 名前が_name_のカラムが参照カラムであるなら+true+を返す。
102
+ def reference_column?(name)
103
+ column(name).range.is_a?(Groonga::Table)
104
+ end
105
+
106
+ # call-seq:
107
+ # record.search(name, query, options={}) -> Groonga::Hash
108
+ #
109
+ # 名前が_name_のGroonga::IndexColumnのsearchメソッドを呼ぶ。
110
+ # _query_と_options_はそのメソッドにそのまま渡される。詳しく
111
+ # はGroonga::IndexColumn#searchを参照。
112
+ def search(name, query, options={})
113
+ column(name).search(query, options)
114
+ end
115
+
116
+ # call-seq:
117
+ # record.key -> 主キー
118
+ #
119
+ # レコードの主キーを返す。
120
+ #
121
+ # _record_が所属するテーブルがGroonga:::Arrayの場合は常
122
+ # に+nil+を返す。
123
+ def key
124
+ if @table.is_a?(Groonga::Array)
125
+ nil
126
+ else
127
+ @key ||= @table.key(@id)
128
+ end
129
+ end
130
+
131
+ # call-seq:
132
+ # record.score -> スコア値
133
+ #
134
+ # レコードのスコア値を返す。検索結果として生成されたテーブル
135
+ # のみに定義される。
136
+ def score
137
+ self["._score"]
138
+ end
139
+
140
+ # call-seq:
141
+ # record.n_sub_records -> 件数
142
+ #
143
+ # 主キーの値が同一であったレコードの件数を返す。検索結果とし
144
+ # て生成されたテーブルのみに定義される。
145
+ def n_sub_records
146
+ self["._nsubrecs"]
147
+ end
148
+
149
+ # call-seq:
150
+ # record.value -> 値
151
+ #
152
+ # レコードの値を返す。
153
+ def value
154
+ @table.value(@id, :id => true)
155
+ end
156
+
157
+ # call-seq:
158
+ # record.value = 値
159
+ #
160
+ # レコードの値を設定する。既存の値は上書きされる。
161
+ def value=(value)
162
+ @table.set_value(@id, value, :id => true)
163
+ end
164
+
165
+ # call-seq:
166
+ # record.increment!(name, delta=nil)
167
+ #
168
+ # このレコードの_name_で指定されたカラムの値を_delta_だけ増
169
+ # 加する。_delta_が+nil+の場合は1増加する。
170
+ def increment!(name, delta=nil)
171
+ column(name).increment!(@id, delta)
172
+ end
173
+
174
+ # call-seq:
175
+ # record.decrement!(name, delta=nil)
176
+ #
177
+ # このレコードの_name_で指定されたカラムの値を_delta_だけ減
178
+ # 少する。_delta_が+nil+の場合は1減少する。
179
+ def decrement!(name, delta=nil)
180
+ column(name).decrement!(@id, delta)
181
+ end
182
+
183
+ # call-seq:
184
+ # record.columns -> Groonga::Columnの配列
185
+ #
186
+ # レコードが所属するテーブルの全てのカラムを返す。
187
+ def columns
188
+ @table.columns
189
+ end
190
+
191
+ # call-seq:
192
+ # attributes -> Hash
193
+ #
194
+ # レコードが所属しているテーブルで定義されているインデックス
195
+ # 型のカラムでない全カラムを対象とし、カラムの名前をキーとし
196
+ # たこのレコードのカラムの値のハッシュを返す。
197
+ def attributes
198
+ attributes = {"id" => id}
199
+ _key = key
200
+ attributes["key"] = _key if _key
201
+ table_name = @table.name
202
+ columns.each do |column|
203
+ next if column.is_a?(Groonga::IndexColumn)
204
+ value = column[@id]
205
+ # TODO: support recursive reference.
206
+ value = value.attributes if value.is_a?(Groonga::Record)
207
+ attributes[column.local_name] = value
208
+ end
209
+ attributes
210
+ end
211
+
212
+ # call-seq:
213
+ # record.delete
214
+ #
215
+ # レコードを削除する。
216
+ def delete
217
+ @table.delete(@id)
218
+ end
219
+
220
+ # call-seq:
221
+ # record.lock(options={})
222
+ # record.lock(options={}) {...}
223
+ #
224
+ # レコードが所属するテーブルをロックする。ロックに失敗した場
225
+ # 合はGroonga::ResourceDeadlockAvoided例外が発生する。
226
+ #
227
+ # ブロックを指定した場合はブロックを抜けたときにunlockする。
228
+ #
229
+ # 利用可能なオプションは以下の通り。
230
+ #
231
+ # [_:timeout_]
232
+ # ロックを獲得できなかった場合は_:timeout_秒間ロックの獲
233
+ # 得を試みる。_:timeout_秒以内にロックを獲得できなかった
234
+ # 場合は例外が発生する。
235
+ def lock(options={}, &block)
236
+ @table.lock(options.merge(:id => @id), &block)
237
+ end
238
+
239
+ # call-seq:
240
+ # record.unlock(options={})
241
+ #
242
+ # レコードが所属するテーブルのロックを解除する。
243
+ #
244
+ # 利用可能なオプションは現在は無い。
245
+ def unlock(options={})
246
+ @table.unlock(options.merge(:id => @id))
247
+ end
248
+
249
+ # call-seq:
250
+ # record.clear_lock(options={})
251
+ #
252
+ # レコードが所属するテーブルのロックを強制的に解除する。
253
+ #
254
+ # 利用可能なオプションは現在は無い。
255
+ def clear_lock(options={})
256
+ @table.clear_lock(options.merge(:id => @id))
257
+ end
258
+
259
+ # call-seq:
260
+ # record.locked?(options={}) -> true/false
261
+ #
262
+ # レコードが所属するテーブルがロックされていれば+true+を返す。
263
+ #
264
+ # 利用可能なオプションは現在は無い。
265
+ def locked?(options={})
266
+ @table.locked?(options.merge(:id => @id))
267
+ end
268
+
269
+ def methods(include_inherited=true) # :nodoc:
270
+ _methods = super
271
+ return _methods unless include_inherited
272
+ columns.each do |column|
273
+ name = column.local_name
274
+ _methods << name
275
+ _methods << "#{name}="
276
+ end
277
+ _methods
278
+ end
279
+
280
+ def respond_to?(name) # :nodoc:
281
+ super or !@table.column(name.to_s.sub(/=\z/, '')).nil?
282
+ end
283
+
284
+ private
285
+ def column(name) # :nodoc:
286
+ _column = @table.column(name.to_s)
287
+ raise NoSuchColumn, "column(#{name.inspect}) is nil" if _column.nil?
288
+ _column
289
+ end
290
+
291
+ def method_missing(name, *args, &block)
292
+ if /=\z/ =~ name.to_s
293
+ base_name = $PREMATCH
294
+ is_setter = true
295
+ else
296
+ base_name = name.to_s
297
+ is_setter = false
298
+ end
299
+ _column = @table.column(base_name)
300
+ if _column
301
+ if is_setter
302
+ _column.send("[]=", @id, *args, &block)
303
+ else
304
+ _column.send("[]", @id, *args, &block)
305
+ end
306
+ else
307
+ super
308
+ end
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,1191 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2009-2010 Kouhei Sutou <kou@clear-code.com>
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
+
19
+ module Groonga
20
+
21
+ # groongaのスキーマ(データ構造)を管理するクラス。
22
+ #
23
+ # Groonga::Schemaを使うことにより簡単にテーブルやカラムを
24
+ # 追加・削除することができる。
25
+ #
26
+ # http://qwik.jp/senna/senna2.files/rect4605.png
27
+ # のようなスキーマを定義する場合は以下のようになる。
28
+ #
29
+ # Groonga::Schema.define do |schema|
30
+ # schema.create_table("Items") do |table|
31
+ # table.short_text("title")
32
+ # end
33
+ #
34
+ # schema.create_table("Users") do |table|
35
+ # table.short_text("name")
36
+ # end
37
+ #
38
+ # schema.create_table("comments") do |table|
39
+ # table.reference("item", "Items")
40
+ # table.reference("author", "Users")
41
+ # table.text("content")
42
+ # table.time("issued")
43
+ # end
44
+ # end
45
+ class Schema
46
+ class << self
47
+
48
+ # call-seq:
49
+ # Groonga::Schema.define(options={}) {|schema| ...}
50
+ #
51
+ # スキーマを定義する。ブロックにはGroonga::Schemaオブ
52
+ # ジェクトがわたるので、そのオブジェクトを利用してスキー
53
+ # マを定義する。以下の省略形。
54
+ #
55
+ # schema = Groonga::Scheme.new(options)
56
+ # ...
57
+ # schema.define
58
+ #
59
+ # _options_に指定可能な値は以下の通り。
60
+ #
61
+ # [+:context+]
62
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
63
+ # 省略した場合はGroonga::Context.defaultを使用する。
64
+ def define(options={})
65
+ schema = new(options)
66
+ yield(schema)
67
+ schema.define
68
+ end
69
+
70
+ # call-seq:
71
+ # Groonga::Schema.create_table(name, options={}) {|table| ...}
72
+ #
73
+ # 名前が_name_のテーブルを作成する。以下の省略形。
74
+ #
75
+ # Groonga::Schema.define do |schema|
76
+ # schema.create_table(name, options) do |table|
77
+ # ...
78
+ # end
79
+ # end
80
+ #
81
+ # ブロックにはGroonga::Schema::TableDefinitionオブジェ
82
+ # クトがわたるので、そのオブジェクトを利用してテーブル
83
+ # の詳細を定義する。
84
+ #
85
+ # _options_に指定可能な値は以下の通り。
86
+ #
87
+ # [+:force+]
88
+ # +true+を指定すると既存の同名のテーブルが存在してい
89
+ # ても、強制的にテーブルを作成する。
90
+ #
91
+ # [+:type+]
92
+ # テーブルの型を指定する。+:array+, +:hash+,
93
+ # +:patricia_trie+のいずれかを指定する。デフォルトで
94
+ # は+:array+になる。
95
+ #
96
+ # [+:context+]
97
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
98
+ # 省略した場合はGroonga::Context.defaultを使用する。
99
+ #
100
+ # [+:path+]
101
+ # テーブルを保存するパスを指定する。パスを指定すると
102
+ # 永続テーブルになる。
103
+ #
104
+ # [+:persistent+]
105
+ # テーブルを永続テーブルとする。+:path:+を省略した場
106
+ # 合はパス名は自動的に作成される。デフォルトでは永続
107
+ # テーブルとなる。
108
+ #
109
+ # [+:value_type+]
110
+ # 値の型を指定する。省略すると値のための領域を確保しない。
111
+ # 値を保存したい場合は必ず指定すること。
112
+ #
113
+ # [+:sub_records+]
114
+ # +true+を指定するとGroonga::Table#groupでグループ化
115
+ # したときに、Groonga::Record#n_sub_recordsでグルー
116
+ # プに含まれるレコードの件数を取得できる。
117
+ #
118
+ # 以下は+:type+に+:hash+あるいは+:patricia_trie+を指定
119
+ # した時に指定可能。
120
+ #
121
+ # [+:key_type+]
122
+ # キーの種類を示すオブジェクトを指定する。キーの種類
123
+ # には型名("Int32"や"ShortText"など)または
124
+ # Groonga::Typeまたはテーブル(Groonga::Array、
125
+ # Groonga::Hash、Groonga::PatriciaTrieのどれか)を指
126
+ # 定する。
127
+ #
128
+ # Groonga::Typeを指定した場合は、その型が示す範囲の
129
+ # 値をキーとして使用する。ただし、キーの最大サイズは
130
+ # 4096バイトであるため、Groonga::Type::TEXTや
131
+ # Groonga::Type::LONG_TEXTは使用できない。
132
+ #
133
+ # テーブルを指定した場合はレコードIDをキーとして使用
134
+ # する。指定したテーブルのGroonga::Recordをキーとし
135
+ # て使用することもでき、その場合は自動的に
136
+ # Groonga::RecordからレコードIDを取得する。
137
+ #
138
+ # 省略した場合は文字列をキーとして使用する。この場合、
139
+ # 4096バイトまで使用可能である。
140
+ #
141
+ # [+:default_tokenizer+]
142
+ # Groonga::IndexColumnで使用するトークナイザを指定す
143
+ # る。デフォルトでは何も設定されていないので、テーブ
144
+ # ルにGroonga::IndexColumnを定義する場合は
145
+ # <tt>"TokenBigram"</tt>などを指定する必要がある。
146
+ #
147
+ # 以下は+:type+に+:patricia_trie+を指定した時に指定可能。
148
+ #
149
+ # [+:key_normalize+]
150
+ # +true+を指定するとキーを正規化する。
151
+ #
152
+ # [+:key_with_sis+]
153
+ # +true+を指定するとキーの文字列の全suffixが自動的に
154
+ # 登録される。
155
+ def create_table(name, options={}, &block)
156
+ define do |schema|
157
+ schema.create_table(name, options, &block)
158
+ end
159
+ end
160
+
161
+ # 名前が_name_のテーブルを削除する。
162
+ #
163
+ # _options_に指定可能な値は以下の通り。
164
+ #
165
+ # [+:context+]
166
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
167
+ # 省略した場合はGroonga::Context.defaultを使用する。
168
+ def remove_table(name, options={})
169
+ define do |schema|
170
+ schema.remove_table(name, options)
171
+ end
172
+ end
173
+
174
+ # call-seq:
175
+ # Groonga::Schema.change_table(name, options={}) {|table| ...}
176
+ #
177
+ # 名前が_name_のテーブルを変更する。以下の省略形。
178
+ #
179
+ # Groonga::Schema.define do |schema|
180
+ # schema.change_table(name, options) do |table|
181
+ # ...
182
+ # end
183
+ # end
184
+ #
185
+ # ブロックにはGroonga::Schema::TableDefinitionオブジェ
186
+ # クトがわたるので、そのオブジェクトを利用してテーブル
187
+ # の詳細を定義する。
188
+ #
189
+ # _options_に指定可能な値は以下の通り。
190
+ #
191
+ # [+:context+]
192
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
193
+ # 省略した場合はGroonga::Context.defaultを使用する。
194
+ def change_table(name, options={}, &block)
195
+ define do |schema|
196
+ schema.change_table(name, options, &block)
197
+ end
198
+ end
199
+
200
+ # call-seq:
201
+ # Groonga::Schema.create_view(name, options={}) {|view| ...}
202
+ #
203
+ # 名前が_name_のビューを作成する。以下の省略形。
204
+ #
205
+ # Groonga::Schema.define do |schema|
206
+ # schema.create_view(name, options) do |view|
207
+ # ...
208
+ # end
209
+ # end
210
+ #
211
+ # ブロックにはGroonga::Schema::ViewDefinitionオブジェ
212
+ # クトがわたるので、そのオブジェクトを利用してビュー
213
+ # の詳細を定義する。
214
+ #
215
+ # _options_に指定可能な値は以下の通り。
216
+ #
217
+ # [+:force+]
218
+ # +true+を指定すると既存の同名のビューが存在してい
219
+ # ても、強制的にビューを作成する。
220
+ #
221
+ # [+:context+]
222
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
223
+ # 省略した場合はGroonga::Context.defaultを使用する。
224
+ #
225
+ # [+:path+]
226
+ # ビューを保存するパスを指定する。パスを指定すると
227
+ # 永続ビューになる。
228
+ #
229
+ # [+:persistent+]
230
+ # ビューを永続ビューとする。+:path:+を省略した場
231
+ # 合はパス名は自動的に作成される。デフォルトでは永続
232
+ # ビューとなる。
233
+ def create_view(name, options={}, &block)
234
+ define do |schema|
235
+ schema.create_view(name, options, &block)
236
+ end
237
+ end
238
+
239
+ # 名前が_name_のテーブルを削除する。
240
+ #
241
+ # _options_に指定可能な値は以下の通り。
242
+ #
243
+ # [+:context+]
244
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
245
+ # 省略した場合はGroonga::Context.defaultを使用する。
246
+ def remove_view(name, options={})
247
+ define do |schema|
248
+ schema.remove_view(name, options)
249
+ end
250
+ end
251
+
252
+ # call-seq:
253
+ # Groonga::Schema.change_view(name, options={}) {|view| ...}
254
+ #
255
+ # 名前が_name_のビューを変更する。以下の省略形。
256
+ #
257
+ # Groonga::Schema.define do |schema|
258
+ # schema.change_view(name, options) do |view|
259
+ # ...
260
+ # end
261
+ # end
262
+ #
263
+ # ブロックにはGroonga::Schema::ViewDefinitionオブジェ
264
+ # クトがわたるので、そのオブジェクトを利用してテーブル
265
+ # の詳細を定義する。
266
+ #
267
+ # _options_に指定可能な値は以下の通り。
268
+ #
269
+ # [+:context+]
270
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
271
+ # 省略した場合はGroonga::Context.defaultを使用する。
272
+ def change_view(name, options={}, &block)
273
+ define do |schema|
274
+ schema.change_view(name, options, &block)
275
+ end
276
+ end
277
+
278
+ # スキーマの内容を文字列で返す。返された値は
279
+ # Groonga::Schema.restoreすることによりスキーマ内に組
280
+ # み込むことができる。
281
+ #
282
+ # dump.rb:
283
+ # File.open("/tmp/groonga-schema.rb", "w") do |schema|
284
+ # dumped_text = Groonga::Schema.dump
285
+ # end
286
+ #
287
+ # restore.rb:
288
+ # dumped_text = Groonga::Schema.dump
289
+ # Groonga::Database.create(:path => "/tmp/new-db.grn")
290
+ # Groonga::Schema.restore(dumped_text)
291
+ #
292
+ # _options_に指定可能な値は以下の通り。
293
+ #
294
+ # [+:context+]
295
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
296
+ # 省略した場合はGroonga::Context.defaultを使用する。
297
+ def dump(options={})
298
+ Dumper.new(options).dump
299
+ end
300
+
301
+ # Groonga::Schema.dumpで文字列化したスキーマを組み込む。
302
+ def restore(dumped_text, options={})
303
+ define(options) do |schema|
304
+ schema.load(dumped_text)
305
+ end
306
+ end
307
+
308
+ def normalize_type(type) # :nodoc:
309
+ return type if type.nil?
310
+ return type if type.is_a?(Groonga::Object)
311
+ case type.to_s
312
+ when "string"
313
+ "ShortText"
314
+ when "text"
315
+ "Text"
316
+ when "int", "integer"
317
+ "Int32"
318
+ when "float"
319
+ "Float"
320
+ when "decimal"
321
+ "Int64"
322
+ when "datetime", "timestamp", "time", "date"
323
+ "Time"
324
+ when "binary"
325
+ "LongText"
326
+ when "boolean"
327
+ "Bool"
328
+ else
329
+ type
330
+ end
331
+ end
332
+ end
333
+
334
+ # スキーマ定義を開始する。
335
+ #
336
+ # _options_に指定可能な値は以下の通り。
337
+ #
338
+ # [+:context+]
339
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
340
+ # 省略した場合はGroonga::Context.defaultを使用する。
341
+ def initialize(options={})
342
+ @options = (options || {}).dup
343
+ @definitions = []
344
+ end
345
+
346
+ # 定義されたスキーマ定義を実際に実行する。
347
+ def define
348
+ @definitions.each do |definition|
349
+ definition.define
350
+ end
351
+ end
352
+
353
+ # Groonga::Schema.dumpで返されたスキーマの内容を読み込む。
354
+ #
355
+ # 読み込まれた内容は#defineを呼び出すまでは実行されない
356
+ # ことに注意すること。
357
+ def load(dumped_text)
358
+ instance_eval(dumped_text)
359
+ end
360
+
361
+ # call-seq:
362
+ # schema.create_table(name, options={}) {|table| ...}
363
+ #
364
+ # 名前が_name_のテーブルを作成する。
365
+ #
366
+ # テーブルの作成は#defineを呼び出すまでは実行されないこ
367
+ # とに注意すること。
368
+ #
369
+ # _options_に指定可能な値は以下の通り。
370
+ #
371
+ # [+:force+]
372
+ # +true+を指定すると既存の同名のテーブルが存在してい
373
+ # ても、強制的にテーブルを作成する。
374
+ #
375
+ # [+:type+]
376
+ # テーブルの型を指定する。+:array+, +:hash+,
377
+ # +:patricia_trie+のいずれかを指定する。デフォルトで
378
+ # は+:array+になる。
379
+ #
380
+ # [+:context+]
381
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
382
+ # 省略した場合はGroonga::Schema.newで指定した
383
+ # Groonga::Contextを使用する。Groonga::Schema.newで指
384
+ # 定していない場合はGroonga::Context.defaultを使用する。
385
+ #
386
+ # [+:path+]
387
+ # テーブルを保存するパスを指定する。パスを指定すると
388
+ # 永続テーブルになる。
389
+ #
390
+ # [+:persistent+]
391
+ # テーブルを永続テーブルとする。+:path:+を省略した場
392
+ # 合はパス名は自動的に作成される。デフォルトでは永続
393
+ # テーブルとなる。
394
+ #
395
+ # [+:value_type+]
396
+ # 値の型を指定する。省略すると値のための領域を確保しな
397
+ # い。値を保存したい場合は必ず指定すること。
398
+ #
399
+ # 参考: Groonga::Type.new
400
+ #
401
+ # [+:sub_records+]
402
+ # +true+を指定するとGroonga::Table#groupでグループ化
403
+ # したときに、Groonga::Record#n_sub_recordsでグルー
404
+ # プに含まれるレコードの件数を取得できる。
405
+ #
406
+ # 以下は+:type+に+:hash+あるいは+:patricia_trie+を指定
407
+ # した時に指定可能。
408
+ #
409
+ # [+:key_type+]
410
+ # キーの種類を示すオブジェクトを指定する。キーの種類
411
+ # には型名("Int32"や"ShortText"など)または
412
+ # Groonga::Typeまたはテーブル(Groonga::Array、
413
+ # Groonga::Hash、Groonga::PatriciaTrieのどれか)を指
414
+ # 定する。
415
+ #
416
+ # Groonga::Typeを指定した場合は、その型が示す範囲の
417
+ # 値をキーとして使用する。ただし、キーの最大サイズは
418
+ # 4096バイトであるため、Groonga::Type::TEXTや
419
+ # Groonga::Type::LONG_TEXTは使用できない。
420
+ #
421
+ # テーブルを指定した場合はレコードIDをキーとして使用
422
+ # する。指定したテーブルのGroonga::Recordをキーとし
423
+ # て使用することもでき、その場合は自動的に
424
+ # Groonga::RecordからレコードIDを取得する。
425
+ #
426
+ # 省略した場合は文字列をキーとして使用する。この場合、
427
+ # 4096バイトまで使用可能である。
428
+ #
429
+ # [+:default_tokenizer+]
430
+ # Groonga::IndexColumnで使用するトークナイザを指定す
431
+ # る。デフォルトでは何も設定されていないので、テーブ
432
+ # ルにGroonga::IndexColumnを定義する場合は
433
+ # <tt>"TokenBigram"</tt>などを指定する必要がある。
434
+ #
435
+ # 以下は+:type+に+:patricia_trie+を指定した時に指定可能。
436
+ #
437
+ # [+:key_normalize+]
438
+ # +true+を指定するとキーを正規化する。
439
+ #
440
+ # [+:key_with_sis+]
441
+ # +true+を指定するとキーの文字列の全suffixが自動的に
442
+ # 登録される。
443
+ def create_table(name, options={})
444
+ definition = TableDefinition.new(name, @options.merge(options || {}))
445
+ yield(definition)
446
+ @definitions << definition
447
+ end
448
+
449
+ # 名前が_name_のテーブルを削除する。
450
+ #
451
+ # テーブルの削除は#defineを呼び出すまでは実行されないこ
452
+ # とに注意すること。
453
+ #
454
+ # _options_に指定可能な値は以下の通り。
455
+ #
456
+ # [+:context+]
457
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
458
+ # 省略した場合はGroonga::Context.defaultを使用する。
459
+ def remove_table(name, options={})
460
+ definition = TableRemoveDefinition.new(name, @options.merge(options || {}))
461
+ @definitions << definition
462
+ end
463
+
464
+ # call-seq:
465
+ # schema.change_table(name, options={}) {|table| ...}
466
+ #
467
+ # 名前が_name_のテーブルを変更する。
468
+ #
469
+ # テーブルの変更は#defineを呼び出すまでは実行されないこ
470
+ # とに注意すること。
471
+ #
472
+ # _options_に指定可能な値は以下の通り。
473
+ #
474
+ # [+:context+]
475
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
476
+ # 省略した場合はGroonga::Context.defaultを使用する。
477
+ def change_table(name, options={})
478
+ options = @options.merge(options || {}).merge(:change => true)
479
+ definition = TableDefinition.new(name, options)
480
+ yield(definition)
481
+ @definitions << definition
482
+ end
483
+
484
+ # call-seq:
485
+ # schema.create_view(name, options={}) {|view| ...}
486
+ #
487
+ # 名前が_name_のビューを作成する。
488
+ #
489
+ # ビューの作成は#defineを呼び出すまでは実行されないこ
490
+ # とに注意すること。
491
+ #
492
+ # _options_に指定可能な値は以下の通り。
493
+ #
494
+ # [+:force+]
495
+ # +true+を指定すると既存の同名のビューが存在してい
496
+ # ても、強制的にビューを作成する。
497
+ #
498
+ # [+:context+]
499
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
500
+ # 省略した場合はGroonga::Schema.newで指定した
501
+ # Groonga::Contextを使用する。Groonga::Schema.newで指
502
+ # 定していない場合はGroonga::Context.defaultを使用する。
503
+ #
504
+ # [+:path+]
505
+ # テーブルを保存するパスを指定する。パスを指定すると
506
+ # 永続テーブルになる。
507
+ #
508
+ # [+:persistent+]
509
+ # テーブルを永続テーブルとする。+:path:+を省略した場
510
+ # 合はパス名は自動的に作成される。デフォルトでは永続
511
+ # テーブルとなる。
512
+ def create_view(name, options={})
513
+ definition = ViewDefinition.new(name, @options.merge(options || {}))
514
+ yield(definition)
515
+ @definitions << definition
516
+ end
517
+
518
+ # 名前が_name_のビューを削除する。
519
+ #
520
+ # ビューの削除は#defineを呼び出すまでは実行されないことに
521
+ # 注意すること。
522
+ #
523
+ # _options_に指定可能な値は以下の通り。
524
+ #
525
+ # [+:context+]
526
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
527
+ # 省略した場合はGroonga::Context.defaultを使用する。
528
+ def remove_view(name, options={})
529
+ definition = ViewRemoveDefinition.new(name, @options.merge(options || {}))
530
+ @definitions << definition
531
+ end
532
+
533
+ # call-seq:
534
+ # schema.change_view(name, options={}) {|table| ...}
535
+ #
536
+ # 名前が_name_のビューを変更する。
537
+ #
538
+ # ビューの変更は#defineを呼び出すまでは実行されないこ
539
+ # とに注意すること。
540
+ #
541
+ # _options_に指定可能な値は以下の通り。
542
+ #
543
+ # [+:context+]
544
+ # スキーマ定義時に使用するGroonga::Contextを指定する。
545
+ # 省略した場合はGroonga::Context.defaultを使用する。
546
+ def change_view(name, options={})
547
+ options = @options.merge(options || {}).merge(:change => true)
548
+ definition = ViewDefinition.new(name, options)
549
+ yield(definition)
550
+ @definitions << definition
551
+ end
552
+
553
+ # スキーマ定義時にGroonga::Schema.create_tableや
554
+ # Groonga::Schema#create_tableからブロックに渡されてくる
555
+ # オブジェクト
556
+ class TableDefinition
557
+ # テーブルの名前
558
+ attr_reader :name
559
+
560
+ def initialize(name, options) # :nodoc:
561
+ @name = name
562
+ @name = @name.to_s if @name.is_a?(Symbol)
563
+ @definitions = []
564
+ validate_options(options)
565
+ @options = options
566
+ @table_type = table_type
567
+ end
568
+
569
+ def define # :nodoc:
570
+ table = context[@name]
571
+ if @options[:change]
572
+ raise ArgumentError, "table doesn't exist: #{@name}" if table.nil?
573
+ else
574
+ if table and @options[:force]
575
+ table.remove
576
+ table = nil
577
+ end
578
+ table ||= @table_type.create(create_options)
579
+ end
580
+ @definitions.each do |definition|
581
+ definition.define(self, table)
582
+ end
583
+ table
584
+ end
585
+
586
+ # 名前が_name_で型が_type_のカラムを作成する。
587
+ #
588
+ # _options_に指定可能な値は以下の通り。
589
+ #
590
+ # [+:force+]
591
+ # +true+を指定すると既存の同名のカラムが存在してい
592
+ # ても、強制的に新しいカラムを作成する。
593
+ #
594
+ # [+:path+]
595
+ # カラムを保存するパス。
596
+ #
597
+ # [+:persistent+]
598
+ # +true+を指定すると永続カラムとなる。+:path+を省略
599
+ # した場合は自動的にパスが付加される。
600
+ #
601
+ # [+:type+]
602
+ # カラムの値の格納方法について指定する。省略した場合は、
603
+ # +:scalar+になる。
604
+ #
605
+ # [+:scalar+]
606
+ # スカラ値(単独の値)を格納する。
607
+ #
608
+ # [+:vector+]
609
+ # 値の配列を格納する。
610
+ #
611
+ # [+:compress+]
612
+ # 値の圧縮方法を指定する。省略した場合は、圧縮しない。
613
+ #
614
+ # [+:zlib+]
615
+ # 値をzlib圧縮して格納する。
616
+ #
617
+ # [+:lzo+]
618
+ # 値をlzo圧縮して格納する。
619
+ def column(name, type, options={})
620
+ definition = self[name, ColumnDefinition]
621
+ if definition.nil?
622
+ definition = ColumnDefinition.new(name, options)
623
+ update_definition(name, ColumnDefinition, definition)
624
+ end
625
+ definition.type = type
626
+ definition.options.merge!(column_options.merge(options))
627
+ self
628
+ end
629
+
630
+ # 名前が_name_のカラムを削除する。
631
+ #
632
+ # _options_に指定可能な値はない(TODO _options_は不要?)。
633
+ #
634
+ def remove_column(name, options={})
635
+ definition = self[name, ColumnRemoveDefinition]
636
+ if definition.nil?
637
+ definition = ColumnRemoveDefinition.new(name, options)
638
+ update_definition(name, ColumnRemoveDefinition, definition)
639
+ end
640
+ definition.options.merge!(options)
641
+ self
642
+ end
643
+
644
+ # call-seq:
645
+ # table.index(target_column_full_name, options={})
646
+ # table.index(target_table, target_column, options={})
647
+ #
648
+ # _target_table_の_target_column_を対象とするインデッ
649
+ # クスカラムを作成する。
650
+ #
651
+ # _target_column_full_name_で指定するときはテーブル名
652
+ # とカラム名を"."でつなげます。例えば、「Users」テーブ
653
+ # ルの「name」カラムのインデックスカラムを指定する場合
654
+ # はこうなります。
655
+ #
656
+ # table.index("Users.name")
657
+ #
658
+ # _options_に指定可能な値は以下の通り。
659
+ #
660
+ # [+:name+]
661
+ # インデックスカラムのカラム名を任意に指定する。
662
+ #
663
+ # [+:force+]
664
+ # +true+を指定すると既存の同名のカラムが存在してい
665
+ # ても、強制的に新しいカラムを作成する。
666
+ #
667
+ # [+:path+]
668
+ # カラムを保存するパス。
669
+ #
670
+ # [+:persistent+]
671
+ # +true+を指定すると永続カラムとなる。+:path+を省略
672
+ # した場合は自動的にパスが付加される。
673
+ #
674
+ # [+:with_section+]
675
+ # 転置索引にsection(段落情報)を合わせて格納する。
676
+ #
677
+ # [+:with_weight+]
678
+ # 転置索引にweight情報を合わせて格納する。
679
+ #
680
+ # [+:with_position+]
681
+ # 転置索引に出現位置情報を合わせて格納する。
682
+ def index(target_table_or_target_column_full_name, *args)
683
+ if args.size > 2
684
+ n_args = args.size + 1
685
+ raise ArgumentError, "wrong number of arguments (#{n_args} for 2 or 3)"
686
+ end
687
+ options = nil
688
+ options = args.pop if args.last.is_a?(::Hash)
689
+ if args.empty?
690
+ target_column_full_name = target_table_or_target_column_full_name
691
+ if target_column_full_name.is_a?(Groonga::Column)
692
+ target_column_full_name = target_column_full_name.name
693
+ end
694
+ target_table, target_column = target_column_full_name.split(/\./, 2)
695
+ else
696
+ target_table = target_table_or_target_column_full_name
697
+ target_column = args.pop
698
+ end
699
+ define_index(target_table, target_column, options || {})
700
+ end
701
+
702
+ # 名前が_name_の32bit符号付き整数のカラムを作成する。
703
+ #
704
+ # _options_に指定可能な値は
705
+ # Groonga::Schema::TableDefinition#columnを参照。
706
+ def integer32(name, options={})
707
+ column(name, "Int32", options)
708
+ end
709
+ alias_method :integer, :integer32
710
+ alias_method :int32, :integer32
711
+
712
+ # 名前が_name_の64bit符号付き整数のカラムを作成する。
713
+ #
714
+ # _options_に指定可能な値は
715
+ # Groonga::Schema::TableDefinition#columnを参照。
716
+ def integer64(name, options={})
717
+ column(name, "Int64", options)
718
+ end
719
+ alias_method :int64, :integer64
720
+
721
+ # 名前が_name_の32bit符号なし整数のカラムを作成する。
722
+ #
723
+ # _options_に指定可能な値は
724
+ # Groonga::Schema::TableDefinition#columnを参照。
725
+ def unsigned_integer32(name, options={})
726
+ column(name, "UInt32", options)
727
+ end
728
+ alias_method :unsigned_integer, :unsigned_integer32
729
+ alias_method :uint32, :unsigned_integer32
730
+
731
+ # 名前が_name_の64bit符号なし整数のカラムを作成する。
732
+ #
733
+ # _options_に指定可能な値は
734
+ # Groonga::Schema::TableDefinition#columnを参照。
735
+ def unsigned_integer64(name, options={})
736
+ column(name, "UInt64", options)
737
+ end
738
+ alias_method :uint64, :unsigned_integer64
739
+
740
+ # 名前が_name_のieee754形式の64bit浮動小数点数のカラム
741
+ # を作成する。
742
+ #
743
+ # _options_に指定可能な値は
744
+ # Groonga::Schema::TableDefinition#columnを参照。
745
+ def float(name, options={})
746
+ column(name, "Float", options)
747
+ end
748
+
749
+ # 名前が_name_の64bit符号付き整数で1970年1月1日0時0分
750
+ # 0秒からの経過マイクロ秒数を格納するカラムを作成する。
751
+ #
752
+ # _options_に指定可能な値は
753
+ # Groonga::Schema::TableDefinition#columnを参照。
754
+ def time(name, options={})
755
+ column(name, "Time", options)
756
+ end
757
+
758
+ # 名前が_name_の4Kbyte以下の文字列を格納できるカラムを
759
+ # 作成する。
760
+ #
761
+ # _options_に指定可能な値は
762
+ # Groonga::Schema::TableDefinition#columnを参照。
763
+ def short_text(name, options={})
764
+ column(name, "ShortText", options)
765
+ end
766
+ alias_method :string, :short_text
767
+
768
+ # 名前が_name_の64Kbyte以下の文字列を格納できるカラムを
769
+ # 作成する。
770
+ #
771
+ # _options_に指定可能な値は
772
+ # Groonga::Schema::TableDefinition#columnを参照。
773
+ def text(name, options={})
774
+ column(name, "Text", options)
775
+ end
776
+
777
+ # 名前が_name_の2Gbyte以下の文字列を格納できるカラムを
778
+ # 作成する。
779
+ #
780
+ # _options_に指定可能な値は
781
+ # Groonga::Schema::TableDefinition#columnを参照。
782
+ def long_text(name, options={})
783
+ column(name, "LongText", options)
784
+ end
785
+
786
+ # 名前が_name_で_table_のレコードIDを格納する参照カラ
787
+ # ムを作成する。
788
+ #
789
+ # _options_に指定可能な値は
790
+ # Groonga::Schema::TableDefinition#columnを参照。
791
+ def reference(name, table, options={})
792
+ column(name, table, options)
793
+ end
794
+
795
+ # 名前が_name_の真偽値を格納できるカラムを作成する。
796
+ #
797
+ # _options_に指定可能な値は
798
+ # Groonga::Schema::TableDefinition#columnを参照。
799
+ def boolean(name, options={})
800
+ column(name, "Bool", options)
801
+ end
802
+ alias_method :bool, :boolean
803
+
804
+ def [](name, definition_class=nil) # :nodoc:
805
+ @definitions.find do |definition|
806
+ definition.name.to_s == name.to_s and
807
+ (definition_class.nil? or definition.is_a?(definition_class))
808
+ end
809
+ end
810
+
811
+ def context # :nodoc:
812
+ @options[:context] || Groonga::Context.default
813
+ end
814
+
815
+ private
816
+ def update_definition(name, definition_class, definition) # :nodoc:
817
+ old_definition = self[name, definition_class]
818
+ if old_definition
819
+ index = @definitions.index(old_definition)
820
+ @definitions[index] = definition
821
+ else
822
+ @definitions << definition
823
+ end
824
+ end
825
+
826
+ AVAILABLE_OPTION_KEYS = [:context, :change, :force,
827
+ :type, :path, :persistent,
828
+ :key_type, :value_type, :sub_records,
829
+ :default_tokenizer,
830
+ :key_normalize, :key_with_sis] # :nodoc:
831
+ def validate_options(options) # :nodoc:
832
+ return if options.nil?
833
+ unknown_keys = options.keys - AVAILABLE_OPTION_KEYS
834
+ unless unknown_keys.empty?
835
+ message = "unknown keys are specified: #{unknown_keys.inspect}"
836
+ message << ": available keys: #{AVAILABLE_OPTION_KEYS.inspect}"
837
+ raise ArgumentError, message
838
+ end
839
+ end
840
+
841
+ def table_type # :nodoc:
842
+ type = @options[:type]
843
+ case type
844
+ when :array, nil
845
+ Groonga::Array
846
+ when :hash
847
+ Groonga::Hash
848
+ when :patricia_trie
849
+ Groonga::PatriciaTrie
850
+ else
851
+ raise ArgumentError, "unknown table type: #{type.inspect}"
852
+ end
853
+ end
854
+
855
+ def create_options # :nodoc:
856
+ common = {
857
+ :name => @name,
858
+ :path => @options[:path],
859
+ :persistent => persistent?,
860
+ :value_type => @options[:value_type],
861
+ :context => context,
862
+ :sub_records => @options[:sub_records],
863
+ }
864
+ key_support_table_common = {
865
+ :key_type => Schema.normalize_type(@options[:key_type]),
866
+ :default_tokenizer => @options[:default_tokenizer],
867
+ }
868
+
869
+ if @table_type == Groonga::Array
870
+ common
871
+ elsif @table_type == Groonga::Hash
872
+ common.merge(key_support_table_common)
873
+ elsif @table_type == Groonga::PatriciaTrie
874
+ options = {
875
+ :key_normalize => @options[:key_normalize],
876
+ :key_with_sis => @options[:key_with_sis],
877
+ }
878
+ common.merge(key_support_table_common).merge(options)
879
+ else
880
+ raise ArgumentError, "unknown table type: #{@table_type.inspect}"
881
+ end
882
+ end
883
+
884
+ def column_options # :nodoc:
885
+ {:persistent => persistent?}
886
+ end
887
+
888
+ def persistent? # :nodoc:
889
+ @options[:persistent].nil? ? true : @options[:persistent]
890
+ end
891
+
892
+ def define_index(target_table, target_column, options)
893
+ name = options.delete(:name)
894
+ name ||= "#{target_table}_#{target_column}".gsub(/\./, "_")
895
+
896
+ definition = self[name, IndexColumnDefinition]
897
+ if definition.nil?
898
+ definition = IndexColumnDefinition.new(name, options)
899
+ update_definition(name, IndexColumnDefinition, definition)
900
+ end
901
+ definition.target_table = target_table
902
+ definition.target_column = target_column
903
+ definition.options.merge!(column_options.merge(options))
904
+ self
905
+ end
906
+ end
907
+
908
+ class TableRemoveDefinition # :nodoc:
909
+ def initialize(name, options={})
910
+ @name = name
911
+ @options = options
912
+ end
913
+
914
+ def define
915
+ context = @options[:context] || Groonga::Context.default
916
+ context[@name].remove
917
+ end
918
+ end
919
+
920
+ # スキーマ定義時にGroonga::Schema.create_viewや
921
+ # Groonga::Schema#create_viewからブロックに渡されてくる
922
+ # オブジェクト
923
+ class ViewDefinition
924
+ # ビューの名前
925
+ attr_reader :name
926
+
927
+ def initialize(name, options) # :nodoc:
928
+ @name = name
929
+ @name = @name.to_s if @name.is_a?(Symbol)
930
+ @tables = []
931
+ validate_options(options)
932
+ @options = options
933
+ end
934
+
935
+ def define # :nodoc:
936
+ view = context[@name]
937
+ if @options[:change]
938
+ raise ArgumentError, "view doesn't exist: #{@name}" if view.nil?
939
+ else
940
+ if view and @options[:force]
941
+ view.remove
942
+ view = nil
943
+ end
944
+ view ||= Groonga::View.create(create_options)
945
+ end
946
+ _context = context
947
+ @tables.each do |table|
948
+ _table = table
949
+ table = context[table] unless table.is_a?(Groonga::Table)
950
+ raise ArgumentError, "table doesn't exist: #{_table}" if table.nil?
951
+ view.add_table(table)
952
+ end
953
+ view
954
+ end
955
+
956
+ # 名前が_table_のテーブルをビューに追加する。
957
+ def add(table)
958
+ table = table.to_s if table.is_a?(Symbol)
959
+ @tables << table
960
+ self
961
+ end
962
+
963
+ def context # :nodoc:
964
+ @options[:context] || Groonga::Context.default
965
+ end
966
+
967
+ private
968
+ AVAILABLE_OPTION_KEYS = [:context, :change, :force,
969
+ :path, :persistent] # :nodoc:
970
+ def validate_options(options) # :nodoc:
971
+ return if options.nil?
972
+ unknown_keys = options.keys - AVAILABLE_OPTION_KEYS
973
+ unless unknown_keys.empty?
974
+ message = "unknown keys are specified: #{unknown_keys.inspect}"
975
+ message << ": available keys: #{AVAILABLE_OPTION_KEYS.inspect}"
976
+ raise ArgumentError, message
977
+ end
978
+ end
979
+
980
+ def create_options # :nodoc:
981
+ {
982
+ :name => @name,
983
+ :path => @options[:path],
984
+ :persistent => persistent?,
985
+ :context => context,
986
+ }
987
+ end
988
+
989
+ def persistent? # :nodoc:
990
+ @options[:persistent].nil? ? true : @options[:persistent]
991
+ end
992
+ end
993
+
994
+ class ViewRemoveDefinition # :nodoc:
995
+ def initialize(name, options={})
996
+ @name = name
997
+ @options = options
998
+ end
999
+
1000
+ def define
1001
+ context = @options[:context] || Groonga::Context.default
1002
+ context[@name].remove
1003
+ end
1004
+ end
1005
+
1006
+ class ColumnDefinition # :nodoc:
1007
+ attr_accessor :name, :type
1008
+ attr_reader :options
1009
+
1010
+ def initialize(name, options={})
1011
+ @name = name
1012
+ @name = @name.to_s if @name.is_a?(Symbol)
1013
+ @options = (options || {}).dup
1014
+ @type = nil
1015
+ end
1016
+
1017
+ def define(table_definition, table)
1018
+ column = table.column(@name)
1019
+ if column
1020
+ return column if same_column?(table_definition, column)
1021
+ if @options.delete(:force)
1022
+ column.remove
1023
+ else
1024
+ raise ArgumentError,
1025
+ "the same name column with different type is " +
1026
+ "already defined: #{@type.inspect}(#{@options.inspect}): " +
1027
+ "#{column.inspect}"
1028
+ end
1029
+ end
1030
+ table.define_column(@name,
1031
+ Schema.normalize_type(@type),
1032
+ @options)
1033
+ end
1034
+
1035
+ private
1036
+ def same_column?(table_definition, column)
1037
+ context = table_definition.context
1038
+ # TODO: should check column type and other options.
1039
+ column.range == context[Schema.normalize_type(@type)]
1040
+ end
1041
+ end
1042
+
1043
+ class ColumnRemoveDefinition # :nodoc:
1044
+ attr_accessor :name
1045
+ attr_reader :options
1046
+
1047
+ def initialize(name, options={})
1048
+ @name = name
1049
+ @name = @name.to_s if @name.is_a?(Symbol)
1050
+ @options = (options || {}).dup
1051
+ end
1052
+
1053
+ def define(table_definition, table)
1054
+ table.column(@name).remove
1055
+ end
1056
+ end
1057
+
1058
+ class IndexColumnDefinition # :nodoc:
1059
+ attr_accessor :name, :target_table, :target_column
1060
+ attr_reader :options
1061
+
1062
+ def initialize(name, options={})
1063
+ @name = name
1064
+ @name = @name.to_s if @name.is_a?(Symbol)
1065
+ @options = (options || {}).dup
1066
+ @target_table = nil
1067
+ @target_column = nil
1068
+ end
1069
+
1070
+ def define(table_definition, table)
1071
+ target_name = "#{@target_table}.#{@target_column}"
1072
+ target_table = table_definition.context[@target_table]
1073
+ if target_table.nil? or
1074
+ !(@target_column == "_key" or
1075
+ target_table.have_column?(@target_column))
1076
+ raise ArgumentError, "Unknown index target: <#{target_name}>"
1077
+ end
1078
+ index = table.column(@name)
1079
+ if index
1080
+ return index if same_index?(table_definition, index)
1081
+ if @options.delete(:force)
1082
+ index.remove
1083
+ else
1084
+ raise ArgumentError,
1085
+ "the same name index column with " +
1086
+ "different target or options is " +
1087
+ "already defined: #{target_name.inspect}" +
1088
+ "(#{@options.inspect}): #{index.inspect}"
1089
+ end
1090
+ end
1091
+ index = table.define_index_column(@name,
1092
+ @target_table,
1093
+ @options)
1094
+ index.source = target_table.column(@target_column)
1095
+ index
1096
+ end
1097
+
1098
+ private
1099
+ def same_index?(table_definition, index)
1100
+ context = table_definition.context
1101
+ # TODO: should check column type and other options.
1102
+ return false if index.range.name != @target_table
1103
+ source_names = index.sources.collect do |source|
1104
+ if source.nil?
1105
+ "#{index.range.name}._key"
1106
+ else
1107
+ source.name
1108
+ end
1109
+ end
1110
+ source_names == ["#{@target_table}.#{@target_column}"]
1111
+ end
1112
+ end
1113
+
1114
+ class Dumper # :nodoc:
1115
+ def initialize(options={})
1116
+ @options = (options || {}).dup
1117
+ end
1118
+
1119
+ def dump
1120
+ context = @options[:context] || Groonga::Context.default
1121
+ database = context.database
1122
+ return nil if database.nil?
1123
+
1124
+ reference_columns = []
1125
+ definitions = []
1126
+ database.each do |object|
1127
+ next unless object.is_a?(Groonga::Table)
1128
+ schema = "create_table(#{object.name.inspect}) do |table|\n"
1129
+ object.columns.sort_by {|column| column.local_name}.each do |column|
1130
+ if column.range.is_a?(Groonga::Table)
1131
+ reference_columns << column
1132
+ else
1133
+ type = column_method(column)
1134
+ name = column.local_name
1135
+ schema << " table.#{type}(#{name.inspect})\n"
1136
+ end
1137
+ end
1138
+ schema << "end"
1139
+ definitions << schema
1140
+ end
1141
+
1142
+ reference_columns.group_by do |column|
1143
+ column.table
1144
+ end.each do |table, columns|
1145
+ schema = "change_table(#{table.name.inspect}) do |table|\n"
1146
+ columns.each do |column|
1147
+ name = column.local_name
1148
+ reference = column.range
1149
+ schema << " table.reference(#{name.inspect}, " +
1150
+ "#{reference.name.inspect})\n"
1151
+ end
1152
+ schema << "end"
1153
+ definitions << schema
1154
+ end
1155
+
1156
+ if definitions.empty?
1157
+ ""
1158
+ else
1159
+ definitions.join("\n\n") + "\n"
1160
+ end
1161
+ end
1162
+
1163
+ private
1164
+ def column_method(column)
1165
+ range = column.range
1166
+ case range.name
1167
+ when "Int32"
1168
+ "integer32"
1169
+ when "Int64"
1170
+ "integer64"
1171
+ when "UInt32"
1172
+ "unsigned_integer32"
1173
+ when "UInt64"
1174
+ "unsigned_integer64"
1175
+ when "Float"
1176
+ "float"
1177
+ when "Time"
1178
+ "time"
1179
+ when "ShortText"
1180
+ "short_text"
1181
+ when "Text"
1182
+ "text"
1183
+ when "LongText"
1184
+ "long_text"
1185
+ else
1186
+ raise ArgumentError, "unsupported column: #{column.inspect}"
1187
+ end
1188
+ end
1189
+ end
1190
+ end
1191
+ end