rroonga 1.1.0 → 1.2.0

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 (64) hide show
  1. data/NEWS.ja.rdoc +18 -0
  2. data/NEWS.rdoc +18 -0
  3. data/bin/grndump +71 -0
  4. data/ext/groonga/rb-grn-accessor.c +64 -1
  5. data/ext/groonga/rb-grn-context.c +40 -1
  6. data/ext/groonga/rb-grn-database.c +47 -5
  7. data/ext/groonga/rb-grn-index-column.c +77 -7
  8. data/ext/groonga/rb-grn-object.c +8 -4
  9. data/ext/groonga/rb-grn-table-cursor.c +56 -1
  10. data/ext/groonga/rb-grn-table-key-support.c +2 -2
  11. data/ext/groonga/rb-grn-table.c +10 -38
  12. data/ext/groonga/rb-grn.h +11 -1
  13. data/html/index.html +6 -6
  14. data/html/ranguba.css +8 -1
  15. data/lib/groonga.rb +1 -0
  16. data/lib/groonga/dumper.rb +481 -0
  17. data/lib/groonga/schema.rb +54 -181
  18. data/rroonga-build.rb +1 -1
  19. data/test-unit-notify/Rakefile +47 -0
  20. data/test-unit-notify/lib/test/unit/notify.rb +104 -0
  21. data/test-unit/COPYING +56 -0
  22. data/test-unit/GPL +340 -0
  23. data/test-unit/PSFL +271 -0
  24. data/test-unit/Rakefile +18 -5
  25. data/test-unit/html/bar.svg +153 -0
  26. data/test-unit/html/developer.svg +469 -0
  27. data/test-unit/html/favicon.ico +0 -0
  28. data/test-unit/html/favicon.svg +82 -0
  29. data/test-unit/html/heading-mark.svg +393 -0
  30. data/test-unit/html/index.html +235 -13
  31. data/test-unit/html/index.html.ja +258 -15
  32. data/test-unit/html/install.svg +636 -0
  33. data/test-unit/html/logo.svg +483 -0
  34. data/test-unit/html/test-unit.css +339 -0
  35. data/test-unit/html/tutorial.svg +559 -0
  36. data/test-unit/lib/test/unit.rb +6 -1
  37. data/test-unit/lib/test/unit/assertions.rb +115 -11
  38. data/test-unit/lib/test/unit/autorunner.rb +5 -2
  39. data/test-unit/lib/test/unit/collector/load.rb +1 -1
  40. data/test-unit/lib/test/unit/color-scheme.rb +6 -2
  41. data/test-unit/lib/test/unit/diff.rb +17 -1
  42. data/test-unit/lib/test/unit/testcase.rb +7 -0
  43. data/test-unit/lib/test/unit/testresult.rb +34 -2
  44. data/test-unit/lib/test/unit/ui/console/testrunner.rb +9 -45
  45. data/test-unit/lib/test/unit/ui/tap/testrunner.rb +2 -12
  46. data/test-unit/lib/test/unit/ui/testrunner.rb +25 -0
  47. data/test-unit/lib/test/unit/util/backtracefilter.rb +1 -0
  48. data/test-unit/lib/test/unit/util/output.rb +31 -0
  49. data/test-unit/lib/test/unit/version.rb +1 -1
  50. data/test-unit/test/test-color-scheme.rb +4 -2
  51. data/test-unit/test/test_assertions.rb +51 -5
  52. data/test-unit/test/ui/test_tap.rb +33 -0
  53. data/test-unit/test/util/test-output.rb +11 -0
  54. data/test/groonga-test-utils.rb +1 -0
  55. data/test/test-accessor.rb +32 -0
  56. data/test/test-context.rb +7 -1
  57. data/test/test-database-dumper.rb +156 -0
  58. data/test/test-index-column.rb +67 -1
  59. data/test/test-schema-dumper.rb +181 -0
  60. data/test/test-schema.rb +53 -97
  61. data/test/test-table-dumper.rb +83 -0
  62. metadata +48 -11
  63. data/ext/groonga/mkmf.log +0 -99
  64. data/test-unit/html/classic.html +0 -15
@@ -133,8 +133,8 @@ rb_grn_object_finalizer (grn_ctx *context, int n_args, grn_obj **grn_objects,
133
133
  RB_GRN_INDEX_COLUMN(rb_grn_object));
134
134
  break;
135
135
  case GRN_ACCESSOR:
136
- rb_grn_named_object_finalizer(context, grn_object,
137
- RB_GRN_NAMED_OBJECT(rb_grn_object));
136
+ rb_grn_accessor_finalizer(context, grn_object,
137
+ RB_GRN_ACCESSOR(rb_grn_object));
138
138
  break;
139
139
  case GRN_EXPR:
140
140
  rb_grn_expression_finalizer(context, grn_object,
@@ -378,8 +378,12 @@ rb_grn_object_assign (VALUE klass, VALUE self, VALUE rb_context,
378
378
  rb_grn_object_bind_common(klass, self, rb_context, rb_grn_object,
379
379
  context, object);
380
380
  rb_grn_column_bind(RB_GRN_COLUMN(rb_grn_object), context, object);
381
- } else if (klass == rb_cGrnAccessor ||
382
- klass == rb_cGrnViewAccessor) {
381
+ } else if (klass == rb_cGrnAccessor) {
382
+ rb_grn_object = ALLOC(RbGrnAccessor);
383
+ rb_grn_object_bind_common(klass, self, rb_context, rb_grn_object,
384
+ context, object);
385
+ rb_grn_accessor_bind(RB_GRN_ACCESSOR(rb_grn_object), context, object);
386
+ } else if (klass == rb_cGrnViewAccessor) {
383
387
  rb_grn_object = ALLOC(RbGrnNamedObject);
384
388
  rb_grn_object_bind_common(klass, self, rb_context, rb_grn_object,
385
389
  context, object);
@@ -1,6 +1,6 @@
1
1
  /* -*- c-file-style: "ruby" -*- */
2
2
  /*
3
- Copyright (C) 2009-2010 Kouhei Sutou <kou@clear-code.com>
3
+ Copyright (C) 2009-2011 Kouhei Sutou <kou@clear-code.com>
4
4
 
5
5
  This library is free software; you can redistribute it and/or
6
6
  modify it under the terms of the GNU Lesser General Public
@@ -64,6 +64,61 @@ rb_grn_table_cursor_deconstruct (RbGrnTableCursor *rb_grn_table_cursor,
64
64
  range_id, range);
65
65
  }
66
66
 
67
+ int
68
+ rb_grn_table_cursor_order_to_flag (VALUE rb_order)
69
+ {
70
+ int flag = 0;
71
+
72
+ if (NIL_P(rb_order) ||
73
+ rb_grn_equal_option(rb_order, "asc") ||
74
+ rb_grn_equal_option(rb_order, "ascending")) {
75
+ flag |= GRN_CURSOR_ASCENDING;
76
+ } else if (rb_grn_equal_option(rb_order, "desc") ||
77
+ rb_grn_equal_option(rb_order, "descending")) {
78
+ flag |= GRN_CURSOR_DESCENDING;
79
+ } else {
80
+ rb_raise(rb_eArgError,
81
+ "order should be one of "
82
+ "[:asc, :ascending, :desc, :descending]: %s",
83
+ rb_grn_inspect(rb_order));
84
+ }
85
+
86
+ return flag;
87
+ }
88
+
89
+ int
90
+ rb_grn_table_cursor_order_by_to_flag (unsigned char table_type,
91
+ VALUE rb_table,
92
+ VALUE rb_order_by)
93
+ {
94
+ int flag = 0;
95
+
96
+ if (NIL_P(rb_order_by)) {
97
+ if (table_type == GRN_TABLE_PAT_KEY) {
98
+ flag |= GRN_CURSOR_BY_KEY;
99
+ } else {
100
+ flag |= GRN_CURSOR_BY_ID;
101
+ }
102
+ } else if (rb_grn_equal_option(rb_order_by, "id")) {
103
+ flag |= GRN_CURSOR_BY_ID;
104
+ } else if (rb_grn_equal_option(rb_order_by, "key")) {
105
+ if (table_type != GRN_TABLE_PAT_KEY) {
106
+ rb_raise(rb_eArgError,
107
+ "order_by => :key is available "
108
+ "only for Groonga::PatriciaTrie: %s",
109
+ rb_grn_inspect(rb_table));
110
+ }
111
+ flag |= GRN_CURSOR_BY_KEY;
112
+ } else {
113
+ rb_raise(rb_eArgError,
114
+ "order_by should be one of [:id%s]: %s",
115
+ table_type == GRN_TABLE_PAT_KEY ? ", :key" : "",
116
+ rb_grn_inspect(rb_order_by));
117
+ }
118
+
119
+ return flag;
120
+ }
121
+
67
122
  /*
68
123
  * call-seq:
69
124
  * cursor.value -> 値
@@ -663,7 +663,7 @@ rb_grn_table_key_support_set_default_tokenizer (VALUE self, VALUE rb_tokenizer)
663
663
  * す。
664
664
  */
665
665
  static VALUE
666
- rb_grn_table_key_support_normalize_key_p (VALUE self)
666
+ rb_grn_table_key_normalize_key_p (VALUE self)
667
667
  {
668
668
  grn_obj *table;
669
669
 
@@ -732,7 +732,7 @@ rb_grn_init_table_key_support (VALUE mGrn)
732
732
  rb_grn_table_key_support_set_default_tokenizer, 1);
733
733
 
734
734
  rb_define_method(rb_mGrnTableKeySupport, "normalize_key?",
735
- rb_grn_table_key_support_normalize_key_p, 0);
735
+ rb_grn_table_key_normalize_key_p, 0);
736
736
 
737
737
  rb_define_method(rb_mGrnTableKeySupport, "support_key?",
738
738
  rb_grn_table_key_support_support_key_p, 0);
@@ -1,6 +1,6 @@
1
1
  /* -*- c-file-style: "ruby" -*- */
2
2
  /*
3
- Copyright (C) 2009-2010 Kouhei Sutou <kou@clear-code.com>
3
+ Copyright (C) 2009-2011 Kouhei Sutou <kou@clear-code.com>
4
4
 
5
5
  This library is free software; you can redistribute it and/or
6
6
  modify it under the terms of the GNU Lesser General Public
@@ -43,7 +43,9 @@ grn_obj *
43
43
  rb_grn_table_from_ruby_object (VALUE object, grn_ctx **context)
44
44
  {
45
45
  if (!RVAL2CBOOL(rb_obj_is_kind_of(object, rb_cGrnTable))) {
46
- rb_raise(rb_eTypeError, "not a groonga table");
46
+ rb_raise(rb_eTypeError,
47
+ "not a groonga table: <%s>",
48
+ rb_grn_inspect(object));
47
49
  }
48
50
 
49
51
  return RVAL2GRNOBJECT(object, context);
@@ -738,41 +740,10 @@ rb_grn_table_open_grn_cursor (int argc, VALUE *argv, VALUE self,
738
740
  if (!NIL_P(rb_limit))
739
741
  limit = NUM2INT(rb_limit);
740
742
 
741
- if (NIL_P(rb_order)) {
742
- } else if (rb_grn_equal_option(rb_order, "asc") ||
743
- rb_grn_equal_option(rb_order, "ascending")) {
744
- flags |= GRN_CURSOR_ASCENDING;
745
- } else if (rb_grn_equal_option(rb_order, "desc") ||
746
- rb_grn_equal_option(rb_order, "descending")) {
747
- flags |= GRN_CURSOR_DESCENDING;
748
- } else {
749
- rb_raise(rb_eArgError,
750
- "order should be one of "
751
- "[:asc, :ascending, :desc, :descending]: %s",
752
- rb_grn_inspect(rb_order));
753
- }
754
- if (NIL_P(rb_order_by)) {
755
- if (table->header.type == GRN_TABLE_PAT_KEY) {
756
- flags |= GRN_CURSOR_BY_KEY;
757
- } else {
758
- flags |= GRN_CURSOR_BY_ID;
759
- }
760
- } else if (rb_grn_equal_option(rb_order_by, "id")) {
761
- flags |= GRN_CURSOR_BY_ID;
762
- } else if (rb_grn_equal_option(rb_order_by, "key")) {
763
- if (table->header.type != GRN_TABLE_PAT_KEY) {
764
- rb_raise(rb_eArgError,
765
- "order_by => :key is available "
766
- "only for Groonga::PatriciaTrie: %s",
767
- rb_grn_inspect(self));
768
- }
769
- flags |= GRN_CURSOR_BY_KEY;
770
- } else {
771
- rb_raise(rb_eArgError,
772
- "order_by should be one of [:id%s]: %s",
773
- table->header.type == GRN_TABLE_PAT_KEY ? ", :key" : "",
774
- rb_grn_inspect(rb_order_by));
775
- }
743
+ flags |= rb_grn_table_cursor_order_to_flag(rb_order);
744
+ flags |= rb_grn_table_cursor_order_by_to_flag(table->header.type,
745
+ self,
746
+ rb_order_by);
776
747
 
777
748
  if (RVAL2CBOOL(rb_greater_than))
778
749
  flags |= GRN_CURSOR_GT;
@@ -816,7 +787,8 @@ rb_grn_table_open_grn_cursor (int argc, VALUE *argv, VALUE self,
816
787
  *
817
788
  * [+:order+]
818
789
  * +:asc+または+:ascending+を指定すると昇順にレコードを取
819
- * り出す。
790
+ * り出す。(デフォルト)
791
+ *
820
792
  * +:desc+または+:descending+を指定すると降順にレコードを
821
793
  * 取り出す。
822
794
  *
data/ext/groonga/rb-grn.h CHANGED
@@ -68,7 +68,7 @@ RB_GRN_BEGIN_DECLS
68
68
  #endif
69
69
 
70
70
  #define RB_GRN_MAJOR_VERSION 1
71
- #define RB_GRN_MINOR_VERSION 1
71
+ #define RB_GRN_MINOR_VERSION 2
72
72
  #define RB_GRN_MICRO_VERSION 0
73
73
 
74
74
  #define RB_GRN_QUERY_DEFAULT_MAX_EXPRESSIONS 32
@@ -153,6 +153,7 @@ typedef struct _RbGrnAccessor RbGrnAccessor;
153
153
  struct _RbGrnAccessor
154
154
  {
155
155
  RbGrnNamedObject parent;
156
+ grn_obj *value;
156
157
  };
157
158
 
158
159
  typedef struct _RbGrnTableCursor RbGrnTableCursor;
@@ -386,6 +387,10 @@ VALUE rb_grn_table_set_column_value (VALUE self,
386
387
 
387
388
  grn_ctx *rb_grn_table_cursor_ensure_context (VALUE cursor,
388
389
  VALUE *rb_context);
390
+ int rb_grn_table_cursor_order_to_flag (VALUE rb_order);
391
+ int rb_grn_table_cursor_order_by_to_flag (unsigned char table_type,
392
+ VALUE rb_table,
393
+ VALUE rb_order_by);
389
394
 
390
395
  void rb_grn_table_key_support_bind (RbGrnTableKeySupport *rb_grn_table_key_support,
391
396
  grn_ctx *context,
@@ -518,6 +523,11 @@ VALUE rb_grn_column_expression_builder_build
518
523
  #define GRNCOLUMN2RVAL(klass, context, column, owner) \
519
524
  (rb_grn_column_to_ruby_object(klass, context, column, owner))
520
525
 
526
+ #define RVAL2GRNACCESSOR(object) \
527
+ (rb_grn_accessor_from_ruby_object(object))
528
+ #define GRNACCESSOR2RVAL(context, accessor, owner) \
529
+ (rb_grn_accessor_to_ruby_object(context, accessor, owner))
530
+
521
531
  #define RVAL2GRNQUERY(object) (rb_grn_query_from_ruby_object(object))
522
532
  #define GRNQUERY2RVAL(context, column)(rb_grn_query_to_ruby_object(context, column))
523
533
 
data/html/index.html CHANGED
@@ -49,7 +49,7 @@
49
49
 
50
50
  <h3>rroongaの最新リリース</h3>
51
51
  <p>
52
- 2011-02-09にリリースされた1.1.0が最新です。
52
+ 2011-04-01にリリースされた1.2.0が最新です。
53
53
  </p>
54
54
 
55
55
  <h3 id="install-rroonga">rroongaのインストール</h3>
@@ -100,7 +100,7 @@
100
100
  これから徐々に追加されていく予定です。
101
101
  </p>
102
102
  <ul>
103
- <li><a href="active_groonga/">ActiveGroongaのリファレンスマニュアル</a></li>
103
+ <li><a href="activegroonga/">ActiveGroongaのリファレンスマニュアル</a></li>
104
104
  </ul>
105
105
 
106
106
  <h2 id="about-racknga">racknga</h2>
@@ -135,11 +135,11 @@
135
135
 
136
136
  <h3>ChupaTextの最新リリース</h3>
137
137
  <p>
138
- 2010-12-26にリリースされた0.8.0が最新です。
138
+ 2011-02-09にリリースされた0.9.0が最新です。
139
139
  </p>
140
140
  <p>
141
- [<a href="http://rubyforge.org/frs/download.php/73718/chupatext-0.8.0.tar.gz">ダウンロード</a>]
142
- [<a href="chupatext/ja/news.html#news.release-0-8-0">変更点</a>]
141
+ [<a href="http://rubyforge.org/frs/download.php/74190/chupatext-0.9.0.tar.gz">ダウンロード</a>]
142
+ [<a href="chupatext/ja/news.html#news.release-0-9-0">変更点</a>]
143
143
  </p>
144
144
 
145
145
  <h3 id="install-chupatext">ChupaTextのインストール</h3>
@@ -216,7 +216,7 @@
216
216
  </p>
217
217
  <p id="sponsor-github">
218
218
  <a href="http://github.com/ranguba/">
219
- ラングバプロジェクトはGitHubにホスティングしてもらっています。
219
+ <img src="github-logo.png" width="100" height="45" border="0" alt="ラングバプロジェクトはGitHubにホスティングしてもらっています。" />
220
220
  </a>
221
221
  </p>
222
222
  <p id="sponsor-tango">
data/html/ranguba.css CHANGED
@@ -248,13 +248,20 @@ div.sponsors p#sponsor-rubyforge
248
248
  right: 50px;
249
249
  }
250
250
 
251
- div.sponsors p#sponsor-tango
251
+ div.sponsors p#sponsor-github
252
252
  {
253
253
  position: absolute;
254
254
  top: 50px;
255
255
  right: 50px;
256
256
  }
257
257
 
258
+ div.sponsors p#sponsor-tango
259
+ {
260
+ position: absolute;
261
+ top: 105px;
262
+ right: 50px;
263
+ }
264
+
258
265
  dt
259
266
  {
260
267
  margin-bottom: 0.25em;
data/lib/groonga.rb CHANGED
@@ -87,5 +87,6 @@ end
87
87
 
88
88
  require 'groonga/context'
89
89
  require 'groonga/patricia-trie'
90
+ require 'groonga/dumper'
90
91
  require 'groonga/schema'
91
92
  require 'groonga/pagination'
@@ -0,0 +1,481 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require 'stringio'
19
+
20
+ module Groonga
21
+ # データベースの内容をgrn式形式の文字列として出力するクラス。
22
+ class DatabaseDumper
23
+ def initialize(options={})
24
+ @options = options
25
+ end
26
+
27
+ def dump
28
+ options = @options.dup
29
+ have_output = !@options[:output].nil?
30
+ options[:output] ||= StringIO.new
31
+ if options[:database].nil?
32
+ options[:context] ||= Groonga::Context.default
33
+ options[:database] = options[:context].database
34
+ end
35
+ options[:dump_schema] = true if options[:dump_schema].nil?
36
+ options[:dump_tables] = true if options[:dump_tables].nil?
37
+
38
+ dump_schema(options) if options[:dump_schema]
39
+ dump_tables(options) if options[:dump_tables]
40
+
41
+ if have_output
42
+ nil
43
+ else
44
+ options[:output].string
45
+ end
46
+ end
47
+
48
+ private
49
+ def dump_schema(options)
50
+ SchemaDumper.new(options.merge(:syntax => :command)).dump
51
+ end
52
+
53
+ def dump_tables(options)
54
+ first_table = true
55
+ options[:database].each(:order_by => :key) do |object|
56
+ next unless object.is_a?(Groonga::Table)
57
+ next if object.size.zero?
58
+ next if lexicon_table?(object)
59
+ next unless target_table?(options[:tables], object)
60
+ options[:output].write("\n") if !first_table or options[:dump_schema]
61
+ first_table = false
62
+ dump_records(object, options)
63
+ end
64
+ end
65
+
66
+ def dump_records(table, options)
67
+ TableDumper.new(table, options).dump
68
+ end
69
+
70
+ def lexicon_table?(table)
71
+ table.support_key? and
72
+ table.default_tokenizer and
73
+ table.columns.any? {|column| column.index?}
74
+ end
75
+
76
+ def target_table?(target_tables, table)
77
+ return true if target_tables.nil? or target_tables.empty?
78
+ target_tables.any? do |name|
79
+ name === table.name
80
+ end
81
+ end
82
+ end
83
+
84
+ # スキーマの内容をRubyスクリプトまたはgrn式形式の文字列と
85
+ # して出力するクラス。
86
+ class SchemaDumper
87
+ def initialize(options={})
88
+ @options = (options || {}).dup
89
+ end
90
+
91
+ def dump
92
+ database = @options[:database]
93
+ if database.nil?
94
+ context = @options[:context] || Groonga::Context.default
95
+ database = context.database
96
+ end
97
+ return nil if database.nil?
98
+
99
+ output = @options[:output]
100
+ have_output = !output.nil?
101
+ output ||= StringIO.new
102
+ result = syntax(database, output).dump
103
+ if have_output
104
+ result
105
+ else
106
+ output.string
107
+ end
108
+ end
109
+
110
+ private
111
+ def syntax(database, output)
112
+ case @options[:syntax]
113
+ when :command
114
+ CommandSyntax.new(database, output)
115
+ else
116
+ RubySyntax.new(database, output)
117
+ end
118
+ end
119
+
120
+ class BaseSyntax # :nodoc:
121
+ def initialize(database, output)
122
+ @database = database
123
+ @output = output
124
+ @table_defined = false
125
+ @index_columns = []
126
+ @reference_columns = []
127
+ end
128
+
129
+ def dump
130
+ header
131
+ dump_schema
132
+ footer
133
+ end
134
+
135
+ def dump_schema
136
+ @database.each(:order_by => :key) do |object|
137
+ create_table(object) if object.is_a?(Groonga::Table)
138
+ end
139
+
140
+ @reference_columns.group_by do |column|
141
+ column.table
142
+ end.each do |table, columns|
143
+ change_table(table) do
144
+ columns.each do |column|
145
+ define_reference_column(table, column)
146
+ end
147
+ end
148
+ end
149
+
150
+ @index_columns.group_by do |column|
151
+ column.table
152
+ end.each do |table, columns|
153
+ change_table(table) do
154
+ columns.each do |column|
155
+ define_index_column(table, column)
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ private
162
+ def write(content)
163
+ @output.write(content)
164
+ end
165
+
166
+ def header
167
+ write("")
168
+ end
169
+
170
+ def footer
171
+ write("")
172
+ end
173
+
174
+ def table_separator
175
+ write("\n")
176
+ end
177
+
178
+ def create_table(table)
179
+ table_separator if @table_defined
180
+ create_table_header(table)
181
+ table.columns.sort_by {|column| column.local_name}.each do |column|
182
+ if column.is_a?(Groonga::IndexColumn)
183
+ @index_columns << column
184
+ else
185
+ if column.range.is_a?(Groonga::Table)
186
+ @reference_columns << column
187
+ else
188
+ define_column(table, column)
189
+ end
190
+ end
191
+ end
192
+ create_table_footer(table)
193
+ @table_defined = true
194
+ end
195
+
196
+ def change_table(table)
197
+ table_separator if @table_defined
198
+ change_table_header(table)
199
+ yield(table)
200
+ change_table_footer(table)
201
+ @table_defined = true
202
+ end
203
+ end
204
+
205
+ class RubySyntax < BaseSyntax # :nodoc:
206
+ private
207
+ def create_table_header(table)
208
+ parameters = []
209
+ unless table.is_a?(Groonga::Array)
210
+ case table
211
+ when Groonga::Hash
212
+ parameters << ":type => :hash"
213
+ when Groonga::PatriciaTrie
214
+ parameters << ":type => :patricia_trie"
215
+ end
216
+ if table.domain
217
+ parameters << ":key_type => #{table.domain.name.dump}"
218
+ if table.normalize_key?
219
+ parameters << ":key_normalize => true"
220
+ end
221
+ end
222
+ default_tokenizer = table.default_tokenizer
223
+ if default_tokenizer
224
+ parameters << ":default_tokenizer => #{default_tokenizer.name.dump}"
225
+ end
226
+ end
227
+ parameters << ":force => true"
228
+ parameters.unshift("")
229
+ parameters = parameters.join(",\n ")
230
+ write("create_table(#{table.name.dump}#{parameters}) do |table|\n")
231
+ end
232
+
233
+ def create_table_footer(table)
234
+ write("end\n")
235
+ end
236
+
237
+ def change_table_header(table)
238
+ write("change_table(#{table.name.inspect}) do |table|\n")
239
+ end
240
+
241
+ def change_table_footer(table)
242
+ write("end\n")
243
+ end
244
+
245
+ def define_column(table, column)
246
+ type = column_method(column)
247
+ name = column.local_name
248
+ write(" table.#{type}(#{name.inspect})\n")
249
+ end
250
+
251
+ def define_reference_column(table, column)
252
+ name = column.local_name
253
+ reference = column.range
254
+ write(" table.reference(#{name.dump}, #{reference.name.dump})\n")
255
+ end
256
+
257
+ def define_index_column(table, column)
258
+ target_table_name = column.range.name
259
+ sources = column.sources
260
+ source_names = sources.collect do |source|
261
+ if source.is_a?(table.class)
262
+ "_key".dump
263
+ else
264
+ source.local_name.dump
265
+ end
266
+ end.join(", ")
267
+ arguments = [target_table_name.dump,
268
+ sources.size == 1 ? source_names : "[#{source_names}]",
269
+ ":name => #{column.local_name.dump}"]
270
+ write(" table.index(#{arguments.join(', ')})\n")
271
+ end
272
+
273
+ def column_method(column)
274
+ range = column.range
275
+ case range.name
276
+ when "Int32"
277
+ "integer32"
278
+ when "Int64"
279
+ "integer64"
280
+ when "UInt32"
281
+ "unsigned_integer32"
282
+ when "UInt64"
283
+ "unsigned_integer64"
284
+ when "Float"
285
+ "float"
286
+ when "Time"
287
+ "time"
288
+ when "ShortText"
289
+ "short_text"
290
+ when "Text"
291
+ "text"
292
+ when "LongText"
293
+ "long_text"
294
+ else
295
+ raise ArgumentError, "unsupported column: #{column.inspect}"
296
+ end
297
+ end
298
+ end
299
+
300
+ class CommandSyntax < BaseSyntax # :nodoc:
301
+ private
302
+ def create_table_header(table)
303
+ parameters = []
304
+ flags = []
305
+ case table
306
+ when Groonga::Array
307
+ flags << "TABLE_NO_KEY"
308
+ when Groonga::Hash
309
+ flags << "TABLE_HASH_KEY"
310
+ when Groonga::PatriciaTrie
311
+ flags << "TABLE_PAT_KEY"
312
+ end
313
+ if table.domain
314
+ flags << "KEY_NORMALIZE" if table.normalize_key?
315
+ if table.is_a?(Groonga::PatriciaTrie) and table.register_key_with_sis?
316
+ flags << "KEY_WITH_SIS"
317
+ end
318
+ end
319
+ parameters << "#{flags.join('|')}"
320
+ if table.domain
321
+ parameters << "--key_type #{table.domain.name}"
322
+ end
323
+ if table.range
324
+ parameters << "--value_type #{table.range.name}"
325
+ end
326
+ if table.domain
327
+ default_tokenizer = table.default_tokenizer
328
+ if default_tokenizer
329
+ parameters << "--default_tokenizer #{default_tokenizer.name}"
330
+ end
331
+ end
332
+ write("table_create #{table.name} #{parameters.join(' ')}\n")
333
+ end
334
+
335
+ def create_table_footer(table)
336
+ end
337
+
338
+ def change_table_header(table)
339
+ end
340
+
341
+ def change_table_footer(table)
342
+ end
343
+
344
+ def define_column(table, column)
345
+ parameters = []
346
+ parameters << table.name
347
+ parameters << column.local_name
348
+ flags = []
349
+ if column.scalar?
350
+ flags << "COLUMN_SCALAR"
351
+ elsif column.vector?
352
+ flags << "COLUMN_VECTOR"
353
+ end
354
+ # TODO: support COMPRESS_ZLIB and COMPRESS_LZO?
355
+ parameters << "#{flags.join('|')}"
356
+ parameters << "#{column.range.name}"
357
+ write("column_create #{parameters.join(' ')}\n")
358
+ end
359
+
360
+ def define_reference_column(table, column)
361
+ define_column(table, column)
362
+ end
363
+
364
+ def define_index_column(table, column)
365
+ parameters = []
366
+ parameters << table.name
367
+ parameters << column.local_name
368
+ flags = []
369
+ flags << "COLUMN_INDEX"
370
+ flags << "WITH_SECTION" if column.with_section?
371
+ flags << "WITH_WEIGHT" if column.with_weight?
372
+ flags << "WITH_POSITION" if column.with_position?
373
+ parameters << "#{flags.join('|')}"
374
+ parameters << "#{column.range.name}"
375
+ source_names = column.sources.collect do |source|
376
+ if source.is_a?(table.class)
377
+ "_key"
378
+ else
379
+ source.local_name
380
+ end
381
+ end
382
+ parameters << "#{source_names.join(',')}" unless source_names.empty?
383
+ write("column_create #{parameters.join(' ')}\n")
384
+ end
385
+ end
386
+ end
387
+
388
+ class TableDumper
389
+ def initialize(table, options={})
390
+ @table = table
391
+ @options = options
392
+ @output = @options[:output]
393
+ @have_output = !@output.nil?
394
+ @output ||= StringIO.new
395
+ end
396
+
397
+ def dump
398
+ dump_load_command
399
+ if @have_output
400
+ nil
401
+ else
402
+ @output.string
403
+ end
404
+ end
405
+
406
+ private
407
+ def write(content)
408
+ @output.write(content)
409
+ end
410
+
411
+ def dump_load_command
412
+ write("load --table #{@table.name}\n")
413
+ write("[\n")
414
+ columns = available_columns
415
+ dump_columns(columns)
416
+ dump_records(columns)
417
+ write("\n]\n")
418
+ end
419
+
420
+ def dump_columns(columns)
421
+ column_names = columns.collect do |column|
422
+ column.local_name
423
+ end
424
+ write(column_names.to_json)
425
+ end
426
+
427
+ def dump_records(columns)
428
+ @table.each do |record|
429
+ write(",\n")
430
+ values = columns.collect do |column|
431
+ resolve_value(column[record.id])
432
+ end
433
+ write(values.to_json)
434
+ end
435
+ end
436
+
437
+ def resolve_value(value)
438
+ case value
439
+ when ::Array
440
+ value.collect do |v|
441
+ resolve_value(v)
442
+ end
443
+ when Groonga::Record
444
+ if value.support_key?
445
+ value = value.key
446
+ else
447
+ value = value.id
448
+ end
449
+ resolve_value(value)
450
+ when Time
451
+ # TODO: groonga should support UTC format literal
452
+ # value.utc.strftime("%Y-%m-%d %H:%M:%S.%6N")
453
+ value.to_f
454
+ when NilClass
455
+ # TODO: remove me. nil reference column value
456
+ # doesn't accept null.
457
+ ""
458
+ else
459
+ value
460
+ end
461
+ end
462
+
463
+ def available_columns
464
+ columns = []
465
+ if @table.support_key?
466
+ columns << @table.column("_key")
467
+ else
468
+ columns << @table.column("_id")
469
+ end
470
+ columns << @table.column("_value") unless @table.range.nil?
471
+ data_columns = @table.columns.reject do |column|
472
+ column.index?
473
+ end
474
+ sorted_columns = data_columns.sort_by do |column|
475
+ column.local_name
476
+ end
477
+ columns.concat(sorted_columns)
478
+ columns
479
+ end
480
+ end
481
+ end