rroonga 2.0.4 → 2.0.5

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 (90) hide show
  1. data/Rakefile +21 -2
  2. data/bin/grndump +3 -2
  3. data/ext/groonga/extconf.rb +1 -6
  4. data/ext/groonga/rb-grn-array.c +58 -31
  5. data/ext/groonga/rb-grn-column.c +220 -148
  6. data/ext/groonga/rb-grn-context.c +46 -32
  7. data/ext/groonga/rb-grn-database.c +102 -90
  8. data/ext/groonga/rb-grn-double-array-trie.c +205 -163
  9. data/ext/groonga/rb-grn-encoding-support.c +2 -3
  10. data/ext/groonga/rb-grn-encoding.c +13 -8
  11. data/ext/groonga/rb-grn-exception.c +1 -1
  12. data/ext/groonga/rb-grn-expression.c +110 -133
  13. data/ext/groonga/rb-grn-fix-size-column.c +5 -4
  14. data/ext/groonga/rb-grn-geo-point.c +55 -0
  15. data/ext/groonga/rb-grn-hash.c +120 -82
  16. data/ext/groonga/rb-grn-index-column.c +66 -70
  17. data/ext/groonga/rb-grn-index-cursor.c +3 -0
  18. data/ext/groonga/rb-grn-logger.c +33 -51
  19. data/ext/groonga/rb-grn-object.c +2 -0
  20. data/ext/groonga/rb-grn-operator.c +1 -1
  21. data/ext/groonga/rb-grn-patricia-trie.c +287 -232
  22. data/ext/groonga/rb-grn-plugin.c +11 -14
  23. data/ext/groonga/rb-grn-snippet.c +37 -54
  24. data/ext/groonga/rb-grn-table-cursor-key-support.c +3 -3
  25. data/ext/groonga/rb-grn-table-cursor.c +8 -6
  26. data/ext/groonga/rb-grn-table-key-support.c +22 -29
  27. data/ext/groonga/rb-grn-table.c +355 -279
  28. data/ext/groonga/rb-grn-type.c +18 -25
  29. data/ext/groonga/rb-grn-utils.c +77 -7
  30. data/ext/groonga/rb-grn-variable-size-column.c +12 -20
  31. data/ext/groonga/rb-grn-view.c +56 -51
  32. data/ext/groonga/rb-grn.h +28 -1
  33. data/ext/groonga/rb-groonga.c +1 -0
  34. data/lib/groonga.rb +2 -0
  35. data/lib/groonga/command.rb +3 -1
  36. data/lib/groonga/database.rb +27 -0
  37. data/lib/groonga/dumper.rb +96 -17
  38. data/lib/groonga/geo-point.rb +216 -0
  39. data/lib/groonga/schema.rb +29 -46
  40. data/rroonga-build.rb +1 -1
  41. data/rroonga.gemspec +90 -0
  42. data/test/groonga-test-utils.rb +168 -0
  43. data/test/run-test.rb +60 -0
  44. data/test/test-accessor.rb +36 -0
  45. data/test/test-array.rb +146 -0
  46. data/test/test-column.rb +350 -0
  47. data/test/test-command-select.rb +181 -0
  48. data/test/test-context.rb +130 -0
  49. data/test/test-database-dumper.rb +259 -0
  50. data/test/test-database.rb +173 -0
  51. data/test/test-double-array-trie.rb +189 -0
  52. data/test/test-encoding.rb +33 -0
  53. data/test/test-exception.rb +230 -0
  54. data/test/test-expression-builder.rb +602 -0
  55. data/test/test-expression.rb +134 -0
  56. data/test/test-fix-size-column.rb +77 -0
  57. data/test/test-geo-point.rb +190 -0
  58. data/test/test-gqtp.rb +70 -0
  59. data/test/test-hash.rb +367 -0
  60. data/test/test-index-column.rb +180 -0
  61. data/test/test-index-cursor.rb +123 -0
  62. data/test/test-logger.rb +37 -0
  63. data/test/test-pagination.rb +249 -0
  64. data/test/test-patricia-trie.rb +440 -0
  65. data/test/test-plugin.rb +35 -0
  66. data/test/test-procedure.rb +37 -0
  67. data/test/test-query-log.rb +258 -0
  68. data/test/test-record.rb +577 -0
  69. data/test/test-remote.rb +63 -0
  70. data/test/test-schema-create-table.rb +267 -0
  71. data/test/test-schema-dumper.rb +235 -0
  72. data/test/test-schema-type.rb +208 -0
  73. data/test/test-schema-view.rb +90 -0
  74. data/test/test-schema.rb +886 -0
  75. data/test/test-snippet.rb +130 -0
  76. data/test/test-table-dumper.rb +353 -0
  77. data/test/test-table-offset-and-limit.rb +100 -0
  78. data/test/test-table-select-mecab.rb +80 -0
  79. data/test/test-table-select-normalize.rb +57 -0
  80. data/test/test-table-select-weight.rb +123 -0
  81. data/test/test-table-select.rb +191 -0
  82. data/test/test-table-traverse.rb +304 -0
  83. data/test/test-table.rb +711 -0
  84. data/test/test-type.rb +79 -0
  85. data/test/test-variable-size-column.rb +147 -0
  86. data/test/test-variable.rb +28 -0
  87. data/test/test-vector-column.rb +76 -0
  88. data/test/test-version.rb +61 -0
  89. data/test/test-view.rb +72 -0
  90. metadata +330 -202
@@ -136,6 +136,7 @@ Init_groonga (void)
136
136
  rb_grn_init_column(mGrn);
137
137
  rb_grn_init_accessor(mGrn);
138
138
  rb_grn_init_view_accessor(mGrn);
139
+ rb_grn_init_geo_point(mGrn);
139
140
  rb_grn_init_record(mGrn);
140
141
  rb_grn_init_view_record(mGrn);
141
142
  rb_grn_init_variable(mGrn);
data/lib/groonga.rb CHANGED
@@ -34,6 +34,7 @@ if local_groonga_bin_dir.exist?
34
34
  prepend_path.call("PATH", File::PATH_SEPARATOR)
35
35
  end
36
36
 
37
+ require 'groonga/geo-point'
37
38
  require 'groonga/view-record'
38
39
  require 'groonga/record'
39
40
  require 'groonga/expression-builder'
@@ -90,6 +91,7 @@ module Groonga
90
91
  end
91
92
 
92
93
  require 'groonga/context'
94
+ require 'groonga/database'
93
95
  require 'groonga/patricia-trie'
94
96
  require 'groonga/dumper'
95
97
  require 'groonga/schema'
@@ -20,7 +20,9 @@ module Groonga
20
20
  class Builder
21
21
  class << self
22
22
  def escape_value(value)
23
- escaped_value = value.to_s.gsub(/"/, '\\"')
23
+ escaped_value = value.to_s.gsub(/[\\"]/) do |matched|
24
+ "\\#{matched}"
25
+ end
24
26
  "\"#{escaped_value}\""
25
27
  end
26
28
  end
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 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
+ module Groonga
19
+ class Database
20
+ # @return [Array<Groonga::Table] tables defined in the database
21
+ def tables
22
+ find_all do |object|
23
+ object.is_a?(Groonga::Table)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -18,6 +18,17 @@
18
18
  require 'stringio'
19
19
 
20
20
  module Groonga
21
+ module Dumper
22
+ module_function
23
+ def default_output
24
+ StringIO.new(utf8_string)
25
+ end
26
+
27
+ def utf8_string
28
+ ""
29
+ end
30
+ end
31
+
21
32
  # データベースの内容をgrn式形式の文字列として出力するクラス。
22
33
  class DatabaseDumper
23
34
  def initialize(options={})
@@ -27,7 +38,8 @@ module Groonga
27
38
  def dump
28
39
  options = @options.dup
29
40
  have_output = !@options[:output].nil?
30
- options[:output] ||= StringIO.new
41
+ options[:output] ||= Dumper.default_output
42
+ options[:error_output] ||= Dumper.default_output
31
43
  if options[:database].nil?
32
44
  options[:context] ||= Groonga::Context.default
33
45
  options[:database] = options[:context].database
@@ -50,7 +62,7 @@ module Groonga
50
62
  private
51
63
  def dump_plugins(options)
52
64
  plugin_paths = {}
53
- options[:database].each(:order_by => :id) do |object|
65
+ options[:database].each(each_options(:order_by => :id)) do |object|
54
66
  next unless object.is_a?(Groonga::Procedure)
55
67
  next if object.builtin?
56
68
  path = object.path
@@ -68,7 +80,7 @@ module Groonga
68
80
 
69
81
  def dump_tables(options)
70
82
  first_table = true
71
- options[:database].each(:order_by => :key) do |object|
83
+ options[:database].each(each_options(:order_by => :key)) do |object|
72
84
  next unless object.is_a?(Groonga::Table)
73
85
  next if object.size.zero?
74
86
  next if target_table?(options[:exclude_tables], object, false)
@@ -99,6 +111,10 @@ module Groonga
99
111
  name === table.name
100
112
  end
101
113
  end
114
+
115
+ def each_options(options)
116
+ {:ignore_missing_object => true}.merge(options)
117
+ end
102
118
  end
103
119
 
104
120
  # スキーマの内容をRubyスクリプトまたはgrn式形式の文字列と
@@ -118,7 +134,7 @@ module Groonga
118
134
 
119
135
  output = @options[:output]
120
136
  have_output = !output.nil?
121
- output ||= StringIO.new
137
+ output ||= Dumper.default_output
122
138
  result = syntax(database, output).dump
123
139
  if have_output
124
140
  result
@@ -154,23 +170,30 @@ module Groonga
154
170
  end
155
171
 
156
172
  def dump_schema
157
- @database.each(:order_by => :key) do |object|
173
+ dump_tables
174
+ dump_reference_columns
175
+ dump_index_columns
176
+ end
177
+
178
+ def dump_tables
179
+ each_options = {:order_by => :key, :ignore_missing_object => true}
180
+ @database.each(each_options) do |object|
158
181
  create_table(object) if object.is_a?(Groonga::Table)
159
182
  end
183
+ end
160
184
 
161
- @reference_columns.group_by do |column|
162
- column.table
163
- end.each do |table, columns|
185
+ def dump_reference_columns
186
+ group_columns(@reference_columns).each do |table, columns|
164
187
  change_table(table) do
165
188
  columns.each do |column|
166
189
  define_reference_column(table, column)
167
190
  end
168
191
  end
169
192
  end
193
+ end
170
194
 
171
- @index_columns.group_by do |column|
172
- column.table
173
- end.each do |table, columns|
195
+ def dump_index_columns
196
+ group_columns(@index_columns).each do |table, columns|
174
197
  change_table(table) do
175
198
  columns.each do |column|
176
199
  define_index_column(table, column)
@@ -192,6 +215,26 @@ module Groonga
192
215
  write("")
193
216
  end
194
217
 
218
+ def group_columns(columns)
219
+ grouped_columns = columns.group_by do |column|
220
+ column.table
221
+ end
222
+ sort_grouped_columns(grouped_columns)
223
+ end
224
+
225
+ def sort_grouped_columns(grouped_columns)
226
+ grouped_columns = grouped_columns.collect do |table, columns|
227
+ sorted_columns = columns.sort_by do |column|
228
+ column.local_name
229
+ end
230
+ [table, sorted_columns]
231
+ end
232
+ grouped_columns.sort_by do |table, columns|
233
+ _ = columns
234
+ table.name
235
+ end
236
+ end
237
+
195
238
  def table_separator
196
239
  write("\n")
197
240
  end
@@ -445,7 +488,8 @@ module Groonga
445
488
  @options = options
446
489
  @output = @options[:output]
447
490
  @have_output = !@output.nil?
448
- @output ||= StringIO.new
491
+ @output ||= Dumper.default_output
492
+ @error_output = @options[:error_output]
449
493
  end
450
494
 
451
495
  def dump
@@ -462,6 +506,11 @@ module Groonga
462
506
  @output.write(content)
463
507
  end
464
508
 
509
+ def error_write(content)
510
+ return if @error_output.nil?
511
+ @error_output.write(content)
512
+ end
513
+
465
514
  def dump_load_command
466
515
  write("load --table #{@table.name}\n")
467
516
  write("[\n")
@@ -482,17 +531,17 @@ module Groonga
482
531
  @table.each(:order_by => @options[:order_by]) do |record|
483
532
  write(",\n")
484
533
  values = columns.collect do |column|
485
- resolve_value(column[record.id])
534
+ resolve_value(record, column, column[record.id])
486
535
  end
487
536
  write(values.to_json)
488
537
  end
489
538
  end
490
539
 
491
- def resolve_value(value)
540
+ def resolve_value(record, column, value)
492
541
  case value
493
542
  when ::Array
494
543
  value.collect do |v|
495
- resolve_value(v)
544
+ resolve_value(record, column, v)
496
545
  end
497
546
  when Groonga::Record
498
547
  if value.support_key?
@@ -500,7 +549,7 @@ module Groonga
500
549
  else
501
550
  value = value.id
502
551
  end
503
- resolve_value(value)
552
+ resolve_value(record, column, value)
504
553
  when Time
505
554
  # TODO: groonga should support UTC format literal
506
555
  # value.utc.strftime("%Y-%m-%d %H:%M:%S.%6N")
@@ -510,8 +559,38 @@ module Groonga
510
559
  # doesn't accept null.
511
560
  ""
512
561
  else
513
- value
562
+ return value unless value.respond_to?(:valid_encoding?)
563
+ sanitized_value = ""
564
+ value = fix_encoding(value)
565
+ value.each_char do |char|
566
+ if char.valid_encoding?
567
+ sanitized_value << char
568
+ else
569
+ table_name = record.table.name
570
+ record_id = record.record_id
571
+ column_name = column.local_name
572
+ error_write("warning: ignore invalid encoding character: " +
573
+ "<#{table_name}[#{record_id}].#{column_name}>: " +
574
+ "<#{inspect_invalid_char(char)}>: " +
575
+ "before: <#{sanitized_value}>\n")
576
+ end
577
+ end
578
+ sanitized_value
579
+ end
580
+ end
581
+
582
+ def fix_encoding(value)
583
+ if value.encoding == ::Encoding::ASCII_8BIT
584
+ value.force_encoding(@table.context.ruby_encoding)
585
+ end
586
+ value
587
+ end
588
+
589
+ def inspect_invalid_char(char)
590
+ bytes_in_hex = char.bytes.collect do |byte|
591
+ "%#0x" % byte
514
592
  end
593
+ bytes_in_hex.join(" ")
515
594
  end
516
595
 
517
596
  def available_columns
@@ -0,0 +1,216 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 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
+ module Groonga
19
+ module GeoPointValueConverter
20
+ module_function
21
+
22
+ MSEC_PER_SEC = 1000 * 1000
23
+ N_SIGNIFICANT_DIGITS = (Math.log10(MSEC_PER_SEC) + 1).truncate
24
+ def msec_to_degree(msec)
25
+ degree_integer_part, degree_fraction_part = msec.divmod(3600 * 1000)
26
+ degree = degree_integer_part + (degree_fraction_part.to_f / (3600 * 1000))
27
+ round(degree, N_SIGNIFICANT_DIGITS)
28
+ end
29
+
30
+ def degree_to_msec(degree)
31
+ round(degree * 3600 * 1000)
32
+ end
33
+
34
+ def msec?(value)
35
+ value.is_a?(Integer)
36
+ end
37
+
38
+ def degree?(value)
39
+ value.is_a?(Float)
40
+ end
41
+
42
+ def to_degree(value)
43
+ return value if degree?(value)
44
+ msec_to_degree(value)
45
+ end
46
+
47
+ def to_msec(value)
48
+ return value if msec?(value)
49
+ degree_to_msec(value)
50
+ end
51
+
52
+ def parse(value)
53
+ if /\A[-+]?\d+\z/ =~ value
54
+ value.to_i
55
+ else
56
+ value.to_f
57
+ end
58
+ end
59
+
60
+ if Numeric.instance_method(:round).arity.zero?
61
+ def round(number, n_digits=0)
62
+ if n_digits.zero?
63
+ number.round
64
+ else
65
+ base = 10 ** n_digits
66
+ (number * base).round / base.to_f
67
+ end
68
+ end
69
+ else
70
+ def round(number, n_digits=0)
71
+ number.round(n_digits)
72
+ end
73
+ end
74
+ end
75
+
76
+ class GeoPoint
77
+ class << self
78
+ def parse(string)
79
+ # TODO: validation
80
+ latitude, longitude = string.split(/[x,]/, 2)
81
+ new(GeoPointValueConverter.parse(latitude),
82
+ GeoPointValueConverter.parse(longitude))
83
+ end
84
+ end
85
+
86
+ attr_accessor :latitude, :longitude
87
+ # TODO: write document
88
+ # @overload initialize(geo_point_in_string)
89
+ # @overload initialize(latitude, longitude)
90
+ def initialize(*arguments)
91
+ if arguments.size == 1
92
+ if arguments.first.is_a?(String)
93
+ geo_point = self.class.parse(arguments.first)
94
+ else
95
+ geo_point = arguments.first
96
+ geo_point = coerce(geo_point)
97
+ end
98
+ @latitude = geo_point.latitude
99
+ @longitude = geo_point.longitude
100
+ else
101
+ # TODO: check # of arguments
102
+ @latitude, @longitude, = arguments
103
+ end
104
+ end
105
+
106
+ def degree?
107
+ GeoPointValueConverter.degree?(latitude) and
108
+ GeoPointValueConverter.degree?(longitude)
109
+ end
110
+
111
+ def msec?
112
+ GeoPointValueConverter.msec?(latitude) and
113
+ GeoPointValueConverter.msec?(longitude)
114
+ end
115
+
116
+ def to_degree
117
+ return self if degree?
118
+ self.class.new(GeoPointValueConverter.to_degree(latitude),
119
+ GeoPointValueConverter.to_degree(longitude))
120
+ end
121
+
122
+ def to_msec
123
+ return self if msec?
124
+ self.class.new(GeoPointValueConverter.to_msec(latitude),
125
+ GeoPointValueConverter.to_msec(longitude))
126
+ end
127
+
128
+ def to_s
129
+ "#{latitude}x#{longitude}"
130
+ end
131
+
132
+ def inspect
133
+ "#<#{self.class} #{to_s}>"
134
+ end
135
+
136
+ def ==(other)
137
+ case other
138
+ when String
139
+ to_s == otehr
140
+ when GeoPoint
141
+ normalized_self = to_msec
142
+ normalized_other = coerce(other).to_msec
143
+ [normalized_self.latitude, normalized_self.longitude] ==
144
+ [normalized_other.latitude, normalized_other.longitude]
145
+ else
146
+ false
147
+ end
148
+ end
149
+ end
150
+
151
+ class TokyoGeoPoint < GeoPoint
152
+ def to_tokyo
153
+ self
154
+ end
155
+
156
+ # TODO: write document
157
+ #
158
+ # TokyoGeoPoint <-> WGS84GeoPoint is based on
159
+ # http://www.jalan.net/jw/jwp0200/jww0203.do
160
+ #
161
+ # jx: longitude in degree in Tokyo Geodetic System.
162
+ # jy: latitude in degree in Tokyo Geodetic System.
163
+ # wx: longitude in degree in WGS 84.
164
+ # wy: latitude in degree in WGS 84.
165
+ #
166
+ # jy = wy * 1.000106961 - wx * 0.000017467 - 0.004602017
167
+ # jx = wx * 1.000083049 + wy * 0.000046047 - 0.010041046
168
+ #
169
+ # wy = jy - jy * 0.00010695 + jx * 0.000017464 + 0.0046017
170
+ # wx = jx - jy * 0.000046038 - jx * 0.000083043 + 0.010040
171
+ def to_wgs84
172
+ in_degree = to_degree
173
+ wgs84_latitude_in_degree =
174
+ in_degree.latitude -
175
+ in_degree.latitude * 0.00010695 +
176
+ in_degree.longitude * 0.000017464 +
177
+ 0.0046017
178
+ wgs84_longitude_in_degree =
179
+ in_degree.longitude -
180
+ in_degree.latitude * 0.000046038 -
181
+ in_degree.longitude * 0.000083043 +
182
+ 0.010040
183
+ WGS84GeoPoint.new(wgs84_latitude_in_degree, wgs84_longitude_in_degree)
184
+ end
185
+
186
+ private
187
+ def coerce(other_geo_point)
188
+ other_geo_point.to_tokyo
189
+ end
190
+ end
191
+
192
+ class WGS84GeoPoint < GeoPoint
193
+ # @see TokyoGeoPoint#to_wgs84
194
+ def to_tokyo
195
+ in_degree = to_degree
196
+ tokyo_latitude_in_degree =
197
+ in_degree.latitude * 1.000106961 -
198
+ in_degree.longitude * 0.000017467 -
199
+ 0.004602017
200
+ tokyo_longitude_in_degree =
201
+ in_degree.longitude * 1.000083049 +
202
+ in_degree.latitude * 0.000046047 -
203
+ 0.010041046
204
+ TokyoGeoPoint.new(tokyo_latitude_in_degree, tokyo_longitude_in_degree)
205
+ end
206
+
207
+ def to_wgs84
208
+ self
209
+ end
210
+
211
+ private
212
+ def coerce(other_geo_point)
213
+ other_geo_point.to_wgs84
214
+ end
215
+ end
216
+ end