rroonga 1.0.1 → 1.0.2

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