dm-groonga-adapter 0.1.0.pre → 0.1.0.pre2
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.
- data/Rakefile +1 -1
- data/VERSION.yml +2 -2
- data/dm-groonga-adapter.gemspec +4 -3
- data/lib/groonga_adapter/adapter.rb +4 -0
- data/lib/groonga_adapter/jsonparser.rb +336 -0
- data/lib/groonga_adapter/local_index.rb +2 -0
- data/lib/groonga_adapter/remote_index.rb +71 -33
- data/lib/groonga_adapter.rb +1 -0
- data/spec/shared/search_example.rb +13 -2
- metadata +5 -4
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ begin
|
|
8
8
|
gem.summary = %Q{datamapper adapter for groonga search engine}
|
9
9
|
gem.description = gem.summary
|
10
10
|
gem.email = "hello@hryk.info"
|
11
|
-
gem.homepage = "http://github.com/
|
11
|
+
gem.homepage = "http://hryk.github.com/dm-groonga-adapter"
|
12
12
|
gem.authors = ["hiroyuki"]
|
13
13
|
|
14
14
|
gem.add_dependency "groonga", ">= 0.9"
|
data/VERSION.yml
CHANGED
data/dm-groonga-adapter.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dm-groonga-adapter}
|
8
|
-
s.version = "0.1.0.
|
8
|
+
s.version = "0.1.0.pre2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["hiroyuki"]
|
12
|
-
s.date = %q{2010-04-
|
12
|
+
s.date = %q{2010-04-15}
|
13
13
|
s.description = %q{datamapper adapter for groonga search engine}
|
14
14
|
s.email = %q{hello@hryk.info}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
|
|
27
27
|
"examples/basic.rb",
|
28
28
|
"lib/groonga_adapter.rb",
|
29
29
|
"lib/groonga_adapter/adapter.rb",
|
30
|
+
"lib/groonga_adapter/jsonparser.rb",
|
30
31
|
"lib/groonga_adapter/local_index.rb",
|
31
32
|
"lib/groonga_adapter/model_ext.rb",
|
32
33
|
"lib/groonga_adapter/remote_index.rb",
|
@@ -42,7 +43,7 @@ Gem::Specification.new do |s|
|
|
42
43
|
"spec/specs/remote_result_spec.rb",
|
43
44
|
"spec/specs/search_spec.rb"
|
44
45
|
]
|
45
|
-
s.homepage = %q{http://github.com/
|
46
|
+
s.homepage = %q{http://hryk.github.com/dm-groonga-adapter}
|
46
47
|
s.rdoc_options = ["--charset=UTF-8"]
|
47
48
|
s.require_paths = ["lib"]
|
48
49
|
s.rubygems_version = %q{1.3.6}
|
@@ -3,6 +3,10 @@ module DataMapper
|
|
3
3
|
module Adapters
|
4
4
|
class GroongaAdapter < AbstractAdapter
|
5
5
|
|
6
|
+
# Initialize adapter
|
7
|
+
#
|
8
|
+
# @param [String] name
|
9
|
+
# @param [Hash] options the options passed to RemoteIndex or LocalIndex.
|
6
10
|
def initialize(name, options)
|
7
11
|
super
|
8
12
|
Groonga::Context.default = nil # Reset Groonga::Context
|
@@ -0,0 +1,336 @@
|
|
1
|
+
# = Simple JSON parser & builder
|
2
|
+
#
|
3
|
+
# Author:: Chihiro Ito
|
4
|
+
# License:: Public domain (unlike other files)
|
5
|
+
# Support:: http://groups.google.com/group/webos-goodies/
|
6
|
+
#
|
7
|
+
# This file contains two simple JSON processing classes. JsonParser
|
8
|
+
# converts a JSON string to an array or a hash. JsonBuilder performs
|
9
|
+
# vice versa. These classes are standard compliant and are designed for
|
10
|
+
# stability and reliability. Especially JsonParser has UTF-8 validation
|
11
|
+
# functionality so you can avoid some kind of security attack.
|
12
|
+
|
13
|
+
require 'strscan'
|
14
|
+
require 'json' if RUBY_VERSION >= '1.9.0'
|
15
|
+
|
16
|
+
|
17
|
+
# = Simple JSON parser
|
18
|
+
#
|
19
|
+
# This class converts a JSON string to an array or a hash. If *json_str*
|
20
|
+
# contains a JSON form string, you can convert it like below.
|
21
|
+
#
|
22
|
+
# ruby_obj = JsonParser.new.parse(json_str)
|
23
|
+
#
|
24
|
+
# If *json_str* has one or more invalid UTF-8 sequence, JsonParser throws
|
25
|
+
# exception by default. You can change this behavior to replacing with an
|
26
|
+
# arbitrary unicode character. See below for details.
|
27
|
+
class JsonParser
|
28
|
+
|
29
|
+
#:stopdoc:
|
30
|
+
RUBY19 = RUBY_VERSION >= '1.9.0'
|
31
|
+
Debug = false
|
32
|
+
Name = 'JsonParser'
|
33
|
+
ERR_IllegalSyntax = "[#{Name}] Syntax error"
|
34
|
+
ERR_IllegalUnicode = "[#{Name}] Illegal unicode sequence"
|
35
|
+
|
36
|
+
StringRegex = /\s*"((?:\\.|[^"\\])*)"/n
|
37
|
+
ValueRegex = /\s*(?:
|
38
|
+
(true)|(false)|(null)| # 1:true, 2:false, 3:null
|
39
|
+
(?:\"((?:\\.|[^\"\\])*)\")| # 4:String
|
40
|
+
([-+]?\d+\.\d+(?:[eE][-+]?\d+)?)| # 5:Float
|
41
|
+
([-+]?\d+)| # 6:Integer
|
42
|
+
(\{)|(\[))/xn # 7:Hash, 8:Array
|
43
|
+
#:startdoc:
|
44
|
+
|
45
|
+
# Create a new instance of JsonParser. *options* can contain these values.
|
46
|
+
# [:validation]
|
47
|
+
# If set to false, UTF-8 validation is disabled. true by default.
|
48
|
+
# [:surrogate]
|
49
|
+
# If set to false, surrogate pair support is disabled. true by default.
|
50
|
+
# [:malformed_chr]
|
51
|
+
# An invalid sequence in JSON string will be replaced with this value.
|
52
|
+
# If set to nil, An exception will be thrown in this situation.
|
53
|
+
# nil by default.
|
54
|
+
# [:compatible]
|
55
|
+
# If set to true, Ruby1.9's JSON module is never used. false by default.
|
56
|
+
def initialize(options = {})
|
57
|
+
@default_validation = options.has_key?(:validation) ? options[:validation] : true
|
58
|
+
@default_surrogate = options.has_key?(:surrogate) ? options[:surrogate] : true
|
59
|
+
@default_malformed_chr = options.has_key?(:malformed_chr) ? options[:malformed_chr] : nil
|
60
|
+
@default_compatible = options.has_key?(:compatible) ? options[:compatible] : false
|
61
|
+
end
|
62
|
+
|
63
|
+
# Convert *str* to an array or hash.
|
64
|
+
# [str]
|
65
|
+
# A JSON form string. This must be encoded using UTF-8.
|
66
|
+
# [options]
|
67
|
+
# Same as new.
|
68
|
+
def parse(str, options = {})
|
69
|
+
@enable_validation = options.has_key?(:validation) ? options[:validation] : @default_validation
|
70
|
+
@enable_surrogate = options.has_key?(:surrogate) ? options[:surrogate] : @default_surrogate
|
71
|
+
@malformed_chr = options.has_key?(:malformed_chr) ? options[:malformed_chr] : @default_malformed_chr
|
72
|
+
@compatible = options.has_key?(:compatible) ? options[:compatible] : @default_compatible
|
73
|
+
@malformed_chr = @malformed_chr[0].ord if String === @malformed_chr
|
74
|
+
if RUBY19
|
75
|
+
str = (str.encode('UTF-8') rescue str.dup)
|
76
|
+
if @enable_validation && !@malformed_chr
|
77
|
+
raise err_msg(ERR_IllegalUnicode) unless str.valid_encoding?
|
78
|
+
@enable_validation = false
|
79
|
+
end
|
80
|
+
if !@enable_validation && @enable_surrogate && !@malformed_chr && !@compatible
|
81
|
+
begin
|
82
|
+
return JSON.parse(str, :max_nesting => false)
|
83
|
+
rescue JSON::JSONError => e
|
84
|
+
exception = RuntimeError.new(e.message)
|
85
|
+
exception.set_backtrace(e.backtrace)
|
86
|
+
raise exception
|
87
|
+
end
|
88
|
+
end
|
89
|
+
str.force_encoding('ASCII-8BIT')
|
90
|
+
end
|
91
|
+
@scanner = StringScanner.new(str)
|
92
|
+
obj = case get_symbol[0]
|
93
|
+
when ?{ then parse_hash
|
94
|
+
when ?[ then parse_array
|
95
|
+
else raise err_msg(ERR_IllegalSyntax)
|
96
|
+
end
|
97
|
+
@scanner = nil
|
98
|
+
obj
|
99
|
+
end
|
100
|
+
|
101
|
+
private #---------------------------------------------------------
|
102
|
+
|
103
|
+
def validate_string(str, malformed_chr = nil)
|
104
|
+
code = 0
|
105
|
+
rest = 0
|
106
|
+
range = nil
|
107
|
+
ucs = []
|
108
|
+
str.each_byte do |c|
|
109
|
+
if rest <= 0
|
110
|
+
case c
|
111
|
+
when 0x01..0x7f then rest = 0 ; ucs << c
|
112
|
+
when 0xc0..0xdf then rest = 1 ; code = c & 0x1f ; range = 0x00080..0x0007ff
|
113
|
+
when 0xe0..0xef then rest = 2 ; code = c & 0x0f ; range = 0x00800..0x00ffff
|
114
|
+
when 0xf0..0xf7 then rest = 3 ; code = c & 0x07 ; range = 0x10000..0x10ffff
|
115
|
+
else ucs << handle_malformed_chr(malformed_chr)
|
116
|
+
end
|
117
|
+
elsif (0x80..0xbf) === c
|
118
|
+
code = (code << 6) | (c & 0x3f)
|
119
|
+
if (rest -= 1) <= 0
|
120
|
+
if !(range === code) || (0xd800..0xdfff) === code
|
121
|
+
code = handle_malformed_chr(malformed_chr)
|
122
|
+
end
|
123
|
+
ucs << code
|
124
|
+
end
|
125
|
+
else
|
126
|
+
ucs << handle_malformed_chr(malformed_chr)
|
127
|
+
rest = 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
ucs << handle_malformed_chr(malformed_chr) if rest > 0
|
131
|
+
ucs.pack('U*')
|
132
|
+
end
|
133
|
+
|
134
|
+
def handle_malformed_chr(chr)
|
135
|
+
raise err_msg(ERR_IllegalUnicode) unless chr
|
136
|
+
chr
|
137
|
+
end
|
138
|
+
|
139
|
+
def err_msg(err)
|
140
|
+
err + (Debug ? " #{@scanner.string[[0, @scanner.pos - 8].max,16].inspect}" : "")
|
141
|
+
end
|
142
|
+
|
143
|
+
def unescape_string(str)
|
144
|
+
str = str.gsub(/\\(["\\\/bfnrt])/n) do
|
145
|
+
$1.tr('"\\/bfnrt', "\"\\/\b\f\n\r\t")
|
146
|
+
end.gsub(/(\\u[0-9a-fA-F]{4})+/n) do |matched|
|
147
|
+
seq = matched.scan(/\\u([0-9a-fA-F]{4})/n).flatten.map { |c| c.hex }
|
148
|
+
if @enable_surrogate
|
149
|
+
seq.each_index do |index|
|
150
|
+
if seq[index] && (0xd800..0xdbff) === seq[index]
|
151
|
+
n = index + 1
|
152
|
+
raise err_msg(ERR_IllegalUnicode) unless seq[n] && 0xdc00..0xdfff === seq[n]
|
153
|
+
seq[index] = 0x10000 + ((seq[index] & 0x03ff) << 10) + (seq[n] & 0x03ff)
|
154
|
+
seq[n] = nil
|
155
|
+
end
|
156
|
+
end.compact!
|
157
|
+
end
|
158
|
+
seq.pack('U*')
|
159
|
+
end
|
160
|
+
str = validate_string(str, @malformed_chr) if @enable_validation
|
161
|
+
RUBY19 ? str.force_encoding('UTF-8') : str
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_symbol
|
165
|
+
raise err_msg(ERR_IllegalSyntax) unless @scanner.scan(/\s*(.)/n)
|
166
|
+
@scanner[1]
|
167
|
+
end
|
168
|
+
|
169
|
+
def peek_symbol
|
170
|
+
@scanner.match?(/\s*(.)/n) ? @scanner[1] : nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def parse_string
|
174
|
+
raise err_msg(ERR_IllegalSyntax) unless @scanner.scan(StringRegex)
|
175
|
+
unescape_string(@scanner[1])
|
176
|
+
end
|
177
|
+
|
178
|
+
def parse_value
|
179
|
+
raise err_msg(ERR_IllegalSyntax) unless @scanner.scan(ValueRegex)
|
180
|
+
case
|
181
|
+
when @scanner[1] then true
|
182
|
+
when @scanner[2] then false
|
183
|
+
when @scanner[3] then nil
|
184
|
+
when @scanner[4] then unescape_string(@scanner[4])
|
185
|
+
when @scanner[5] then @scanner[5].to_f
|
186
|
+
when @scanner[6] then @scanner[6].to_i
|
187
|
+
when @scanner[7] then parse_hash
|
188
|
+
when @scanner[8] then parse_array
|
189
|
+
else raise err_msg(ERR_IllegalSyntax)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def parse_hash
|
194
|
+
obj = {}
|
195
|
+
if peek_symbol[0] == ?} then get_symbol ; return obj ; end
|
196
|
+
while true
|
197
|
+
index = parse_string
|
198
|
+
raise err_msg(ERR_IllegalSyntax) unless get_symbol[0] == ?:
|
199
|
+
value = parse_value
|
200
|
+
obj[index] = value
|
201
|
+
case get_symbol[0]
|
202
|
+
when ?} then return obj
|
203
|
+
when ?, then next
|
204
|
+
else raise err_msg(ERR_IllegalSyntax)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_array
|
210
|
+
obj = []
|
211
|
+
if peek_symbol[0] == ?] then get_symbol ; return obj ; end
|
212
|
+
while true
|
213
|
+
obj << parse_value
|
214
|
+
case get_symbol[0]
|
215
|
+
when ?] then return obj
|
216
|
+
when ?, then next
|
217
|
+
else raise err_msg(ERR_IllegalSyntax)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
# = Simple JSON builder
|
225
|
+
#
|
226
|
+
# This class converts an Ruby object to a JSON string. you can convert
|
227
|
+
# *ruby_obj* like below.
|
228
|
+
#
|
229
|
+
# json_str = JsonBuilder.new.build(ruby_obj)
|
230
|
+
#
|
231
|
+
# *ruby_obj* must satisfy these conditions.
|
232
|
+
# - It must support to_s method, otherwise must be an array, a hash or nil.
|
233
|
+
# - All keys of a hash must support to_s method.
|
234
|
+
# - All values of an array or a hash must satisfy all conditions mentioned above.
|
235
|
+
#
|
236
|
+
# If the *ruby_obj* is not an array or a hash, it will be converted to an array
|
237
|
+
# with a single element.
|
238
|
+
class JsonBuilder
|
239
|
+
|
240
|
+
#:stopdoc:
|
241
|
+
RUBY19 = RUBY_VERSION >= '1.9.0'
|
242
|
+
Name = 'JsonBuilder'
|
243
|
+
ERR_NestIsTooDeep = "[#{Name}] Array / Hash nested too deep."
|
244
|
+
ERR_NaN = "[#{Name}] NaN and Infinite are not permitted in JSON."
|
245
|
+
#:startdoc:
|
246
|
+
|
247
|
+
# Create a new instance of JsonBuilder. *options* can contain these values.
|
248
|
+
# [:max_nest]
|
249
|
+
# If Array / Hash is nested more than this value, an exception would be thrown.
|
250
|
+
# 19 by default.
|
251
|
+
# [:nan]
|
252
|
+
# NaN is replaced with this value. If nil or false, an exception will be thrown.
|
253
|
+
# nil by default.
|
254
|
+
def initialize(options = {})
|
255
|
+
@default_max_nest = options.has_key?(:max_nest) ? options[:max_nest] : 19
|
256
|
+
@default_nan = options.has_key?(:nan) ? options[:nan] : nil
|
257
|
+
end
|
258
|
+
|
259
|
+
# Convert *obj* to a JSON form string.
|
260
|
+
# [obj]
|
261
|
+
# A ruby object. this object must satisfy all conditions mentioned above.
|
262
|
+
# [options]
|
263
|
+
# Same as new.
|
264
|
+
def build(obj, options = {})
|
265
|
+
@max_nest = options.has_key?(:max_nest) ? options[:max_nest] : @default_max_nest
|
266
|
+
@nan = options.has_key?(:nan) ? options[:nan] : @default_nan
|
267
|
+
if RUBY19 && !@nan
|
268
|
+
begin
|
269
|
+
JSON.generate(obj, :max_nesting => @max_nest, :check_circular => false)
|
270
|
+
rescue JSON::JSONError => e
|
271
|
+
exception = RuntimeError.new(e.message)
|
272
|
+
exception.set_backtrace(e.backtrace)
|
273
|
+
raise exception
|
274
|
+
end
|
275
|
+
else
|
276
|
+
case obj
|
277
|
+
when Array then build_array(obj, 0)
|
278
|
+
when Hash then build_object(obj, 0)
|
279
|
+
else build_array([obj], 0)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
private #---------------------------------------------------------
|
285
|
+
|
286
|
+
ESCAPE_CONVERSION = {
|
287
|
+
'"' => '\"', '\\' => '\\\\', '/' => '/', "\x08" => '\b',
|
288
|
+
"\x0c" => '\f', "\x0a" => '\n', "\x0d" => '\r', "\x09" => '\t'
|
289
|
+
}
|
290
|
+
if RUBY19
|
291
|
+
def escape(str)
|
292
|
+
str = str.to_s.encode('UTF-8')
|
293
|
+
str.force_encoding('ASCII-8BIT')
|
294
|
+
str = str.gsub(/[^\x20-\x21\x23-\x2e\x30-\x5b\x5d-\xff]/n) do |chr|
|
295
|
+
escaped = ESCAPE_CONVERSION[chr]
|
296
|
+
escaped = sprintf("\\u%04X", chr[0].ord) unless escaped
|
297
|
+
escaped
|
298
|
+
end
|
299
|
+
str.force_encoding('UTF-8')
|
300
|
+
"\"#{str}\""
|
301
|
+
end
|
302
|
+
else
|
303
|
+
def escape(str)
|
304
|
+
str = str.gsub(/[^\x20-\x21\x23-\x2e\x30-\x5b\x5d-\xff]/n) do |chr|
|
305
|
+
escaped = ESCAPE_CONVERSION[chr]
|
306
|
+
escaped = sprintf("\\u%04x", chr[0]) unless escaped
|
307
|
+
escaped
|
308
|
+
end
|
309
|
+
"\\\"#{str}\\\""
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def build_value(obj, level)
|
314
|
+
case obj
|
315
|
+
when Integer, TrueClass, FalseClass then obj.to_s
|
316
|
+
when Float then raise ERR_NaN unless obj.finite? || (obj = @nan) ; obj.to_s
|
317
|
+
when NilClass then 'null'
|
318
|
+
when Array then build_array(obj, level + 1)
|
319
|
+
when Hash then build_object(obj, level + 1)
|
320
|
+
else escape(obj)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def build_array(obj, level)
|
325
|
+
raise ERR_NestIsTooDeep if level >= @max_nest
|
326
|
+
'[' + obj.map { |item| build_value(item, level) }.join(',') + ']'
|
327
|
+
end
|
328
|
+
|
329
|
+
def build_object(obj, level)
|
330
|
+
raise ERR_NestIsTooDeep if level >= @max_nest
|
331
|
+
'{' + obj.map do |item|
|
332
|
+
"#{build_value(item[0].to_s,level)}:#{build_value(item[1],level)}"
|
333
|
+
end.join(',') + '}'
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'nkf'
|
2
3
|
|
3
4
|
module DataMapper
|
4
5
|
module Adapters
|
@@ -9,6 +10,7 @@ module DataMapper
|
|
9
10
|
def initialize(options)
|
10
11
|
@context = Groonga::Context.default
|
11
12
|
@context.connect(:host => options[:host], :port => options[:port])
|
13
|
+
@jsonbuilder = JsonBuilder.new
|
12
14
|
# request "status" # <- TODO check connection with status command
|
13
15
|
end
|
14
16
|
|
@@ -17,8 +19,8 @@ module DataMapper
|
|
17
19
|
doc_id = doc[:id] #doc.delete(:id)
|
18
20
|
record = []
|
19
21
|
record << doc.update("_key" => doc_id)
|
20
|
-
|
21
|
-
res = request "load --table #{table_name} --values #{
|
22
|
+
values = escape_value record
|
23
|
+
res = request "load --table #{table_name} --values #{values}"
|
22
24
|
result = GroongaResult::Count.new res
|
23
25
|
if result.success? && result.count > 0
|
24
26
|
return doc
|
@@ -37,9 +39,20 @@ module DataMapper
|
|
37
39
|
# [offset [limit [drilldown [drilldown_sortby [drilldown_output_columns
|
38
40
|
# [drilldown_offset [drilldown_limit [output_type]]]]]]]]]]]]]]
|
39
41
|
def search(table_name, grn_query, grn_sort=[], options={})
|
42
|
+
# In case of columns contain long string, groonga return 10 results with no-limit..
|
43
|
+
# --limit #{num} option is needed to get all result.
|
44
|
+
# because of this, current remote_index request at twice.
|
40
45
|
sort_by, offset, limit = parse_grn_sort grn_sort
|
41
|
-
remote_query
|
42
|
-
remote_sort_by = (sort_by.empty?)
|
46
|
+
remote_query = (grn_query.empty?) ? "" : "--query #{grn_query}"
|
47
|
+
remote_sort_by = (sort_by.empty?) ? "" : "--sortby #{sort_by}"
|
48
|
+
# 1st request for get count.
|
49
|
+
|
50
|
+
if limit.nil? or limit == -1
|
51
|
+
count = GroongaResult::List.new(request "select #{table_name} #{remote_query} #{remote_sort_by} --output_columns _id")
|
52
|
+
limit = count.size
|
53
|
+
end
|
54
|
+
|
55
|
+
# 2nd request for get ids.
|
43
56
|
res = request "select #{table_name} #{remote_query} #{remote_sort_by} --offset #{offset} --limit #{limit}"
|
44
57
|
list = GroongaResult::List.new res
|
45
58
|
if list.success?
|
@@ -109,12 +122,66 @@ module DataMapper
|
|
109
122
|
|
110
123
|
protected
|
111
124
|
|
125
|
+
def escape_value record
|
126
|
+
record.map! {|r|
|
127
|
+
tmp = {}
|
128
|
+
r.each {|k,v|
|
129
|
+
if !v.nil?
|
130
|
+
if v.is_a? String
|
131
|
+
tmp[k] = NKF.nkf('-wZ1',v)
|
132
|
+
tmp[k].gsub!(/\\/, '\\\\\\')
|
133
|
+
tmp[k].gsub!(/"/) { |c| "\\\\\\#{c}" }
|
134
|
+
else
|
135
|
+
tmp[k] = v.to_s
|
136
|
+
end
|
137
|
+
end
|
138
|
+
}
|
139
|
+
tmp
|
140
|
+
}
|
141
|
+
json = @jsonbuilder.build record
|
142
|
+
json.gsub!(/'|\s|\(|\)|\t/) { |c| "\\#{c}" }
|
143
|
+
json
|
144
|
+
end
|
145
|
+
|
112
146
|
def err_code(res)
|
113
147
|
return if res.nil?
|
114
148
|
code = res[0][0]
|
115
149
|
code
|
116
150
|
end
|
117
151
|
|
152
|
+
def trans_type(dmtype)
|
153
|
+
case dmtype.to_s
|
154
|
+
when 'String'
|
155
|
+
return 'ShortText'
|
156
|
+
when 'Text'
|
157
|
+
return 'Text'
|
158
|
+
when 'Float'
|
159
|
+
return 'Float'
|
160
|
+
when 'Bool'
|
161
|
+
return 'Bool'
|
162
|
+
when 'Boolean'
|
163
|
+
return 'Bool'
|
164
|
+
when 'Integer'
|
165
|
+
return 'Int32'
|
166
|
+
when 'BigDecimal'
|
167
|
+
return 'Int64'
|
168
|
+
when 'Time'
|
169
|
+
return 'Time'
|
170
|
+
when /^DataMapper::Types::(.+)$/
|
171
|
+
case $1
|
172
|
+
when "Boolean"
|
173
|
+
return 'Bool'
|
174
|
+
when "Serial"
|
175
|
+
return 'Int32'
|
176
|
+
when "Text"
|
177
|
+
return "Text"
|
178
|
+
end
|
179
|
+
else
|
180
|
+
return 'ShortText'
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
118
185
|
def create_term_table(table_name, key_prop="ShortText", tokenizer="TokenBigram")
|
119
186
|
res = request "table_create #{table_name} TABLE_PAT_KEY|KEY_NORMALIZE #{key_prop} Void #{tokenizer}"
|
120
187
|
throw "Fale to create term table." unless err_code(res) == 0 || err_code(res) == -22
|
@@ -159,35 +226,6 @@ module DataMapper
|
|
159
226
|
[ sort_str, options[:offset], options[:limit] ]
|
160
227
|
end
|
161
228
|
|
162
|
-
def trans_type(dmtype)
|
163
|
-
case dmtype.to_s
|
164
|
-
when 'String'
|
165
|
-
return 'ShortText'
|
166
|
-
when 'Text'
|
167
|
-
return 'LongText'
|
168
|
-
when 'Float'
|
169
|
-
return 'Float'
|
170
|
-
when 'Bool'
|
171
|
-
return 'Bool'
|
172
|
-
when 'Boolean'
|
173
|
-
return 'Bool'
|
174
|
-
when 'Integer'
|
175
|
-
return 'Int32'
|
176
|
-
when 'BigDecimal'
|
177
|
-
return 'Int64'
|
178
|
-
when 'Time'
|
179
|
-
return 'Time'
|
180
|
-
when /^DataMapper::Types::(.+)$/
|
181
|
-
case $1
|
182
|
-
when "Boolean"
|
183
|
-
return 'Bool'
|
184
|
-
when "Serial"
|
185
|
-
return 'Int32'
|
186
|
-
end
|
187
|
-
else
|
188
|
-
return 'ShortText'
|
189
|
-
end
|
190
|
-
end
|
191
229
|
end # class GroongaAdapter::RemoteIndex
|
192
230
|
end # module Adapters
|
193
231
|
end # module DataMapper
|
data/lib/groonga_adapter.rb
CHANGED
@@ -3,13 +3,13 @@ shared_examples_for 'as is_search plugin' do
|
|
3
3
|
before(:each) do
|
4
4
|
DataMapper.setup(:default, "sqlite3::memory:")
|
5
5
|
DataMapper.setup(:search, "groonga://#{index_path}")
|
6
|
-
DataMapper::Logger.new($stderr, :debug)
|
6
|
+
# DataMapper::Logger.new($stderr, :debug)
|
7
7
|
Object.send(:remove_const, :Image) if defined?(Image)
|
8
8
|
class ::Image
|
9
9
|
include DataMapper::Resource
|
10
10
|
property :id, Serial
|
11
11
|
property :title, String
|
12
|
-
|
12
|
+
property :description, Text
|
13
13
|
is :searchable # this defaults to :search repository, you could also do
|
14
14
|
end
|
15
15
|
|
@@ -61,4 +61,15 @@ shared_examples_for 'as is_search plugin' do
|
|
61
61
|
# Story.fulltext_search("John").should == [story, story2] # <--- Crash on local index.
|
62
62
|
Story.fulltext_search("author:@John").should == [story, story2]
|
63
63
|
end
|
64
|
+
|
65
|
+
it 'should return all result when there is more than 10 result' do
|
66
|
+
suffix = (0..10).map {|i| "long description." }
|
67
|
+
(0..157).each do |num|
|
68
|
+
img = Image.create(:title => "Picture_#{num}", :description => "#{suffix}")
|
69
|
+
img.save
|
70
|
+
end
|
71
|
+
results = Image.search(:title.like => "Picture")
|
72
|
+
results.size.should == 158
|
73
|
+
end
|
64
74
|
end
|
75
|
+
|
metadata
CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
|
|
6
6
|
- 0
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.1.0.
|
9
|
+
- pre2
|
10
|
+
version: 0.1.0.pre2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- hiroyuki
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-04-
|
18
|
+
date: 2010-04-15 00:00:00 +09:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -105,6 +105,7 @@ files:
|
|
105
105
|
- examples/basic.rb
|
106
106
|
- lib/groonga_adapter.rb
|
107
107
|
- lib/groonga_adapter/adapter.rb
|
108
|
+
- lib/groonga_adapter/jsonparser.rb
|
108
109
|
- lib/groonga_adapter/local_index.rb
|
109
110
|
- lib/groonga_adapter/model_ext.rb
|
110
111
|
- lib/groonga_adapter/remote_index.rb
|
@@ -120,7 +121,7 @@ files:
|
|
120
121
|
- spec/specs/remote_result_spec.rb
|
121
122
|
- spec/specs/search_spec.rb
|
122
123
|
has_rdoc: true
|
123
|
-
homepage: http://github.com/
|
124
|
+
homepage: http://hryk.github.com/dm-groonga-adapter
|
124
125
|
licenses: []
|
125
126
|
|
126
127
|
post_install_message:
|