rroonga 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/NEWS.ja.rdoc +46 -0
  2. data/NEWS.rdoc +46 -0
  3. data/README.ja.rdoc +2 -2
  4. data/README.rdoc +2 -3
  5. data/Rakefile +148 -11
  6. data/example/bookmark.rb +94 -91
  7. data/ext/groonga/extconf.rb +25 -25
  8. data/ext/groonga/groonga.def +2 -0
  9. data/ext/groonga/mkmf.log +7 -7
  10. data/ext/groonga/rb-grn-column.c +66 -0
  11. data/ext/groonga/rb-grn-context.c +92 -5
  12. data/ext/groonga/rb-grn-database.c +24 -1
  13. data/ext/groonga/rb-grn-exception.c +15 -0
  14. data/ext/groonga/rb-grn-hash.c +2 -2
  15. data/ext/groonga/rb-grn-index-column.c +8 -4
  16. data/ext/groonga/rb-grn-object.c +4 -2
  17. data/ext/groonga/rb-grn-patricia-trie.c +2 -2
  18. data/ext/groonga/rb-grn-snippet.c +1 -0
  19. data/ext/groonga/rb-grn-table-key-support.c +22 -19
  20. data/ext/groonga/rb-grn-table.c +63 -5
  21. data/ext/groonga/rb-grn.h +1 -1
  22. data/ext/groonga/rb-groonga.c +1 -0
  23. data/extconf.rb +1 -1
  24. data/html/developer.html +8 -2
  25. data/html/favicon.ico +0 -0
  26. data/html/favicon.svg +591 -0
  27. data/html/index.html +71 -8
  28. data/html/logo.svg +612 -0
  29. data/html/ranguba.css +92 -7
  30. data/html/readme.svg +256 -0
  31. data/lib/groonga/expression-builder.rb +8 -4
  32. data/lib/groonga/record.rb +77 -7
  33. data/lib/groonga/schema.rb +429 -100
  34. data/rroonga-build.rb +1 -1
  35. data/test/run-test.rb +1 -1
  36. data/test/test-array.rb +4 -0
  37. data/test/test-context.rb +8 -0
  38. data/test/test-database.rb +8 -1
  39. data/test/test-expression-builder.rb +14 -0
  40. data/test/test-fix-size-column.rb +4 -0
  41. data/test/test-hash.rb +10 -4
  42. data/test/test-index-column.rb +11 -0
  43. data/test/test-patricia-trie.rb +7 -1
  44. data/test/test-record.rb +14 -0
  45. data/test/test-remote.rb +3 -2
  46. data/test/test-schema-create-table.rb +32 -2
  47. data/test/test-schema.rb +108 -11
  48. data/test/test-table-select-normalize.rb +7 -3
  49. data/test/test-table-select.rb +12 -0
  50. data/test/test-table.rb +7 -0
  51. data/test/test-variable-size-column.rb +4 -0
  52. data/test-unit/Rakefile +40 -0
  53. data/test-unit/TODO +5 -0
  54. data/test-unit/bin/testrb +5 -0
  55. data/test-unit/html/classic.html +15 -0
  56. data/test-unit/html/index.html +25 -0
  57. data/test-unit/html/index.html.ja +27 -0
  58. data/test-unit/lib/test/unit/assertionfailederror.rb +25 -0
  59. data/test-unit/lib/test/unit/assertions.rb +1230 -0
  60. data/test-unit/lib/test/unit/attribute.rb +125 -0
  61. data/test-unit/lib/test/unit/autorunner.rb +360 -0
  62. data/test-unit/lib/test/unit/collector/descendant.rb +23 -0
  63. data/test-unit/lib/test/unit/collector/dir.rb +108 -0
  64. data/test-unit/lib/test/unit/collector/load.rb +144 -0
  65. data/test-unit/lib/test/unit/collector/objectspace.rb +34 -0
  66. data/test-unit/lib/test/unit/collector.rb +36 -0
  67. data/test-unit/lib/test/unit/color-scheme.rb +102 -0
  68. data/test-unit/lib/test/unit/color.rb +96 -0
  69. data/test-unit/lib/test/unit/diff.rb +724 -0
  70. data/test-unit/lib/test/unit/error.rb +130 -0
  71. data/test-unit/lib/test/unit/exceptionhandler.rb +39 -0
  72. data/test-unit/lib/test/unit/failure.rb +136 -0
  73. data/test-unit/lib/test/unit/fixture.rb +176 -0
  74. data/test-unit/lib/test/unit/notification.rb +129 -0
  75. data/test-unit/lib/test/unit/omission.rb +191 -0
  76. data/test-unit/lib/test/unit/pending.rb +150 -0
  77. data/test-unit/lib/test/unit/priority.rb +180 -0
  78. data/test-unit/lib/test/unit/runner/console.rb +52 -0
  79. data/test-unit/lib/test/unit/runner/emacs.rb +8 -0
  80. data/test-unit/lib/test/unit/runner/tap.rb +8 -0
  81. data/test-unit/lib/test/unit/testcase.rb +476 -0
  82. data/test-unit/lib/test/unit/testresult.rb +89 -0
  83. data/test-unit/lib/test/unit/testsuite.rb +110 -0
  84. data/test-unit/lib/test/unit/ui/console/outputlevel.rb +14 -0
  85. data/test-unit/lib/test/unit/ui/console/testrunner.rb +466 -0
  86. data/test-unit/lib/test/unit/ui/emacs/testrunner.rb +63 -0
  87. data/test-unit/lib/test/unit/ui/tap/testrunner.rb +92 -0
  88. data/test-unit/lib/test/unit/ui/testrunner.rb +28 -0
  89. data/test-unit/lib/test/unit/ui/testrunnermediator.rb +77 -0
  90. data/test-unit/lib/test/unit/ui/testrunnerutilities.rb +41 -0
  91. data/test-unit/lib/test/unit/util/backtracefilter.rb +41 -0
  92. data/test-unit/lib/test/unit/util/method-owner-finder.rb +28 -0
  93. data/test-unit/lib/test/unit/util/observable.rb +90 -0
  94. data/test-unit/lib/test/unit/util/procwrapper.rb +48 -0
  95. data/test-unit/lib/test/unit/version.rb +7 -0
  96. data/test-unit/lib/test/unit.rb +323 -0
  97. data/test-unit/sample/adder.rb +13 -0
  98. data/test-unit/sample/subtracter.rb +12 -0
  99. data/test-unit/sample/test_adder.rb +20 -0
  100. data/test-unit/sample/test_subtracter.rb +20 -0
  101. data/test-unit/sample/test_user.rb +23 -0
  102. data/test-unit/test/collector/test-descendant.rb +133 -0
  103. data/test-unit/test/collector/test-load.rb +442 -0
  104. data/test-unit/test/collector/test_dir.rb +406 -0
  105. data/test-unit/test/collector/test_objectspace.rb +100 -0
  106. data/test-unit/test/run-test.rb +15 -0
  107. data/test-unit/test/test-attribute.rb +86 -0
  108. data/test-unit/test/test-color-scheme.rb +67 -0
  109. data/test-unit/test/test-color.rb +47 -0
  110. data/test-unit/test/test-diff.rb +518 -0
  111. data/test-unit/test/test-emacs-runner.rb +60 -0
  112. data/test-unit/test/test-fixture.rb +287 -0
  113. data/test-unit/test/test-notification.rb +33 -0
  114. data/test-unit/test/test-omission.rb +81 -0
  115. data/test-unit/test/test-pending.rb +70 -0
  116. data/test-unit/test/test-priority.rb +119 -0
  117. data/test-unit/test/test-testcase.rb +544 -0
  118. data/test-unit/test/test_assertions.rb +1151 -0
  119. data/test-unit/test/test_error.rb +26 -0
  120. data/test-unit/test/test_failure.rb +33 -0
  121. data/test-unit/test/test_testresult.rb +113 -0
  122. data/test-unit/test/test_testsuite.rb +129 -0
  123. data/test-unit/test/testunit-test-util.rb +14 -0
  124. data/test-unit/test/ui/test_testrunmediator.rb +20 -0
  125. data/test-unit/test/util/test-method-owner-finder.rb +38 -0
  126. data/test-unit/test/util/test_backtracefilter.rb +41 -0
  127. data/test-unit/test/util/test_observable.rb +102 -0
  128. data/test-unit/test/util/test_procwrapper.rb +36 -0
  129. data/text/{TUTORIAL.ja.rdoc → tutorial.ja.rdoc} +165 -126
  130. metadata +106 -16
  131. data/html/favicon.xcf +0 -0
  132. data/html/logo.xcf +0 -0
  133. data/license/GPL +0 -340
  134. data/license/RUBY +0 -59
  135. data/pkg-config.rb +0 -333
@@ -15,6 +15,7 @@
15
15
  # License along with this library; if not, write to the Free Software
16
16
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
 
18
+ require 'fileutils'
18
19
 
19
20
  module Groonga
20
21
 
@@ -80,12 +81,22 @@ module Groonga
80
81
  end
81
82
  end
82
83
 
84
+ # 未知のインデックス対象テーブルを指定したときに発生する。
85
+ class UnknownIndexTargetTable < Error
86
+ attr_reader :table
87
+ def initialize(table)
88
+ @table = table
89
+ super("unknown index target table: <#{@table.inspect}>")
90
+ end
91
+ end
92
+
83
93
  # 未知のインデックス対象を指定したときに発生する。
84
94
  class UnknownIndexTarget < Error
85
- attr_reader :target_name
86
- def initialize(target_name)
87
- @target_name = target_name
88
- super("unknown index target: <#{@target_name}>")
95
+ attr_reader :table, :targets
96
+ def initialize(table, targets)
97
+ @table = table
98
+ @targets = targets
99
+ super("unknown index target: <#{@table.inspect}>: <#{@targets.inspect}>")
89
100
  end
90
101
  end
91
102
 
@@ -96,7 +107,7 @@ module Groonga
96
107
  @options = options
97
108
  @unknown_keys = unknown_keys
98
109
  @available_keys = available_keys
99
- message = "unknown keys are specified: #{u@nknown_keys.inspect}"
110
+ message = "unknown keys are specified: #{@unknown_keys.inspect}"
100
111
  message << ": available keys: #{@available_keys.inspect}"
101
112
  message << ": options: #{@options.inspect}"
102
113
  super(message)
@@ -114,6 +125,18 @@ module Groonga
114
125
  end
115
126
  end
116
127
 
128
+ # 参照先のテーブルを推測できないときに発生する。
129
+ class UnguessableReferenceTable < Error
130
+ attr_reader :name, :tried_table_names
131
+ def initialize(name, tried_table_names)
132
+ @name = name
133
+ @tried_table_names = tried_table_names
134
+ super("failed to guess referenced table name " +
135
+ "for reference column: #{@name.inspect}: " +
136
+ "tried table names: #{@tried_table_names.inspect}")
137
+ end
138
+ end
139
+
117
140
  class << self
118
141
 
119
142
  # call-seq:
@@ -349,6 +372,20 @@ module Groonga
349
372
  end
350
373
  end
351
374
 
375
+ # call-seq:
376
+ # Groonga::Schema.remove_column(table_name, column_name)
377
+ #
378
+ # 以下と同様:
379
+ #
380
+ # Groonga::Schema.change_table(table_name) do |table|
381
+ # table.remove_column(column_name)
382
+ # end
383
+ def remove_column(table_name, column_name)
384
+ change_table(table_name) do |table|
385
+ table.remove_column(column_name)
386
+ end
387
+ end
388
+
352
389
  # スキーマの内容を文字列で返す。返された値は
353
390
  # Groonga::Schema.restoreすることによりスキーマ内に組
354
391
  # み込むことができる。
@@ -369,13 +406,15 @@ module Groonga
369
406
  # スキーマ定義時に使用するGroonga::Contextを指定する。
370
407
  # 省略した場合はGroonga::Context.defaultを使用する。
371
408
  def dump(options={})
372
- Dumper.new(options).dump
409
+ options = options.dup
410
+ schema = new(:context => options.delete(:context))
411
+ schema.dump
373
412
  end
374
413
 
375
414
  # Groonga::Schema.dumpで文字列化したスキーマを組み込む。
376
415
  def restore(dumped_text, options={})
377
416
  define(options) do |schema|
378
- schema.load(dumped_text)
417
+ schema.restore(dumped_text)
379
418
  end
380
419
  end
381
420
 
@@ -414,6 +453,7 @@ module Groonga
414
453
  # 省略した場合はGroonga::Context.defaultを使用する。
415
454
  def initialize(options={})
416
455
  @options = (options || {}).dup
456
+ @options[:context] ||= Groonga::Context.default
417
457
  @definitions = []
418
458
  end
419
459
 
@@ -424,13 +464,23 @@ module Groonga
424
464
  end
425
465
  end
426
466
 
427
- # Groonga::Schema.dumpで返されたスキーマの内容を読み込む。
467
+ # Groonga::Schema#dumpで返されたスキーマの内容を読み込む。
428
468
  #
429
469
  # 読み込まれた内容は#defineを呼び出すまでは実行されない
430
470
  # ことに注意すること。
431
- def load(dumped_text)
471
+ def restore(dumped_text)
432
472
  instance_eval(dumped_text)
433
473
  end
474
+ # for backward compatibility.
475
+ # TODO: remove this at the next major release.
476
+ alias_method :load, :restore
477
+
478
+ # スキーマの内容を文字列で返す。返された値は
479
+ # Groonga::Schema#restoreすることによりスキーマ内に組み込むことができる。
480
+ def dump
481
+ dumper = Dumper.new(:context => @options[:context])
482
+ dumper.dump
483
+ end
434
484
 
435
485
  # call-seq:
436
486
  # schema.create_table(name, options={}) {|table| ...}
@@ -516,7 +566,7 @@ module Groonga
516
566
  # 登録される。
517
567
  def create_table(name, options={})
518
568
  definition = TableDefinition.new(name, @options.merge(options || {}))
519
- yield(definition)
569
+ yield(definition) if block_given?
520
570
  @definitions << definition
521
571
  end
522
572
 
@@ -624,6 +674,24 @@ module Groonga
624
674
  @definitions << definition
625
675
  end
626
676
 
677
+ # call-seq:
678
+ # schema.remove_column(table_name, column_name)
679
+ #
680
+ # 以下と同様:
681
+ #
682
+ # schema.change_table(table_name) do |table|
683
+ # table.remove_column(column_name)
684
+ # end
685
+ def remove_column(table_name, column_name)
686
+ change_table(table_name) do |table|
687
+ table.remove_column(column_name)
688
+ end
689
+ end
690
+
691
+ def context # :nodoc:
692
+ @options[:context] || Groonga::Context.default
693
+ end
694
+
627
695
  # スキーマ定義時にGroonga::Schema.create_tableや
628
696
  # Groonga::Schema#create_tableからブロックに渡されてくる
629
697
  # オブジェクト
@@ -708,9 +776,9 @@ module Groonga
708
776
  self
709
777
  end
710
778
 
711
- # 名前が_name_のカラムを削除する。
779
+ # 名前が_name_のカラムを削除します。
712
780
  #
713
- # _options_に指定可能な値はない(TODO _options_は不要?)。
781
+ # _options_に指定可能な値はありません(TODO _options_は不要?)。
714
782
  #
715
783
  def remove_column(name, options={})
716
784
  definition = self[name, ColumnRemoveDefinition]
@@ -725,9 +793,11 @@ module Groonga
725
793
  # call-seq:
726
794
  # table.index(target_column_full_name, options={})
727
795
  # table.index(target_table, target_column, options={})
796
+ # table.index(target_table, target_column1, target_column2, ..., options={})
728
797
  #
729
- # _target_table_の_target_column_を対象とするインデッ
730
- # クスカラムを作成する。
798
+ # _target_table_の_target_column_を対象とするインデック
799
+ # スカラムを作成します。複数のカラムを指定することもで
800
+ # きます。
731
801
  #
732
802
  # _target_column_full_name_で指定するときはテーブル名
733
803
  # とカラム名を"."でつなげます。例えば、「Users」テーブ
@@ -753,31 +823,71 @@ module Groonga
753
823
  # した場合は自動的にパスが付加される。
754
824
  #
755
825
  # [+:with_section+]
756
- # 転置索引にsection(段落情報)を合わせて格納する。
826
+ # +true+を指定すると転置索引にsection(段落情報)を合
827
+ # わせて格納する。未指定または+nil+を指定した場合、
828
+ # 複数のカラムを指定すると自動的に有効になる。
757
829
  #
758
830
  # [+:with_weight+]
759
- # 転置索引にweight情報を合わせて格納する。
831
+ # +true+を指定すると転置索引にweight情報を合わせて格
832
+ # 納する。
760
833
  #
761
834
  # [+:with_position+]
762
- # 転置索引に出現位置情報を合わせて格納する。
835
+ # +true+を指定すると転置索引に出現位置情報を合わせて
836
+ # 格納する。未指定または+nil+を指定した場合、テーブ
837
+ # ルがN-gram系のトークナイザーを利用している場合は自
838
+ # 動的に有効になる。
763
839
  def index(target_table_or_target_column_full_name, *args)
764
- if args.size > 2
765
- n_args = args.size + 1
766
- raise ArgumentError, "wrong number of arguments (#{n_args} for 2 or 3)"
840
+ key, target_table, target_columns, options =
841
+ parse_index_argument(target_table_or_target_column_full_name, *args)
842
+
843
+ name = options.delete(:name)
844
+ definition = self[key, IndexColumnDefinition]
845
+ if definition.nil?
846
+ definition = IndexColumnDefinition.new(name, options)
847
+ update_definition(key, IndexColumnDefinition, definition)
767
848
  end
768
- options = nil
769
- options = args.pop if args.last.is_a?(::Hash)
770
- if args.empty?
771
- target_column_full_name = target_table_or_target_column_full_name
772
- if target_column_full_name.is_a?(Groonga::Column)
773
- target_column_full_name = target_column_full_name.name
774
- end
775
- target_table, target_column = target_column_full_name.split(/\./, 2)
776
- else
777
- target_table = target_table_or_target_column_full_name
778
- target_column = args.pop
849
+ definition.target_table = target_table
850
+ definition.target_columns = target_columns
851
+ definition.options.merge!(column_options.merge(options))
852
+ self
853
+ end
854
+
855
+ # call-seq:
856
+ # table.remove_index(target_column_full_name, options={})
857
+ # table.remove_index(target_table, target_column, options={})
858
+ # table.remove_index(target_table, target_column1, target_column2, ..., options={})
859
+ #
860
+ # _target_table_の_target_column_を対象とするインデッ
861
+ # クスカラムを削除します。
862
+ #
863
+ # _target_column_full_name_で指定するときはテーブル名
864
+ # とカラム名を"."でつなげます。例えば、「Users」テーブ
865
+ # ルの「name」カラムのインデックスカラムを削除する場合
866
+ # はこうなります。
867
+ #
868
+ # table.remove_index("Users.name")
869
+ #
870
+ # _options_に指定可能な値は以下の通り。
871
+ #
872
+ # [+:name+]
873
+ # インデックスカラムのカラム名を任意に指定する。
874
+ def remove_index(target_table_or_target_column_full_name, *args)
875
+ key, target_table, target_columns, options =
876
+ parse_index_argument(target_table_or_target_column_full_name, *args)
877
+
878
+ name = options.delete(:name)
879
+ name ||= lambda do |context|
880
+ IndexColumnDefinition.column_name(context,
881
+ target_table,
882
+ target_columns)
883
+ end
884
+ definition = self[key, ColumnRemoveDefinition]
885
+ if definition.nil?
886
+ definition = ColumnRemoveDefinition.new(name, options)
887
+ update_definition(key, ColumnRemoveDefinition, definition)
779
888
  end
780
- define_index(target_table, target_column, options || {})
889
+ definition.options.merge!(options)
890
+ self
781
891
  end
782
892
 
783
893
  # 名前が_name_の32bit符号付き整数のカラムを作成する。
@@ -836,6 +946,14 @@ module Groonga
836
946
  column(name, "Time", options)
837
947
  end
838
948
 
949
+ # 以下と同様:
950
+ # table.time("updated_at")
951
+ # table.time("created_at")
952
+ def timestamps(options={})
953
+ time("created_at", options)
954
+ time("updated_at", options)
955
+ end
956
+
839
957
  # 名前が_name_の4Kbyte以下の文字列を格納できるカラムを
840
958
  # 作成する。
841
959
  #
@@ -867,9 +985,13 @@ module Groonga
867
985
  # 名前が_name_で_table_のレコードIDを格納する参照カラ
868
986
  # ムを作成する。
869
987
  #
988
+ # _table_が省略された場合は_name_の複数形が使われる。
989
+ # 例えば、_name_が"user"な場合は_table_は"users"になる。
990
+ #
870
991
  # _options_に指定可能な値は
871
992
  # Groonga::Schema::TableDefinition#columnを参照。
872
- def reference(name, table, options={})
993
+ def reference(name, table=nil, options={})
994
+ table ||= lambda {|context| guess_table_name(context, name)}
873
995
  column(name, table, options)
874
996
  end
875
997
 
@@ -894,8 +1016,8 @@ module Groonga
894
1016
  end
895
1017
 
896
1018
  private
897
- def update_definition(name, definition_class, definition) # :nodoc:
898
- old_definition = self[name, definition_class]
1019
+ def update_definition(key, definition_class, definition) # :nodoc:
1020
+ old_definition = self[key, definition_class]
899
1021
  if old_definition
900
1022
  index = @definitions.index(old_definition)
901
1023
  @definitions[index] = definition
@@ -934,14 +1056,14 @@ module Groonga
934
1056
  def create_options # :nodoc:
935
1057
  common = {
936
1058
  :name => @name,
937
- :path => @options[:path],
1059
+ :path => path,
938
1060
  :persistent => persistent?,
939
1061
  :value_type => @options[:value_type],
940
1062
  :context => context,
941
1063
  :sub_records => @options[:sub_records],
942
1064
  }
943
1065
  key_support_table_common = {
944
- :key_type => Schema.normalize_type(@options[:key_type]),
1066
+ :key_type => normalize_key_type(@options[:key_type] || "ShortText"),
945
1067
  :key_normalize => @options[:key_normalize],
946
1068
  :default_tokenizer => @options[:default_tokenizer],
947
1069
  }
@@ -958,6 +1080,14 @@ module Groonga
958
1080
  end
959
1081
  end
960
1082
 
1083
+ def path
1084
+ user_path = @options[:path]
1085
+ return user_path if user_path
1086
+ tables_dir = "#{context.database.path}.tables"
1087
+ FileUtils.mkdir_p(tables_dir)
1088
+ File.join(tables_dir, @name)
1089
+ end
1090
+
961
1091
  def column_options # :nodoc:
962
1092
  {:persistent => persistent?}
963
1093
  end
@@ -966,19 +1096,26 @@ module Groonga
966
1096
  @options[:persistent].nil? ? true : @options[:persistent]
967
1097
  end
968
1098
 
969
- def define_index(target_table, target_column, options)
970
- name = options.delete(:name)
971
- name ||= "#{target_table}_#{target_column}".gsub(/\./, "_")
972
-
973
- definition = self[name, IndexColumnDefinition]
974
- if definition.nil?
975
- definition = IndexColumnDefinition.new(name, options)
976
- update_definition(name, IndexColumnDefinition, definition)
1099
+ def parse_index_argument(target_table_or_target_column_full_name, *args)
1100
+ options = nil
1101
+ options = args.pop if args.last.is_a?(::Hash)
1102
+ if args.empty?
1103
+ target_column_full_name = target_table_or_target_column_full_name
1104
+ if target_column_full_name.is_a?(Groonga::Column)
1105
+ target_column_full_name = target_column_full_name.name
1106
+ end
1107
+ target_table, target_column = target_column_full_name.split(/\./, 2)
1108
+ target_columns = [target_column]
1109
+ key = [target_table, target_columns]
1110
+ else
1111
+ target_table_name = target_table_or_target_column_full_name
1112
+ target_table = lambda do |context|
1113
+ guess_table_name(context, target_table_name)
1114
+ end
1115
+ target_columns = args
1116
+ key = [target_table_name, target_columns]
977
1117
  end
978
- definition.target_table = target_table
979
- definition.target_column = target_column
980
- definition.options.merge!(column_options.merge(options))
981
- self
1118
+ [key, target_table, target_columns, options || {}]
982
1119
  end
983
1120
 
984
1121
  def same_table?(table, options)
@@ -987,12 +1124,15 @@ module Groonga
987
1124
  sub_records = options[:sub_records]
988
1125
  sub_records = false if sub_records.nil?
989
1126
  return false unless table.support_sub_records? == sub_records
1127
+ path = options[:path]
1128
+ return false if path and table.path != path
990
1129
 
991
1130
  case table
992
1131
  when Groonga::Array
993
1132
  true
994
1133
  when Groonga::Hash, Groonga::PatriciaTrie
995
- return false unless table.domain == resolve_name(options[:key_type])
1134
+ key_type = normalize_key_type(options[:key_type])
1135
+ return false unless table.domain == resolve_name(key_type)
996
1136
  default_tokenizer = resolve_name(options[:default_tokenizer])
997
1137
  return false unless table.default_tokenizer == default_tokenizer
998
1138
  key_normalize = options[:key_normalize]
@@ -1009,15 +1149,40 @@ module Groonga
1009
1149
  end
1010
1150
  end
1011
1151
 
1152
+ def normalize_key_type(key_type)
1153
+ Schema.normalize_type(key_type || "ShortText")
1154
+ end
1155
+
1012
1156
  def resolve_name(type)
1013
- if type.nil?
1014
- nil
1015
- elsif type.is_a?(String)
1157
+ if type.is_a?(String)
1016
1158
  context[type]
1017
1159
  else
1018
1160
  type
1019
1161
  end
1020
1162
  end
1163
+
1164
+ def guess_table_name(context, name)
1165
+ original_name = name
1166
+ name = name.to_s
1167
+ candidate_names = [name]
1168
+ if name.respond_to?(:pluralize)
1169
+ pluralized_name = name.pluralize
1170
+ else
1171
+ pluralized_name = "#{name}s"
1172
+ end
1173
+ candidate_names << pluralized_name
1174
+ if pluralized_name.respond_to?(:camelize)
1175
+ candidate_names << pluralized_name.camelize
1176
+ else
1177
+ candidate_names << pluralized_name.split(/_/).collect do |word|
1178
+ word.capitalize
1179
+ end.join
1180
+ end
1181
+ candidate_names.each do |table_name|
1182
+ return table_name if context[table_name]
1183
+ end
1184
+ raise UnguessableReferenceTable.new(original_name, candidate_names)
1185
+ end
1021
1186
  end
1022
1187
 
1023
1188
  class TableRemoveDefinition # :nodoc:
@@ -1027,7 +1192,7 @@ module Groonga
1027
1192
  end
1028
1193
 
1029
1194
  def define
1030
- context = @options[:context] || Groonga::Context.default
1195
+ context = @options[:context]
1031
1196
  context[@name].remove
1032
1197
  end
1033
1198
  end
@@ -1130,26 +1295,51 @@ module Groonga
1130
1295
  end
1131
1296
 
1132
1297
  def define(table_definition, table)
1298
+ context = table_definition.context
1133
1299
  column = table.column(@name)
1300
+ options = define_options(context, table)
1134
1301
  if column
1135
- return column if same_column?(table_definition, column)
1136
- if @options.delete(:force)
1302
+ return column if same_column?(context, column)
1303
+ if @options[:force]
1137
1304
  column.remove
1138
1305
  else
1139
- options = @options.merge(:type => @type)
1140
1306
  raise ColumnCreationWithDifferentOptions.new(column, options)
1141
1307
  end
1142
1308
  end
1143
1309
  table.define_column(@name,
1144
- Schema.normalize_type(@type),
1145
- @options)
1310
+ normalize_type(context),
1311
+ options)
1146
1312
  end
1147
1313
 
1148
1314
  private
1149
- def same_column?(table_definition, column)
1150
- context = table_definition.context
1315
+ def normalize_type(context)
1316
+ if @type.respond_to?(:call)
1317
+ resolved_type = @type.call(context)
1318
+ else
1319
+ resolved_type = @type
1320
+ end
1321
+ Schema.normalize_type(resolved_type)
1322
+ end
1323
+
1324
+ def same_column?(context, column)
1151
1325
  # TODO: should check column type and other options.
1152
- column.range == context[Schema.normalize_type(@type)]
1326
+ column.range == context[normalize_type(context)]
1327
+ end
1328
+
1329
+ def define_options(context, table)
1330
+ {
1331
+ :path => path(context, table),
1332
+ :type => @options[:type],
1333
+ :compress => @options[:compress],
1334
+ }
1335
+ end
1336
+
1337
+ def path(context, table)
1338
+ user_path = @options[:path]
1339
+ return user_path if user_path
1340
+ columns_dir = "#{table.path}.columns"
1341
+ FileUtils.mkdir_p(columns_dir)
1342
+ File.join(columns_dir, @name)
1153
1343
  end
1154
1344
  end
1155
1345
 
@@ -1164,12 +1354,33 @@ module Groonga
1164
1354
  end
1165
1355
 
1166
1356
  def define(table_definition, table)
1167
- table.column(@name).remove
1357
+ if @name.respond_to?(:call)
1358
+ name = @name.call(table_definition.context)
1359
+ else
1360
+ name = @name
1361
+ end
1362
+ table.column(name).remove
1168
1363
  end
1169
1364
  end
1170
1365
 
1171
1366
  class IndexColumnDefinition # :nodoc:
1172
- attr_accessor :name, :target_table, :target_column
1367
+ class << self
1368
+ def column_name(context, target_table, target_columns)
1369
+ target_table = resolve(context, target_table)
1370
+ "#{target_table.name}_#{target_columns.join('_')}"
1371
+ end
1372
+
1373
+ def resolve(context, object)
1374
+ return object if object.is_a?(Groonga::Object)
1375
+ if object.respond_to?(:call)
1376
+ object = object.call(context)
1377
+ end
1378
+ return nil if object.nil?
1379
+ context[object]
1380
+ end
1381
+ end
1382
+
1383
+ attr_accessor :name, :target_table, :target_columns
1173
1384
  attr_reader :options
1174
1385
 
1175
1386
  def initialize(name, options={})
@@ -1177,48 +1388,83 @@ module Groonga
1177
1388
  @name = @name.to_s if @name.is_a?(Symbol)
1178
1389
  @options = (options || {}).dup
1179
1390
  @target_table = nil
1180
- @target_column = nil
1391
+ @target_columns = nil
1181
1392
  end
1182
1393
 
1183
1394
  def define(table_definition, table)
1184
- target_name = "#{@target_table}.#{@target_column}"
1185
- target_table = table_definition.context[@target_table]
1186
- if target_table.nil? or
1187
- !(@target_column == "_key" or
1188
- target_table.have_column?(@target_column))
1189
- raise UnknownIndexTarget.new(target_name)
1395
+ context = table_definition.context
1396
+ target_table = resolve_target_table(context)
1397
+ if target_table.nil?
1398
+ raise UnknownIndexTargetTable.new(@target_table)
1190
1399
  end
1191
- index = table.column(@name)
1400
+ nonexistent_columns = nonexistent_columns(target_table)
1401
+ unless nonexistent_columns.empty?
1402
+ raise UnknownIndexTarget.new(target_table, nonexistent_columns)
1403
+ end
1404
+ name = @name || self.class.column_name(context,
1405
+ target_table,
1406
+ @target_columns)
1407
+ index = table.column(name)
1192
1408
  if index
1193
- return index if same_index?(table_definition, index)
1194
- if @options.delete(:force)
1409
+ return index if same_index?(context, index, target_table)
1410
+ if @options[:force]
1195
1411
  index.remove
1196
1412
  else
1197
1413
  options = @options.merge(:type => :index,
1198
- :target_name => target_name)
1414
+ :target_table => target_table,
1415
+ :target_columns => @target_columns)
1199
1416
  raise ColumnCreationWithDifferentOptions.new(index, options)
1200
1417
  end
1201
1418
  end
1202
- index = table.define_index_column(@name,
1203
- @target_table,
1204
- @options)
1205
- index.source = target_table.column(@target_column)
1419
+ index = table.define_index_column(name,
1420
+ target_table,
1421
+ define_options(context, table, name))
1422
+ index.sources = @target_columns.collect do |column|
1423
+ target_table.column(column)
1424
+ end
1206
1425
  index
1207
1426
  end
1208
1427
 
1209
1428
  private
1210
- def same_index?(table_definition, index)
1211
- context = table_definition.context
1429
+ def same_index?(context, index, target_table)
1212
1430
  # TODO: should check column type and other options.
1213
- return false if index.range.name != @target_table
1431
+ range = index.range
1432
+ return false if range != target_table
1214
1433
  source_names = index.sources.collect do |source|
1215
- if source == index
1216
- "#{index.range.name}._key"
1434
+ if source == range
1435
+ "_key"
1217
1436
  else
1218
- source.name
1437
+ source.local_name
1219
1438
  end
1220
1439
  end
1221
- source_names == ["#{@target_table}.#{@target_column}"]
1440
+ source_names.sort == @target_columns.sort
1441
+ end
1442
+
1443
+ def nonexistent_columns(target_table)
1444
+ @target_columns.reject do |column|
1445
+ column == "_key" or target_table.have_column?(column)
1446
+ end
1447
+ end
1448
+
1449
+ def resolve_target_table(context)
1450
+ self.class.resolve(context, @target_table)
1451
+ end
1452
+
1453
+ def define_options(context, table, name)
1454
+ {
1455
+ :path => path(context, table, name),
1456
+ :with_section => @options[:with_section],
1457
+ :with_weight => @options[:with_weight],
1458
+ :with_position => @options[:with_position],
1459
+ }
1460
+ end
1461
+
1462
+ def path(context, table, name)
1463
+ user_path = @options[:path]
1464
+ return user_path if user_path
1465
+ columns_dir = "#{table.path}.columns"
1466
+ FileUtils.mkdir_p(columns_dir)
1467
+ File.join(columns_dir, name)
1222
1468
  end
1223
1469
  end
1224
1470
 
@@ -1232,35 +1478,51 @@ module Groonga
1232
1478
  database = context.database
1233
1479
  return nil if database.nil?
1234
1480
 
1481
+ header + dump_schema(database) + footer
1482
+ end
1483
+
1484
+ def dump_schema(database)
1485
+ index_columns = []
1235
1486
  reference_columns = []
1236
1487
  definitions = []
1237
1488
  database.each do |object|
1238
1489
  next unless object.is_a?(Groonga::Table)
1239
- schema = "create_table(#{object.name.inspect}) do |table|\n"
1240
- object.columns.sort_by {|column| column.local_name}.each do |column|
1241
- if column.range.is_a?(Groonga::Table)
1242
- reference_columns << column
1490
+ table = object
1491
+ schema = create_table_header(table)
1492
+ table.columns.sort_by {|column| column.local_name}.each do |column|
1493
+ if column.is_a?(Groonga::IndexColumn)
1494
+ index_columns << column
1243
1495
  else
1244
- type = column_method(column)
1245
- name = column.local_name
1246
- schema << " table.#{type}(#{name.inspect})\n"
1496
+ if column.range.is_a?(Groonga::Table)
1497
+ reference_columns << column
1498
+ else
1499
+ schema << define_column(table, column)
1500
+ end
1247
1501
  end
1248
1502
  end
1249
- schema << "end"
1503
+ schema << create_table_footer(table)
1250
1504
  definitions << schema
1251
1505
  end
1252
1506
 
1253
1507
  reference_columns.group_by do |column|
1254
1508
  column.table
1255
1509
  end.each do |table, columns|
1256
- schema = "change_table(#{table.name.inspect}) do |table|\n"
1510
+ schema = change_table_header(table)
1257
1511
  columns.each do |column|
1258
- name = column.local_name
1259
- reference = column.range
1260
- schema << " table.reference(#{name.inspect}, " +
1261
- "#{reference.name.inspect})\n"
1512
+ schema << define_reference_column(table, column)
1262
1513
  end
1263
- schema << "end"
1514
+ schema << change_table_footer(table)
1515
+ definitions << schema
1516
+ end
1517
+
1518
+ index_columns.group_by do |column|
1519
+ column.table
1520
+ end.each do |table, columns|
1521
+ schema = change_table_header(table)
1522
+ columns.each do |column|
1523
+ schema << define_index_column(table, column)
1524
+ end
1525
+ schema << change_table_footer(table)
1264
1526
  definitions << schema
1265
1527
  end
1266
1528
 
@@ -1272,6 +1534,73 @@ module Groonga
1272
1534
  end
1273
1535
 
1274
1536
  private
1537
+ def header
1538
+ ""
1539
+ end
1540
+
1541
+ def footer
1542
+ ""
1543
+ end
1544
+
1545
+ def create_table_header(table)
1546
+ parameters = []
1547
+ unless table.is_a?(Groonga::Array)
1548
+ case table
1549
+ when Groonga::Hash
1550
+ parameters << ":type => :hash"
1551
+ when Groonga::PatriciaTrie
1552
+ parameters << ":type => :patricia_trie"
1553
+ end
1554
+ if table.domain
1555
+ parameters << ":key_type => #{table.domain.name.dump}"
1556
+ end
1557
+ end
1558
+ parameters << ":force => true"
1559
+ parameters.unshift("")
1560
+ parameters = parameters.join(",\n ")
1561
+ "create_table(#{table.name.dump}#{parameters}) do |table|\n"
1562
+ end
1563
+
1564
+ def create_table_footer(table)
1565
+ "end"
1566
+ end
1567
+
1568
+ def change_table_header(table)
1569
+ "change_table(#{table.name.inspect}) do |table|\n"
1570
+ end
1571
+
1572
+ def change_table_footer(table)
1573
+ "end"
1574
+ end
1575
+
1576
+ def define_column(table, column)
1577
+ type = column_method(column)
1578
+ name = column.local_name
1579
+ " table.#{type}(#{name.inspect})\n"
1580
+ end
1581
+
1582
+ def define_reference_column(table, column)
1583
+ name = column.local_name
1584
+ reference = column.range
1585
+ " table.reference(#{name.dump}, #{reference.name.dump})\n"
1586
+ end
1587
+
1588
+ def define_index_column(table, column)
1589
+ target_table_name = column.range.name
1590
+ sources = column.sources
1591
+ source_names = sources.collect do |source|
1592
+ if source.is_a?(table.class)
1593
+ "_key".dump
1594
+ else
1595
+ source.local_name.dump
1596
+ end
1597
+ end.join(", ")
1598
+ arguments = [target_table_name.dump,
1599
+ sources.size == 1 ? source_names : "[#{source_names}]",
1600
+ ":name => #{column.local_name.dump}"]
1601
+ " table.index(#{arguments.join(', ')})\n"
1602
+ end
1603
+
1275
1604
  def column_method(column)
1276
1605
  range = column.range
1277
1606
  case range.name