groonga-client-model 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/doc/text/news.md +30 -0
  3. data/groonga-client-model.gemspec +1 -1
  4. data/lib/groonga_client_model/log_subscriber.rb +1 -3
  5. data/lib/groonga_client_model/migration.rb +493 -0
  6. data/lib/groonga_client_model/migrator.rb +240 -0
  7. data/lib/groonga_client_model/railtie.rb +4 -2
  8. data/lib/groonga_client_model/railties/groonga.rake +55 -2
  9. data/lib/groonga_client_model/record.rb +39 -1
  10. data/lib/groonga_client_model/schema.rb +8 -0
  11. data/lib/groonga_client_model/schema_loader.rb +4 -9
  12. data/lib/groonga_client_model/test/groonga_server_runner.rb +19 -3
  13. data/lib/groonga_client_model/version.rb +1 -1
  14. data/lib/rails/generators/groonga_client_model/migration/templates/create_table_migration.rb +14 -0
  15. data/lib/rails/generators/groonga_client_model/migration/templates/delete_config_migration.rb +9 -0
  16. data/lib/rails/generators/groonga_client_model/migration/templates/migration.rb +12 -0
  17. data/lib/rails/generators/groonga_client_model/migration/templates/set_config_migration.rb +10 -0
  18. data/lib/rails/generators/groonga_client_model/migration_generator.rb +125 -0
  19. data/lib/rails/generators/groonga_client_model/{model/model_generator.rb → model_generator.rb} +18 -4
  20. data/test/apps/rails4/Gemfile.lock +2 -2
  21. data/test/apps/rails4/db/groonga/migrate/20170303120517_create_posts.rb +8 -0
  22. data/test/apps/rails4/db/groonga/migrate/20170303120527_create_terms.rb +7 -0
  23. data/test/apps/rails4/db/groonga/migrate/20170303120536_create_ages.rb +6 -0
  24. data/test/apps/rails4/log/development.log +22 -0
  25. data/test/apps/rails4/log/test.log +1350 -0
  26. data/test/apps/rails4/test/generators/migration_generator_test.rb +103 -0
  27. data/test/apps/rails4/test/tmp/db/groonga/migrate/20170307045825_set_config_alias_column.rb +10 -0
  28. data/test/apps/rails5/Gemfile.lock +8 -8
  29. data/test/apps/rails5/db/groonga/migrate/20170301061420_create_posts.rb +8 -0
  30. data/test/apps/rails5/db/groonga/migrate/20170303115054_create_terms.rb +7 -0
  31. data/test/apps/rails5/db/groonga/migrate/20170303115135_create_ages.rb +6 -0
  32. data/test/apps/rails5/log/development.log +350 -0
  33. data/test/apps/rails5/log/test.log +3260 -0
  34. data/test/apps/rails5/test/generators/migration_generator_test.rb +103 -0
  35. data/test/apps/rails5/test/tmp/db/groonga/migrate/20170307081511_remove_title_from_posts.rb +5 -0
  36. data/test/apps/rails5/tmp/cache/assets/sprockets/v3.0/5C/5Cws9GLWcOju_f3tIpY01qSaj7zkLAU0a2bQmpf7sS8.cache +1 -0
  37. data/test/apps/rails5/tmp/cache/assets/sprockets/v3.0/XH/XH9pWZvGgK476BpPGID5z8hjzRmGJjtzV0cHBLXhIKc.cache +1 -0
  38. data/test/unit/fixtures/migrate/20170301061420_create_posts.rb +8 -0
  39. data/test/unit/fixtures/migrate/20170303115054_create_terms.rb +7 -0
  40. data/test/unit/fixtures/migrate/20170303115135_create_ages.rb +6 -0
  41. data/test/unit/migration/test_config.rb +62 -0
  42. data/test/unit/migration/test_copy.rb +117 -0
  43. data/test/unit/migration/test_create_table.rb +528 -0
  44. data/test/unit/migration/test_exist.rb +47 -0
  45. data/test/unit/migration/test_load.rb +83 -0
  46. data/test/unit/record/test_active_model.rb +31 -0
  47. data/test/unit/record/test_readers.rb +45 -0
  48. data/test/unit/record/test_timestamps.rb +76 -0
  49. data/test/unit/record/test_validators.rb +295 -0
  50. data/test/unit/run-test.rb +1 -2
  51. data/test/unit/test_helper.rb +109 -0
  52. data/test/unit/test_load_value_generator.rb +8 -7
  53. data/test/unit/test_migrator.rb +156 -0
  54. metadata +64 -11
  55. data/test/apps/rails4/db/schema.grn +0 -9
  56. data/test/apps/rails5/db/schema.grn +0 -11
  57. data/test/unit/test_record.rb +0 -345
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a4d70ddd3492d74c3be891c28f0801bbc7f8eae
4
- data.tar.gz: 1bdfc60e0062e824047ba5ed71e3b1cdd1276b2a
3
+ metadata.gz: 8bb098148454489683ac35966f22fc18a32b8a3c
4
+ data.tar.gz: 9ab10bad50574e82563929857c3a5d4a3b209767
5
5
  SHA512:
6
- metadata.gz: fd5a08fd22a36b8f38c4e9bd40ba0a1a84e4427c0d07d1da538bbf11a014c2ff25d4bf83f5a0db9e99a2fcce19b360de149d44d2aa1c80f7157c9de57f84f520
7
- data.tar.gz: 70929c9b847839fcfb2779f21096e395403a449009e29f8d047a67826225de095fdc4d472c1fcecef27c31d1a089a76a2c4750b8a82091a003fd71adcddddf8b
6
+ metadata.gz: d5b863f329f13dc589514544397da3a4e10d9ff830fcb2a69bd6a686574fce6589642c2b132f436e95fbf70f84f1f24e65118bd03f2571a9a7a59ef1a232dd3b
7
+ data.tar.gz: 6840fa4300924d4e9566cb8f3958e68590ca9f2eb187bb3174e2a5977c6325cf771b6ec414a24fa82e834bcdce50c1697a05c5a1e450a464c94826aafc6db030
data/doc/text/news.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # NEWS
2
2
 
3
+ ## 1.0.1 - 2016-03-09
4
+
5
+ ### Improvements
6
+
7
+ * Supported migration: The following tasks are available:
8
+
9
+ * `groonga:migrate`: migrates the Groonga database to the latest schema.
10
+
11
+ * `groonga:migrate:rollback`: rollbacks the last migration.
12
+
13
+ * `groonga:migrate:redo`: rollbacks the last migration and
14
+ re-applies the last migration.
15
+
16
+ * Supported auto timestamp columns (`created_at`, `created_on`,
17
+ `updated_at` and `updated_on`) update.
18
+
19
+ * {GroongaClientModel::Record#find}: Supported record object.
20
+
21
+ * Added model generator.
22
+
23
+ * Added migration generator.
24
+
25
+ * {GroongaClientModel::Record}: Supported auto predicate method
26
+ generation for `Bool` type columns.
27
+
28
+ ### Fixes
29
+
30
+ * Fixed a bug that groonga-client-model is the default ORM even when
31
+ Active Record is enabled.
32
+
3
33
  ## 1.0.0 - 2016-02-07
4
34
 
5
35
  ### Fixes
@@ -43,7 +43,7 @@ Gem::Specification.new do |spec|
43
43
  spec.files += Dir.glob("doc/text/*")
44
44
  spec.test_files += Dir.glob("test/**/*")
45
45
 
46
- spec.add_runtime_dependency("groonga-client", ">= 0.4.1")
46
+ spec.add_runtime_dependency("groonga-client", ">= 0.4.2")
47
47
  spec.add_runtime_dependency("groonga-command-parser")
48
48
  spec.add_runtime_dependency("activemodel")
49
49
 
@@ -43,10 +43,8 @@ module GroongaClientModel
43
43
  end
44
44
  end
45
45
 
46
- reset_runtime
47
-
48
46
  def groonga(event)
49
- self.class.runtime += event.duration
47
+ self.class.runtime = (self.class.runtime || 0) + event.duration
50
48
 
51
49
  debug do
52
50
  command = event.payload[:command]
@@ -0,0 +1,493 @@
1
+ # Copyright (C) 2017 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 as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ require "benchmark"
18
+
19
+ module GroongaClientModel
20
+ class MigrationError < Error
21
+ end
22
+
23
+ class IrreversibleMigrationError < MigrationError
24
+ end
25
+
26
+ class IllegalMigrationNameError < MigrationError
27
+ class << self
28
+ def validate(name)
29
+ case name
30
+ when /\A[_a-z0-9]+\z/
31
+ # OK
32
+ else
33
+ raise new(name)
34
+ end
35
+ end
36
+ end
37
+
38
+ attr_reader :name
39
+ def initialize(name)
40
+ @name = name
41
+ message = "Illegal name for migration file: #{name}\n"
42
+ message << "\t(only lower case letters, numbers, and '_' allowed)."
43
+ super(message)
44
+ end
45
+ end
46
+
47
+ class Migration
48
+ attr_accessor :output
49
+ attr_reader :client
50
+
51
+ def initialize(client)
52
+ @client = client
53
+ @output = nil
54
+ @reverting = false
55
+ @pending_actions = []
56
+ end
57
+
58
+ def up
59
+ change
60
+ end
61
+
62
+ def down
63
+ revert do
64
+ change
65
+ end
66
+ end
67
+
68
+ def create_table(name,
69
+ type: nil,
70
+ key_type: nil,
71
+ tokenizer: nil,
72
+ default_tokenizer: nil,
73
+ normalizer: nil,
74
+ propose: nil)
75
+ if @reverting
76
+ @pending_actions << [:remove_table, name]
77
+ return
78
+ end
79
+
80
+ case propose
81
+ when :full_text_search
82
+ type ||= :patricia_trie
83
+ tokenizer ||= :bigram
84
+ normalizer ||= :auto
85
+ end
86
+
87
+ type = normalize_table_type(type || :array)
88
+ if type != "TABLE_NO_KEY" and key_type.nil?
89
+ key_type ||= "ShortText"
90
+ end
91
+ key_type = normalize_type(key_type)
92
+ if type != "TABLE_NO_KEY" and key_type == "ShortText"
93
+ tokenizer ||= default_tokenizer
94
+ tokenizer = normalize_tokenizer(tokenizer)
95
+ normalizer = normalize_normalizer(normalizer)
96
+ end
97
+ options = {type: type}
98
+ options[:key_type] = key_type if key_type
99
+ options[:tokenizer] = tokenizer if tokenizer
100
+ options[:normalizer] = normalizer if normalizer
101
+ report(__method__, [name, options]) do
102
+ @client.request(:table_create).
103
+ parameter(:name, name).
104
+ flags_parameter(:flags, [type]).
105
+ parameter(:key_type, key_type).
106
+ parameter(:default_tokenizer, tokenizer).
107
+ parameter(:normalizer, normalizer).
108
+ response
109
+ end
110
+
111
+ yield(CreateTableMigration.new(self, name)) if block_given?
112
+ end
113
+
114
+ def remove_table(name)
115
+ if @reverting
116
+ raise IrreversibleMigrationError, "can't revert remove_table(#{name})"
117
+ end
118
+
119
+ report(__method__, [name]) do
120
+ @client.request(:table_remove).
121
+ parameter(:name, name).
122
+ response
123
+ end
124
+ end
125
+
126
+ def copy_table(from_table_name, to_table_name)
127
+ if @reverting
128
+ message = "can't revert copy_table(#{from_table_name}, #{to_table_name})"
129
+ raise IrreversibleMigrationError, message
130
+ end
131
+
132
+ report(__method__, [from_table_name, to_table_name]) do
133
+ @client.request(:table_copy).
134
+ parameter(:from_name, from_table_name).
135
+ parameter(:to_name, to_table_name).
136
+ response
137
+ end
138
+ end
139
+
140
+ def add_column(table_name, column_name, value_type,
141
+ flags: nil,
142
+ type: nil,
143
+ sources: nil,
144
+ source: nil)
145
+ if @reverting
146
+ @pending_actions << [:remove_column, table_name, column_name]
147
+ return
148
+ end
149
+
150
+ value_type = normalize_type(value_type)
151
+ type = normalize_column_type(type || :scalar)
152
+ sources ||= source || []
153
+ flags = Array(flags) | [type]
154
+ if type == "COLUMN_INDEX"
155
+ schema = GroongaClientModel::Schema.new
156
+ case schema.tables[table_name].tokenizer
157
+ when nil, "TokenDelimit"
158
+ # do nothing
159
+ else
160
+ flags << "WITH_POSITION"
161
+ end
162
+ if sources.size > 1
163
+ flags << "WITH_SECTION"
164
+ end
165
+ end
166
+ options = {
167
+ flags: flags,
168
+ value_type: value_type,
169
+ }
170
+ options[:sources] = sources unless sources.blank?
171
+ report(__method__, [table_name, column_name, options]) do
172
+ @client.request(:column_create).
173
+ parameter(:table, table_name).
174
+ parameter(:name, column_name).
175
+ flags_parameter(:flags, flags).
176
+ parameter(:type, value_type).
177
+ values_parameter(:source, sources).
178
+ response
179
+ end
180
+ end
181
+
182
+ def remove_column(table_name, column_name)
183
+ if @reverting
184
+ message = "can't revert remove_column(#{table_name}, #{column_name})"
185
+ raise IrreversibleMigrationError, message
186
+ end
187
+
188
+ report(__method__, [table_name, column_name]) do
189
+ @client.request(:column_remove).
190
+ parameter(:table_name, table_name).
191
+ parameter(:name, column_name).
192
+ response
193
+ end
194
+ end
195
+
196
+ def add_timestamp_columns(table_name)
197
+ add_column(table_name, :created_at, :time)
198
+ add_column(table_name, :updated_at, :time)
199
+ end
200
+
201
+ def remove_timestamp_columns(table_name)
202
+ remove_column(table_name, :updated_at)
203
+ remove_column(table_name, :created_at)
204
+ end
205
+
206
+ def copy_column(from_full_column_name,
207
+ to_full_column_name)
208
+ if @reverting
209
+ message = "can't revert copy_column"
210
+ message << "(#{from_full_column_name}, #{to_full_column_name})"
211
+ raise IrreversibleMigrationError, message
212
+ end
213
+
214
+ from_table_name, from_column_name = from_full_column_name.split(".", 2)
215
+ to_table_name, to_column_name = to_full_column_name.split(".", 2)
216
+ report(__method__, [from_full_column_name, to_full_column_name]) do
217
+ @client.request(:column_copy).
218
+ parameter(:from_table, from_table_name).
219
+ parameter(:from_name, from_column_name).
220
+ parameter(:to_table, to_table_name).
221
+ parameter(:to_name, to_column_name).
222
+ response
223
+ end
224
+ end
225
+
226
+ def set_config(key, value)
227
+ if @reverting
228
+ message = "can't revert set_config(#{key.inspect}, #{value.inspect})"
229
+ raise IrreversibleMigrationError, message
230
+ end
231
+
232
+ report(__method__, [key, value]) do
233
+ @client.request(:config_set).
234
+ parameter(:key, key).
235
+ parameter(:value, value).
236
+ response
237
+ end
238
+ end
239
+
240
+ def delete_config(key)
241
+ if @reverting
242
+ message = "can't revert delete_config(#{key.inspect})"
243
+ raise IrreversibleMigrationError, message
244
+ end
245
+
246
+ report(__method__, [key]) do
247
+ @client.request(:config_delete).
248
+ parameter(:key, key).
249
+ response
250
+ end
251
+ end
252
+
253
+ def load(table_name, values, options={})
254
+ if @reverting
255
+ message = "can't revert load(#{table_name.inspect})"
256
+ raise IrreversibleMigrationError, message
257
+ end
258
+
259
+ case values
260
+ when Hash
261
+ json_values = [values].to_json
262
+ when Array
263
+ json_values = values.to_json
264
+ else
265
+ json_values = values
266
+ end
267
+ report(__method__, [table_name]) do
268
+ @client.request(:load).
269
+ parameter(:table, table_name).
270
+ values_parameter(:columns, options[:columns]).
271
+ parameter(:values, json_values).
272
+ response
273
+ end
274
+ end
275
+
276
+ def exist?(name)
277
+ @client.request(:object_exist).
278
+ parameter(:name, name).
279
+ response.
280
+ body
281
+ end
282
+
283
+ def reverting?
284
+ @reverting
285
+ end
286
+
287
+ private
288
+ def puts(*args)
289
+ if @output
290
+ @output.puts(*args)
291
+ else
292
+ super
293
+ end
294
+ end
295
+
296
+ def report(method_name, arguments)
297
+ argument_list = arguments.collect(&:inspect).join(", ")
298
+ puts("-- #{method_name}(#{argument_list})")
299
+ time = Benchmark.measure do
300
+ yield
301
+ end
302
+ puts(" -> %.4fs" % time.real)
303
+ end
304
+
305
+ def revert
306
+ @pending_actions.clear
307
+ @reverting = true
308
+ begin
309
+ yield
310
+ ensure
311
+ @reverting = false
312
+ end
313
+ @pending_actions.reverse_each do |action|
314
+ public_send(*action)
315
+ end
316
+ @pending_actions.clear
317
+ end
318
+
319
+ def normalize_table_type(type)
320
+ case type.to_s
321
+ when "array", /\A(?:TABLE_)?NO_KEY\z/i
322
+ "TABLE_NO_KEY"
323
+ when "hash", "hash_table", /\A(?:TABLE_)?HASH_KEY\z/i
324
+ "TABLE_HASH_KEY"
325
+ when "pat", "patricia_trie", /\A(?:TABLE_)?PAT_KEY\z/i
326
+ "TABLE_PAT_KEY"
327
+ when "dat", "double_array_trie", /\A(?:TABLE_)?DAT_KEY\z/i
328
+ "TABLE_DAT_KEY"
329
+ else
330
+ message = "table type must be one of "
331
+ message << "[:array, :hash_table, :patricia_trie, :double_array_trie]: "
332
+ message << "#{type.inspect}"
333
+ raise ArgumentError, message
334
+ end
335
+ end
336
+
337
+ def normalize_column_type(type)
338
+ case type.to_s
339
+ when "scalar", /\A(?:COLUMN_)?SCALAR\z/i
340
+ "COLUMN_SCALAR"
341
+ when "vector", /\A(?:COLUMN_)?VECTOR\z/i
342
+ "COLUMN_VECTOR"
343
+ when "index", /\A(?:COLUMN_)?INDEX\z/i
344
+ "COLUMN_INDEX"
345
+ else
346
+ message = "table type must be one of "
347
+ message << "[:array, :hash_table, :patricia_trie, :double_array_trie]: "
348
+ message << "#{type.inspect}"
349
+ raise ArgumentError, message
350
+ end
351
+ end
352
+
353
+ def normalize_type(type)
354
+ case type.to_s
355
+ when /\Abool(?:ean)?\z/i
356
+ "Bool"
357
+ when /\Aint(8|16|32|64)\z/i
358
+ "Int#{$1}"
359
+ when /\Auint(8|16|32|64)\z/i
360
+ "UInt#{$1}"
361
+ when /\Afloat\z/i
362
+ "Float"
363
+ when /\Atime\z/i
364
+ "Time"
365
+ when /\Ashort_?text\z/i
366
+ "ShortText"
367
+ when /\Atext\z/i
368
+ "Text"
369
+ when /\Along_?text\z/i
370
+ "LongText"
371
+ when /\Atokyo_?geo_?point\z/i
372
+ "TokyoGeoPoint"
373
+ when /\A(?:wgs84)?_?geo_?point\z/i
374
+ "WGS84GeoPoint"
375
+ else
376
+ type
377
+ end
378
+ end
379
+
380
+ def normalize_tokenizer(tokenizer)
381
+ case tokenizer.to_s
382
+ when /\A(?:token_?)?bigram\z/i
383
+ "TokenBigram"
384
+ when /\A(?:token_?)?delimit\z/i
385
+ "TokenDelimit"
386
+ when /\A(?:token_?)?mecab\z/i
387
+ "TokenMecab"
388
+ else
389
+ tokenizer
390
+ end
391
+ end
392
+
393
+ def normalize_normalizer(normalizer)
394
+ case normalizer.to_s
395
+ when /\A(?:normalizer_?)?auto\z/i
396
+ "NormalizerAuto"
397
+ else
398
+ normalizer
399
+ end
400
+ end
401
+
402
+ class CreateTableMigration
403
+ def initialize(migration, table_name)
404
+ @migration = migration
405
+ @table_name = table_name
406
+ end
407
+
408
+ def boolean(column_name, options={})
409
+ @migration.add_column(@table_name, column_name, :bool, options)
410
+ end
411
+ alias_method :bool, :boolean
412
+
413
+ def integer(column_name, options={})
414
+ options = options.dup
415
+ bit = options.delete(:bit) || 32
416
+ unsigned = options.delete(:unsigned)
417
+ if unsigned
418
+ type = "uint#{bit}"
419
+ else
420
+ type = "int#{bit}"
421
+ end
422
+ @migration.add_column(@table_name, column_name, type, options)
423
+ end
424
+
425
+ def float(column_name, options={})
426
+ @migration.add_column(@table_name, column_name, :float, options)
427
+ end
428
+
429
+ def time(column_name, options={})
430
+ @migration.add_column(@table_name, column_name, :time, options)
431
+ end
432
+
433
+ def short_text(column_name, options={})
434
+ @migration.add_column(@table_name, column_name, :short_text, options)
435
+ end
436
+
437
+ def text(column_name, options={})
438
+ @migration.add_column(@table_name, column_name, :text, options)
439
+ end
440
+
441
+ def long_text(column_name, options={})
442
+ @migration.add_column(@table_name, column_name, :long_text, options)
443
+ end
444
+
445
+ def geo_point(column_name, options={})
446
+ options = options.dup
447
+ datum = options.delete(:datum) || :wgs84
448
+ case datum
449
+ when :wgs84
450
+ type = "WGS84GeoPoint"
451
+ when :tokyo
452
+ type = "TokyoGeoPoint"
453
+ else
454
+ message = "invalid geodetic datum: "
455
+ message << "available: [:wgs84, :tokyo]: #{datum.inspect}"
456
+ raise ArgumentError, message
457
+ end
458
+ @migration.add_column(@table_name, column_name, type, options)
459
+ end
460
+
461
+ def wgs84_geo_point(column_name, options={})
462
+ geo_point(column_name, options.merge(datum: :wgs84))
463
+ end
464
+
465
+ def tokyo_geo_point(column_name, options={})
466
+ geo_point(column_name, options.merge(datum: :tokyo))
467
+ end
468
+
469
+ def reference(column_name, reference_table_name, options={})
470
+ @migration.add_column(@table_name,
471
+ column_name,
472
+ reference_table_name,
473
+ options)
474
+ end
475
+
476
+ def timestamps
477
+ @migration.add_timestamp_columns(@table_name)
478
+ end
479
+
480
+ def index(source_table_name, source_column_names, options={})
481
+ options = options.dup
482
+ source_column_names = Array(source_column_names)
483
+ column_name = options.delete(:name)
484
+ column_name ||= [source_table_name, *source_column_names].join("_")
485
+ @migration.add_column(@table_name,
486
+ column_name,
487
+ source_table_name,
488
+ options.merge(:type => :index,
489
+ :sources => source_column_names))
490
+ end
491
+ end
492
+ end
493
+ end