activegroonga 0.0.2 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/NEWS.ja.rdoc +4 -0
  2. data/NEWS.rdoc +4 -0
  3. data/README.ja.rdoc +1 -1
  4. data/README.rdoc +1 -1
  5. data/Rakefile +3 -4
  6. data/lib/active_groonga.rb +2 -0
  7. data/lib/active_groonga/base.rb +125 -99
  8. data/lib/active_groonga/column.rb +9 -0
  9. data/lib/active_groonga/dynamic_record_expression_builder.rb +40 -0
  10. data/lib/active_groonga/schema.rb +107 -204
  11. data/lib/active_groonga/schema_dumper.rb +40 -25
  12. data/lib/active_groonga/tasks/groonga.rake +2 -0
  13. data/lib/active_groonga/version.rb +1 -1
  14. data/rails_generators/index_table_groonga/USAGE +23 -0
  15. data/rails_generators/index_table_groonga/index_table_groonga_generator.rb +44 -0
  16. data/rails_generators/index_table_groonga/templates/migration.rb +12 -0
  17. data/rails_generators/migration_groonga/USAGE +29 -0
  18. data/rails_generators/migration_groonga/migration_groonga_generator.rb +19 -0
  19. data/rails_generators/migration_groonga/templates/migration.rb +11 -0
  20. data/test-unit/Rakefile +6 -1
  21. data/test-unit/lib/test/unit/autorunner.rb +26 -3
  22. data/test-unit/lib/test/unit/priority.rb +21 -1
  23. data/test-unit/lib/test/unit/testcase.rb +101 -36
  24. data/test-unit/lib/test/unit/ui/console/testrunner.rb +7 -4
  25. data/test-unit/test/{test_testcase.rb → test-testcase.rb} +30 -1
  26. data/test-unit/test/test_assertions.rb +1 -1
  27. data/test/active-groonga-test-utils.rb +25 -26
  28. data/test/test-base.rb +16 -6
  29. data/test/test-schema-dumper.rb +48 -0
  30. data/test/test-schema.rb +20 -4
  31. data/test/tmp/database/database.groonga +0 -0
  32. data/test/tmp/database/database.groonga.0000000 +0 -0
  33. data/test/tmp/database/database.groonga.0000100 +0 -0
  34. data/test/tmp/database/database.groonga.0000101 +0 -0
  35. metadata +19 -7
@@ -60,6 +60,14 @@ module ActiveGroonga
60
60
  end
61
61
  end
62
62
 
63
+ def index?
64
+ @type == :index
65
+ end
66
+
67
+ def index_sources
68
+ @column.sources
69
+ end
70
+
63
71
  def reference_type?
64
72
  @type == :references
65
73
  end
@@ -77,6 +85,7 @@ module ActiveGroonga
77
85
 
78
86
  private
79
87
  def detect_type
88
+ return :index if @column.is_a?(Groonga::IndexColumn)
80
89
  case @column.range
81
90
  when Groonga::Type
82
91
  case @column.range.id
@@ -0,0 +1,40 @@
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
+ module ActiveGroonga
17
+ class DynamicRecordExpressionBuilder
18
+ # Instantiates a new dynamic record expression builder.
19
+ #
20
+ # +record+ is a Groonga::RecordExpressionBuilder.
21
+ def initialize(record)
22
+ @record = record
23
+ define_column_readers
24
+ end
25
+
26
+ def [](name)
27
+ @record[name]
28
+ end
29
+
30
+ private
31
+ def define_column_readers
32
+ singleton_class = class << self; self; end
33
+ @record.table.columns.each do |column|
34
+ singleton_class.send(:define_method, column.local_name) do ||
35
+ self[column.local_name]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -54,155 +54,54 @@ module ActiveGroonga
54
54
  end
55
55
 
56
56
  def initialize_schema_management_tables
57
- initialize_index_management_table
58
- initialize_index_table
59
57
  initialize_migrations_table
60
58
  end
61
59
 
62
- def create_table(name, options={})
63
- table_definition = TableDefinition.new(name)
64
- yield(table_definition)
65
- table_definition.create
60
+ def create_table(name, options={}, &block)
61
+ table_file = File.join(Base.tables_directory, "#{name}.groonga")
62
+ table_name = Base.groonga_table_name(name)
63
+ options = {:path => table_file}.merge(options)
64
+ options = default_table_options(options).merge(options)
65
+ options = options.merge(:context => Base.context)
66
+ Groonga::Schema.create_table(table_name, options) do |table|
67
+ block.call(TableDefinitionWrapper.new(table))
68
+ end
66
69
  end
67
70
 
68
- def drop_table(name, options={})
69
- table = Base.context[Base.groonga_table_name(name)]
70
- table_id = table.id
71
- table.remove
72
- index_management_table.open_cursor do |cursor|
73
- while cursor.next
74
- cursor.delete if cursor.table_id == table_id
75
- end
76
- end
71
+ def remove_table(name, options={})
72
+ options = options.merge(:context => Base.context)
73
+ Groonga::Schema.remove_table(name, options)
77
74
  end
75
+ alias_method :drop_table, :remove_table
78
76
 
79
77
  def add_column(table_name, column_name, type, options={})
80
- column = ColumnDefinition.new(table_name, column_name)
81
- if type.to_s == "references"
82
- table = options.delete(:to) || column_name.pluralize
83
- column.type = Base.groonga_table_name(table)
84
- else
85
- column.type = type
78
+ options_with_context = options.merge(:context => Base.context)
79
+ Groonga::Schema.change_table(table_name, options_with_context) do |table|
80
+ table = TableDefinitionWrapper.new(table)
81
+ table.column(column_name, type, options)
86
82
  end
87
- column.create(options)
88
83
  end
89
84
 
90
85
  def remove_column(table_name, *column_names)
91
- column_names.each do |column_name|
92
- ColumnDefinition.new(table_name, column_name).remove
86
+ options_with_context = options.merge(:context => Base.context)
87
+ Groonga::Schema.change_table(table_name, options_with_context) do |table|
88
+ column_names.each do |column_name|
89
+ table.remove_column(column_name)
90
+ end
93
91
  end
94
92
  end
95
93
 
96
- def add_index(table_name, column_name, options={})
97
- groonga_table_name = Base.groonga_table_name(table_name)
98
- table = Base.context[groonga_table_name]
99
- column_name = column_name.to_s
100
- column = table.column(column_name)
101
-
102
- base_dir = File.join(Base.metadata_directory,
103
- index_table_name,
104
- table_name)
105
- FileUtils.mkdir_p(base_dir)
106
-
107
- name = "#{table_name}/#{column_name}"
108
- path = File.join(base_dir, "#{column_name}.groonga")
109
- index_column = index_table.define_index_column(name, table,
110
- :path => path,
111
- :compress => "zlib",
112
- # :with_section => true,
113
- # :with_weight => true,
114
- :with_position => true)
115
- index_column.source = column
116
-
117
- record = index_management_table.add
118
- record["table"] = groonga_table_name
119
- record["column"] = column_name
120
- record["index"] = name
121
- end
122
-
123
- def index_management_table
124
- Base.context[groonga_index_management_table_name]
125
- end
126
-
127
- def index_table
128
- Base.context[groonga_index_table_name]
129
- end
130
-
131
- def indexes(table_or_table_name)
132
- if table_or_table_name.is_a?(String)
133
- table_name = table_or_table_name
134
- table = Base.context[Base.groonga_table_name(table_name)]
135
- else
136
- table = table_or_table_name
137
- table_name = table.name.gsub(/(?:\A<table:|>\z)/, '')
138
- end
139
- indexes = []
140
- index_management_table.records.each do |record|
141
- next if record["table"] != table.name
142
- indexes << IndexDefinition.new(table_name, record["index"],
143
- false, record["column"])
94
+ def add_index_column(table_name, target_table_name, target_column_name,
95
+ options={})
96
+ options_for_table = options.reject {|key, value| key == :name}
97
+ options_for_table = options_for_table.merge(:context => Base.context)
98
+ Groonga::Schema.change_table(table_name, options_for_table) do |table|
99
+ table = TableDefinitionWrapper.new(table)
100
+ table.index(target_table_name, target_column_name, options)
144
101
  end
145
- indexes
146
102
  end
147
103
 
148
104
  private
149
- def index_management_table_name
150
- Base.table_name_prefix + 'indexes' + Base.table_name_suffix
151
- end
152
-
153
- def groonga_index_management_table_name
154
- Base.groonga_metadata_table_name(index_management_table_name)
155
- end
156
-
157
- def index_table_name
158
- Base.table_name_prefix + 'index' + Base.table_name_suffix
159
- end
160
-
161
- def groonga_index_table_name
162
- Base.groonga_metadata_table_name(index_table_name)
163
- end
164
-
165
- def initialize_index_management_table
166
- table_name = index_management_table_name
167
- groonga_table_name = groonga_index_management_table_name
168
- if Base.context[groonga_table_name].nil?
169
- table_file = File.join(Base.metadata_directory,
170
- "#{table_name}.groonga")
171
- table = Groonga::Array.create(:name => groonga_table_name,
172
- :path => table_file)
173
-
174
- base_dir = File.join(Base.metadata_directory, table_name)
175
- FileUtils.mkdir_p(base_dir)
176
-
177
- column_file = File.join(base_dir, "table.groonga")
178
- table.define_column("table", "<shorttext>", :path => column_file)
179
-
180
- column_file = File.join(base_dir, "column.groonga")
181
- table.define_column("column", "<shorttext>", :path => column_file)
182
-
183
- column_file = File.join(base_dir, "index.groonga")
184
- table.define_column("index", "<shorttext>", :path => column_file)
185
- end
186
- end
187
-
188
- def initialize_index_table
189
- table_name = index_table_name
190
- groonga_table_name = groonga_index_table_name
191
- if Base.context[groonga_table_name].nil?
192
- table_file = File.join(Base.metadata_directory,
193
- "#{table_name}.groonga")
194
- table = Groonga::PatriciaTrie.create(:name => groonga_table_name,
195
- :key_type => "<shorttext>",
196
- # :key_with_sis => true,
197
- # :key_normalize => true,
198
- :path => table_file)
199
- table.default_tokenizer = "<token:bigram>"
200
-
201
- base_dir = File.join(Base.metadata_directory, table_name)
202
- FileUtils.mkdir_p(base_dir)
203
- end
204
- end
205
-
206
105
  def initialize_migrations_table
207
106
  table_name = Migrator.schema_migrations_table_name
208
107
  groonga_table_name = Migrator.groonga_schema_migrations_table_name
@@ -211,104 +110,108 @@ module ActiveGroonga
211
110
  "#{table_name}.groonga")
212
111
  Groonga::Hash.create(:name => groonga_table_name,
213
112
  :path => table_file,
214
- :key_type => "<shorttext>")
113
+ :key_type => "ShortText")
215
114
  end
216
115
  end
116
+
117
+ def default_table_options(options)
118
+ default_options = {:sub_records => true}
119
+ case options[:type]
120
+ when :hash, :patricia_trie
121
+ default_options[:default_tokenizer] = "TokenBigram"
122
+ end
123
+ default_options
124
+ end
217
125
  end
218
126
 
219
- class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
220
- undef_method :primary_key, :to_sql, :native
127
+ class TableDefinitionWrapper
128
+ def initialize(definition)
129
+ @definition = definition
130
+ end
131
+
132
+ def column(name, type, options={})
133
+ column_file = File.join(Base.columns_directory(@definition.name),
134
+ "#{name}.groonga")
135
+ options = {:path => column_file}.merge(options)
136
+ @definition.column(name, type, options)
137
+ end
138
+
139
+ def remove_column(name, options={})
140
+ @definition.remove_column(name, options)
141
+ end
142
+ alias_method :remove_index, :remove_column
143
+
144
+ def index(target_table_name, target_column_name, options={})
145
+ column_name = options.delete(:name)
146
+ column_name ||= [target_table_name, target_column_name].join("_")
147
+ column_dir = Base.index_columns_directory(@definition.name,
148
+ target_table_name.to_s)
149
+ column_file = File.join(column_dir, "#{column_name}.groonga")
150
+ options = {
151
+ :with_position => true,
152
+ :path => column_file,
153
+ :name => column_name,
154
+ }.merge(options)
155
+ target_table = @definition.context[target_table_name]
156
+ target_column = target_table.column(target_column_name)
157
+ @definition.index(target_column, options)
158
+ end
159
+
160
+ def timestamps(*args)
161
+ options = args.extract_options!
162
+ column(:created_at, :datetime, options)
163
+ column(:updated_at, :datetime, options)
164
+ end
221
165
 
222
- def initialize(name)
223
- super(nil)
224
- @name = name
225
- @indexes = []
166
+ def string(*args)
167
+ columns("ShortText", *args)
226
168
  end
227
169
 
228
- def create
229
- table_file = File.join(Base.tables_directory, "#{@name}.groonga")
230
- Groonga::Array.create(:name => Base.groonga_table_name(@name),
231
- :path => table_file)
232
- @columns.each(&:create)
233
- @indexes.each do |column_name, options|
234
- Schema.add_index(@name.to_s, column_name, options)
235
- end
170
+ def text(*args)
171
+ columns("Text", *args)
236
172
  end
237
173
 
238
- def column(name, type, options={})
239
- column = self[name] || ColumnDefinition.new(@name, name)
240
- column.type = type
241
- @columns << column unless @columns.include?(column)
242
- self
174
+ def integer(*args)
175
+ columns("Int32", *args)
243
176
  end
244
177
 
245
- def index(column_name, options={})
246
- @indexes << [column_name.to_s, options]
178
+ def float(*args)
179
+ columns("Float", *args)
247
180
  end
248
181
 
249
- def references(*args)
250
- options = args.extract_options!
251
- args.each do |col|
252
- groonga_table_name = Base.groonga_table_name(col.to_s.pluralize)
253
- table = Base.context[groonga_table_name]
254
- column(col, table, options)
255
- end
182
+ def decimal(*args)
183
+ columns("Int64", *args)
256
184
  end
257
- alias :belongs_to :references
258
- end
259
185
 
260
- class ColumnDefinition
261
- attr_accessor :name, :type
186
+ def time(*args)
187
+ columns("Time", *args)
188
+ end
189
+ alias_method :datetime, :time
190
+ alias_method :timestamp, :time
262
191
 
263
- def initialize(table_name, name)
264
- @table_name = table_name
265
- @name = name
266
- @name = @name.to_s if @name.is_a?(Symbol)
267
- @type = nil
192
+ def binary(*args)
193
+ columns("LongText", *args)
268
194
  end
269
195
 
270
- def create(options={})
271
- column_file = File.join(Base.columns_directory(@table_name),
272
- "#{@name}.groonga")
273
- options = options.merge(:path => column_file)
274
- table = Base.context[Base.groonga_table_name(@table_name)]
275
- table.define_column(@name,
276
- normalize_type(@type),
277
- options)
196
+ def boolean(*args)
197
+ columns("Bool", *args)
278
198
  end
279
199
 
280
- def remove
281
- Base.context[@name].remove
200
+ def reference(name, table=nil, options={})
201
+ table = Base.groonga_table_name(table || name.to_s.pluralize)
202
+ column(name, table, options)
282
203
  end
204
+ alias_method :references, :reference
205
+ alias_method :belongs_to, :references
283
206
 
284
- def normalize_type(type)
285
- return type if type.is_a?(Groonga::Object)
286
- case type.to_s
287
- when "string"
288
- "<shorttext>"
289
- when "text"
290
- "<text>"
291
- when "integer"
292
- "<int>"
293
- when "float"
294
- "<float>"
295
- when "decimal"
296
- "<int64>"
297
- when "datetime", "timestamp", "time", "date"
298
- "<time>"
299
- when "binary"
300
- "<longtext>"
301
- when "boolean"
302
- "<int>"
303
- else
304
- type
207
+ private
208
+ def columns(type, *args)
209
+ options = args.extract_options!
210
+ column_names = args
211
+ column_names.each do |name|
212
+ column(name, type, options)
305
213
  end
306
214
  end
307
215
  end
308
-
309
- class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
310
- alias_method :column, :columns
311
- alias_method :column=, :columns=
312
- end
313
216
  end
314
217
  end
@@ -28,9 +28,11 @@ module ActiveGroonga
28
28
 
29
29
  def dump(stream)
30
30
  @references = []
31
+ @indexes = []
31
32
  header(stream)
32
33
  dump_tables(stream)
33
34
  dump_references(stream)
35
+ dump_indexes(stream)
34
36
  trailer(stream)
35
37
  stream
36
38
  end
@@ -52,13 +54,34 @@ module ActiveGroonga
52
54
  def dump_table(name, stream)
53
55
  begin
54
56
  table_schema = StringIO.new
55
- table_schema.puts " create_table #{name.inspect}, :force => true do |t|"
57
+
58
+ table_name = Base.groonga_table_name(name)
59
+ table = Base.context[table_name]
60
+ options = [":force => true"]
61
+ case table
62
+ when Groonga::Hash
63
+ options << ":type => :hash"
64
+ when Groonga::PatriciaTrie
65
+ options << ":type => :patricia_trie"
66
+ end
67
+ if table.domain
68
+ options << ":key_type => #{table.domain.name.inspect}"
69
+ end
70
+ if table.respond_to?(:default_tokenizer) and table.default_tokenizer
71
+ options << ":default_tokenizer => #{table.default_tokenizer.name.inspect}"
72
+ end
73
+
74
+ table_schema.puts " create_table #{name.inspect}, #{options.join(', ')} do |t|"
56
75
  column_specs = []
57
76
  columns(name).each do |column|
58
77
  if column.reference_type?
59
78
  @references << [name, column]
60
79
  next
61
80
  end
81
+ if column.index?
82
+ @indexes << [name, column]
83
+ next
84
+ end
62
85
 
63
86
  spec = {}
64
87
  spec[:type] = column.type.to_s
@@ -74,8 +97,6 @@ module ActiveGroonga
74
97
  table_schema.puts " end"
75
98
  table_schema.puts
76
99
 
77
- dump_indexes(name, table_schema)
78
-
79
100
  stream.print table_schema.string
80
101
  rescue => e
81
102
  stream.puts "# Could not dump table #{name.inspect} because of following #{e.class}"
@@ -89,42 +110,36 @@ module ActiveGroonga
89
110
  stream
90
111
  end
91
112
 
92
- def dump_indexes(table, stream)
93
- _indexes = indexes(table)
94
- return if _indexes.empty?
95
-
96
- add_index_statements = _indexes.collect do |index|
97
- statement_parts = []
98
- statement_parts << "add_index #{index.table.inspect}"
99
- statement_parts << index.columns.inspect
100
- statement_parts << ":name => #{index.name.inspect}"
101
- ' ' + statement_parts.join(', ')
102
- end
103
-
104
- stream.puts add_index_statements.sort.join("\n")
105
- stream.puts
106
- end
107
-
108
113
  def dump_references(stream)
109
114
  @references.sort_by do |name, column|
110
115
  [name, column.name]
111
116
  end.each do |name, column|
112
117
  statement = " add_column #{name.inspect}, "
113
- statement << "#{column.name.inspect}, :references, "
114
- statement << ":to => #{column.reference_object_name.inspect}"
118
+ statement << "#{column.name.inspect}, "
119
+ statement << "#{column.reference_object_name.inspect}"
115
120
  stream.puts(statement)
116
121
  end
117
122
  end
118
123
 
124
+ def dump_indexes(stream)
125
+ @indexes.sort_by do |name, column|
126
+ [name, column.name]
127
+ end.each do |name, column|
128
+ column.index_sources.each do |source|
129
+ statement = " add_index_column #{name.inspect}, "
130
+ statement << "#{source.table.name.inspect}, "
131
+ statement << "#{source.local_name.inspect}, "
132
+ statement << ":name => #{column.name.inspect}"
133
+ stream.puts(statement)
134
+ end
135
+ end
136
+ end
137
+
119
138
  def columns(table_name)
120
139
  table_name = Base.groonga_table_name(table_name)
121
140
  Base.context[table_name].columns.collect {|column| Column.new(column)}
122
141
  end
123
142
 
124
- def indexes(table_name)
125
- Schema.indexes(table_name)
126
- end
127
-
128
143
  class StreamWrapper
129
144
  def initialize(stream)
130
145
  @stream = stream