activegroonga 0.0.2 → 0.0.6

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 (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