dm-groonga-adapter 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +26 -0
- data/LICENSE +14 -0
- data/README.rdoc +43 -0
- data/Rakefile +52 -0
- data/VERSION.yml +5 -0
- data/dm-groonga-adapter.gemspec +85 -0
- data/examples/basic.rb +45 -0
- data/lib/groonga_adapter/adapter.rb +207 -0
- data/lib/groonga_adapter/local_index.rb +191 -0
- data/lib/groonga_adapter/model_ext.rb +12 -0
- data/lib/groonga_adapter/remote_index.rb +250 -0
- data/lib/groonga_adapter/remote_result.rb +101 -0
- data/lib/groonga_adapter/repository_ext.rb +13 -0
- data/lib/groonga_adapter/unicode_ext.rb +17 -0
- data/lib/groonga_adapter.rb +9 -0
- data/spec/rcov.opts +6 -0
- data/spec/shared/adapter_example.rb +53 -0
- data/spec/shared/search_example.rb +64 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/specs/adapter_spec.rb +25 -0
- data/spec/specs/remote_result_spec.rb +100 -0
- data/spec/specs/search_spec.rb +26 -0
- metadata +161 -0
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Adapters
|
5
|
+
class GroongaAdapter::RemoteIndex
|
6
|
+
attr_accessor :logger
|
7
|
+
attr_accessor :context
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@context = Groonga::Context.default
|
11
|
+
@context.connect(:host => options[:host], :port => options[:port])
|
12
|
+
# request "status" # <- TODO check connection with status command
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(table_name, doc)
|
16
|
+
return unless exist_table(table_name)
|
17
|
+
doc_id = doc[:id] #doc.delete(:id)
|
18
|
+
record = []
|
19
|
+
record << doc.update("_key" => doc_id)
|
20
|
+
json = JSON.generate record
|
21
|
+
res = request "load --table #{table_name} --values #{Unicode.unescape(json.gsub(/"/, '\"').gsub(/\s/, '\ '))}"
|
22
|
+
result = GroongaResult::Count.new res
|
23
|
+
if result.success? && result.count > 0
|
24
|
+
return doc
|
25
|
+
else
|
26
|
+
throw "failed to load record. : #{result.err_code}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(table_name, grn_query)
|
31
|
+
self.search(table_name, grn_query).each do |i|
|
32
|
+
request "delete #{table_name} --id #{i['_id']}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# select table [match_columns [query [filter [scorer [sortby [output_columns
|
37
|
+
# [offset [limit [drilldown [drilldown_sortby [drilldown_output_columns
|
38
|
+
# [drilldown_offset [drilldown_limit [output_type]]]]]]]]]]]]]]
|
39
|
+
def search(table_name, grn_query, grn_sort=[], options={})
|
40
|
+
sort_by, offset, limit = parse_grn_sort grn_sort
|
41
|
+
remote_query = (grn_query.empty?) ? "" : "--query #{grn_query}"
|
42
|
+
remote_sort_by = (sort_by.empty?) ? "" : "--sort-by #{sort_by}"
|
43
|
+
res = request "select #{table_name} #{remote_query} #{remote_sort_by} --offset #{offset} --limit #{limit}"
|
44
|
+
list = GroongaResult::List.new res
|
45
|
+
if list.success?
|
46
|
+
return list.to_a
|
47
|
+
else
|
48
|
+
throw list.err_msg
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def exist_table(table_name)
|
53
|
+
res = request "table_list"
|
54
|
+
table_list = GroongaResult::List.new res
|
55
|
+
if table_list.success?
|
56
|
+
existence = false
|
57
|
+
table_list.each do |row|
|
58
|
+
existence = true if row[:name] == table_name
|
59
|
+
end
|
60
|
+
return existence
|
61
|
+
else
|
62
|
+
throw table_list.err_msg
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def exist_column(table_name, column_name)
|
67
|
+
# groonga 1.4
|
68
|
+
# [["id","name","path","type","flags","domain"],[260,"title","test.0000104","var",49152,259]]
|
69
|
+
# groonga 1.7
|
70
|
+
# [
|
71
|
+
# [0,1269972586.4569,1.4e-05],
|
72
|
+
# [[["id", "UInt32"],["name","ShortText"],["path","ShortText"],["type","ShortText"],["flags","ShortText"],["domain", "ShortText"],["range", "ShortText"],["source","ShortText"]]]
|
73
|
+
# ]
|
74
|
+
res = request "column_list #{table_name}"
|
75
|
+
list = GroongaResult::List.new res
|
76
|
+
if list.success?
|
77
|
+
existence = false
|
78
|
+
list.each do |row|
|
79
|
+
existence = true if row[:name] == column_name
|
80
|
+
end
|
81
|
+
existence
|
82
|
+
else
|
83
|
+
throw list.err_msg
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_table(table_name, properties, key_prop=nil)
|
88
|
+
key_type = (key_prop.nil?) ? "UInt64" : trans_type(key_prop.type)
|
89
|
+
# create table
|
90
|
+
res = request "table_create #{table_name} 0 #{key_type}";
|
91
|
+
result = GroongaResult::Base.new res
|
92
|
+
throw result.err_msg unless result.err_code == 0 || result.err_code == -22
|
93
|
+
properties.each do |prop|
|
94
|
+
type = trans_type(prop.type)
|
95
|
+
propname = prop.name.to_s
|
96
|
+
query = "column_create #{table_name} #{propname} 0 #{type}"
|
97
|
+
res = GroongaResult::Base.new(request query)
|
98
|
+
err = res.err_code
|
99
|
+
|
100
|
+
unless err == 0 || err == -22
|
101
|
+
throw "Create Column Failed : #{res.inspect} : #{query}"
|
102
|
+
end
|
103
|
+
|
104
|
+
if type == "ShortText" || type == "Text" || type == "LongText"
|
105
|
+
add_term(table_name, propname)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
def err_code(res)
|
113
|
+
return if res.nil?
|
114
|
+
code = res[0][0]
|
115
|
+
code
|
116
|
+
end
|
117
|
+
|
118
|
+
def create_term_table(table_name, key_prop="ShortText", tokenizer="TokenBigram")
|
119
|
+
res = request "table_create #{table_name} TABLE_PAT_KEY|KEY_NORMALIZE #{key_prop} Void #{tokenizer}"
|
120
|
+
throw "Fale to create term table." unless err_code(res) == 0 || err_code(res) == -22
|
121
|
+
true
|
122
|
+
end
|
123
|
+
|
124
|
+
def add_term(table_name, propname)
|
125
|
+
term_table_name = 'DMGTerms'
|
126
|
+
term_column_name = "#{table_name.downcase}_#{propname.downcase}"
|
127
|
+
# check existence of term table
|
128
|
+
unless exist_table term_table_name
|
129
|
+
create_term_table term_table_name
|
130
|
+
end
|
131
|
+
# check existence of column in term table
|
132
|
+
unless exist_column(term_table_name, term_column_name)
|
133
|
+
request "column_create DMGTerms #{term_column_name} COLUMN_INDEX|WITH_POSITION #{table_name} #{propname}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def request(message)
|
138
|
+
@context.send message
|
139
|
+
self.logger.debug "Query: " + message
|
140
|
+
id, result = @context.receive
|
141
|
+
self.logger.debug "Result: " + result
|
142
|
+
if result == 'true'
|
143
|
+
true
|
144
|
+
elsif result == 'false'
|
145
|
+
false
|
146
|
+
else
|
147
|
+
JSON.parse(result)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def parse_grn_sort(grn_sort=[])
|
152
|
+
return "" if grn_sort == []
|
153
|
+
sort = grn_sort[0]
|
154
|
+
options = grn_sort[1]
|
155
|
+
sort_str = sort.map {|i|
|
156
|
+
desc = (i[:order] == :desc) ? '-' : ''
|
157
|
+
"#{desc}#{i[:key]}"
|
158
|
+
}.join(',')
|
159
|
+
[ sort_str, options[:offset], options[:limit] ]
|
160
|
+
end
|
161
|
+
|
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
|
+
end # class GroongaAdapter::RemoteIndex
|
192
|
+
end # module Adapters
|
193
|
+
end # module DataMapper
|
194
|
+
|
195
|
+
|
196
|
+
__END__
|
197
|
+
|
198
|
+
def test_send
|
199
|
+
_context = Groonga::Context.new
|
200
|
+
_context.connect(:host => @host, :port => @port)
|
201
|
+
assert_equal(0, _context.send("status"))
|
202
|
+
id, result = _context.receive
|
203
|
+
assert_equal(0, id)
|
204
|
+
status, values = JSON.load(result)
|
205
|
+
return_code, start_time, elapsed, = status
|
206
|
+
assert_equal([0, ["alloc_count", "starttime", "uptime"]],
|
207
|
+
[return_code, values.keys.sort])
|
208
|
+
end
|
209
|
+
|
210
|
+
Commands
|
211
|
+
|
212
|
+
add
|
213
|
+
column_create
|
214
|
+
column_list
|
215
|
+
define_selector
|
216
|
+
delete
|
217
|
+
get
|
218
|
+
load
|
219
|
+
log_level
|
220
|
+
log_put
|
221
|
+
log_put
|
222
|
+
quit
|
223
|
+
select
|
224
|
+
set
|
225
|
+
shutdown
|
226
|
+
status
|
227
|
+
table_create
|
228
|
+
table_list
|
229
|
+
view_add
|
230
|
+
|
231
|
+
Types
|
232
|
+
|
233
|
+
Object 任意のテーブルに属する全てのレコード [1]
|
234
|
+
Bool bool型。trueとfalse。
|
235
|
+
Int8 8bit符号付き整数。
|
236
|
+
UInt8 8bit符号なし整数。
|
237
|
+
Int16 16bit符号付き整数。
|
238
|
+
UInt16 16bit符号なし整数。
|
239
|
+
Int32 32bit符号付き整数。
|
240
|
+
UInt32 32bit符号なし整数。
|
241
|
+
Int64 64bit符号付き整数。
|
242
|
+
UInt64 64bit符号なし整数。
|
243
|
+
Float ieee754形式の64bit浮動小数点数。
|
244
|
+
Time 1970年1月1日0時0分0秒からの経過マイクロ秒数を
|
245
|
+
64bit符号付き整数で表現した値。
|
246
|
+
ShortText 4Kbyte以下の文字列。
|
247
|
+
Text 64Kbyte以下の文字列。
|
248
|
+
LongText 2Gbyte以下の文字列。
|
249
|
+
TokyoGeoPoint 日本測地系緯度経度座標。
|
250
|
+
WGS84GeoPoint 世界測地系緯度経度座標。
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
module GroongaResult
|
4
|
+
class Base
|
5
|
+
attr_accessor :err_code
|
6
|
+
attr_accessor :err_msg
|
7
|
+
attr_accessor :start_time
|
8
|
+
attr_accessor :elapsed_time
|
9
|
+
|
10
|
+
def initialize(raw_result)
|
11
|
+
@err_code, @start_time, @elased_time, @err_msg = raw_result[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
if @err_code == 0
|
16
|
+
true
|
17
|
+
else
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end # class Result::Base
|
22
|
+
|
23
|
+
class Count < Base
|
24
|
+
attr_accessor :count
|
25
|
+
def initialize(raw_result)
|
26
|
+
super raw_result
|
27
|
+
# [[0,1270199923.22467,5.2e-05],1]
|
28
|
+
@count = raw_result[1]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Status < Base
|
33
|
+
attr_accessor :alloc_count
|
34
|
+
attr_accessor :process_starttime
|
35
|
+
attr_accessor :uptime
|
36
|
+
attr_accessor :version
|
37
|
+
|
38
|
+
def initialize(raw_result)
|
39
|
+
super(raw_result)
|
40
|
+
if success?
|
41
|
+
@alloc_count = raw_result[1]['alloc_count']
|
42
|
+
@process_starttime = raw_result[1]['starttime']
|
43
|
+
@uptime = raw_result[1]['uptime']
|
44
|
+
@version = raw_result[1]['version']
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class List < Base
|
50
|
+
include Enumerable
|
51
|
+
attr_accessor :columns
|
52
|
+
attr_accessor :rows
|
53
|
+
# attr_accessor :raw_rows
|
54
|
+
# attr_accessor :raw_columns
|
55
|
+
attr_accessor :size
|
56
|
+
|
57
|
+
def initialize(raw_result)
|
58
|
+
super(raw_result)
|
59
|
+
if success?
|
60
|
+
@raw_columns, @rows, @size = if raw_result[1].size > 1
|
61
|
+
# no count
|
62
|
+
raws = raw_result[1].dup
|
63
|
+
[raws.shift,raws,nil]
|
64
|
+
else
|
65
|
+
# with count
|
66
|
+
raws = raw_result[1].dup.shift
|
67
|
+
size = raws.shift.shift
|
68
|
+
rawcols = raws.shift
|
69
|
+
[rawcols, raws, size]
|
70
|
+
end
|
71
|
+
# columns
|
72
|
+
@columns = @raw_columns.map {|item| item[0] }
|
73
|
+
parse_rows
|
74
|
+
self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def each
|
79
|
+
@mash_rows.each do |m|
|
80
|
+
yield m
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_a
|
85
|
+
return @mash_rows unless @mash_rows.nil?
|
86
|
+
[]
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_rows
|
90
|
+
@mash_rows = @rows.map {|row|
|
91
|
+
m = Mash.new
|
92
|
+
@columns.each_with_index {|item, idx|
|
93
|
+
m[@columns[idx]] = row[idx]
|
94
|
+
}
|
95
|
+
m
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end # now # module Result
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DataMapper
|
2
|
+
class Repository
|
3
|
+
# This accepts a ferret query string and an optional limit argument
|
4
|
+
# which defaults to all. This is the proper way to perform searches more
|
5
|
+
# complicated than DM's query syntax can handle (such as OR searches).
|
6
|
+
#
|
7
|
+
# See DataMapper::Adapters::GroongaAdapter#search for information on
|
8
|
+
# the return value.
|
9
|
+
def search(query, limit = :all)
|
10
|
+
adapter.search(query, limit)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# http://d.hatena.ne.jp/cesar/20070401/p1
|
2
|
+
module Unicode
|
3
|
+
def escape(str)
|
4
|
+
ary = str.unpack("U*").map!{|i| "\\u#{i.to_s(16)}"}
|
5
|
+
ary.join
|
6
|
+
end
|
7
|
+
|
8
|
+
UNESCAPE_WORKER_ARRAY = []
|
9
|
+
def unescape(str)
|
10
|
+
str.gsub(/\\u([0-9a-f]{4})/) {
|
11
|
+
UNESCAPE_WORKER_ARRAY[0] = $1.hex
|
12
|
+
UNESCAPE_WORKER_ARRAY.pack("U")
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
module_function :escape, :unescape
|
17
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'groonga'
|
2
|
+
|
3
|
+
require 'groonga_adapter/adapter'
|
4
|
+
require 'groonga_adapter/local_index'
|
5
|
+
require 'groonga_adapter/remote_index'
|
6
|
+
require 'groonga_adapter/remote_result'
|
7
|
+
require 'groonga_adapter/repository_ext'
|
8
|
+
require 'groonga_adapter/model_ext'
|
9
|
+
require 'groonga_adapter/unicode_ext'
|
data/spec/rcov.opts
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
shared_examples_for "as adapter" do
|
2
|
+
before(:each) do
|
3
|
+
@adapter = DataMapper.setup(:default, "groonga://#{index_path}")
|
4
|
+
|
5
|
+
Object.send(:remove_const, :User) if defined?(User)
|
6
|
+
class ::User
|
7
|
+
include DataMapper::Resource
|
8
|
+
|
9
|
+
property :id, Serial
|
10
|
+
property :name, String
|
11
|
+
end
|
12
|
+
|
13
|
+
Object.send(:remove_const, :Photo) if defined?(Photo)
|
14
|
+
class ::Photo
|
15
|
+
include DataMapper::Resource
|
16
|
+
|
17
|
+
property :uuid, String, :default => proc { UUIDTools::UUID.random_create }, :key => true
|
18
|
+
property :happy, Boolean, :default => true
|
19
|
+
property :description, String
|
20
|
+
end
|
21
|
+
|
22
|
+
User.auto_migrate!
|
23
|
+
Photo.auto_migrate!
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should work with a model using id' do
|
27
|
+
u = User.create(:id => 2)
|
28
|
+
repository.search(User, '').should == { User => [ 2 ] }
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should work with a model using another key than id' do
|
32
|
+
p = Photo.create
|
33
|
+
repository.search(Photo, '').should == { Photo => [p.uuid] }
|
34
|
+
p.destroy!
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should allow lookups using Model#get' do
|
38
|
+
u = User.create(:id => 2, :name => "foovarbuz")
|
39
|
+
User.get(2).should == u
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should allow delete rows using Model#destroy' do
|
43
|
+
u = User.create(:id => 2, :name => "Alice")
|
44
|
+
u2 = User.create(:id => 3, :name => "Bob")
|
45
|
+
User.get(2).should == u
|
46
|
+
bob = User.get(3)
|
47
|
+
repository.search(User,'name:Bob').should == { User => [ 3 ] } #[User].size.should == 1
|
48
|
+
bob.destroy!.should == true
|
49
|
+
repository.search(User,'name:Bob').should == {}
|
50
|
+
repository.search(User,'name:Alice').should == {User => [ 2 ]}
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
shared_examples_for 'as is_search plugin' do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
DataMapper.setup(:default, "sqlite3::memory:")
|
5
|
+
DataMapper.setup(:search, "groonga://#{index_path}")
|
6
|
+
DataMapper::Logger.new($stderr, :debug)
|
7
|
+
Object.send(:remove_const, :Image) if defined?(Image)
|
8
|
+
class ::Image
|
9
|
+
include DataMapper::Resource
|
10
|
+
property :id, Serial
|
11
|
+
property :title, String
|
12
|
+
|
13
|
+
is :searchable # this defaults to :search repository, you could also do
|
14
|
+
end
|
15
|
+
|
16
|
+
Object.send(:remove_const, :Story) if defined?(Story)
|
17
|
+
class ::Story
|
18
|
+
include DataMapper::Resource
|
19
|
+
property :id, Serial
|
20
|
+
property :title, String
|
21
|
+
property :author, String
|
22
|
+
|
23
|
+
is :searchable
|
24
|
+
end
|
25
|
+
|
26
|
+
Story.auto_migrate!
|
27
|
+
Image.auto_migrate!
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should allow search with no operator' do
|
31
|
+
|
32
|
+
pending "grn expression may have bug." # FIXME
|
33
|
+
|
34
|
+
image = Image.create(:title => "Oil Rig");
|
35
|
+
story = Story.create(:title => "Oil Rig",
|
36
|
+
:author => "John Doe");
|
37
|
+
Image.search(:title => "Oil Rig").should == [image]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should allow search with :like operator' do
|
41
|
+
image = Image.create(:title => "Oil Rig");
|
42
|
+
Image.search(:title.like => "Oil").should == [image]
|
43
|
+
image.title = "Owl Owl"
|
44
|
+
image.save
|
45
|
+
Image.search(:title.like => "Owl").should == [image]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should allow search with japanese" do
|
49
|
+
image = Image.create(:title => "お腹すいた");
|
50
|
+
Image.search(:title.like => "お腹").should == [image]
|
51
|
+
image.title = "すいてない"
|
52
|
+
image.save
|
53
|
+
Image.search(:title.like => "すいてない").should == [image]
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should allow search with all columns' do
|
57
|
+
story = Story.create(:title => "Oil Rig",
|
58
|
+
:author => "John Doe");
|
59
|
+
story2 = Story.create(:title => "Lolem ipsum",
|
60
|
+
:author => "John Doe");
|
61
|
+
# Story.fulltext_search("John").should == [story, story2] # <--- Crash on local index.
|
62
|
+
Story.fulltext_search("author:@John").should == [story, story2]
|
63
|
+
end
|
64
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'uuidtools'
|
3
|
+
require 'dm-core'
|
4
|
+
require 'dm-is-searchable'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
|
+
SPEC_ROOT = Pathname(__FILE__).dirname.expand_path
|
9
|
+
|
10
|
+
require 'groonga_adapter'
|
11
|
+
require 'spec'
|
12
|
+
require 'spec/autorun'
|
13
|
+
|
14
|
+
(Pathname.new(__FILE__).parent + "shared").children.grep(/\.rb$/).each do |example|
|
15
|
+
puts example
|
16
|
+
require example
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_driver(name, default_uri)
|
20
|
+
return false if ENV['ADAPTER'] != name.to_s
|
21
|
+
|
22
|
+
begin
|
23
|
+
DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
|
24
|
+
DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
|
25
|
+
true
|
26
|
+
rescue LoadError => e
|
27
|
+
warn "Could not load do_#{name}: #{e}"
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
ENV['ADAPTER'] ||= 'sqlite3'
|
33
|
+
|
34
|
+
HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
|
35
|
+
HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
|
36
|
+
HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
|
37
|
+
|
38
|
+
def local_groonga_path
|
39
|
+
Pathname(SPEC_ROOT) + 'test/index'
|
40
|
+
end
|
41
|
+
def remote_groonga_path
|
42
|
+
ENV["DM_GRN_URL"] || "127.0.0.1:10041" # "192.168.81.132:8888" <- 1.4.0
|
43
|
+
end
|
44
|
+
|
45
|
+
# Spec::Runner.configure do |config|
|
46
|
+
# end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "local_index adapter" do
|
4
|
+
def index_path;local_groonga_path;end
|
5
|
+
|
6
|
+
after(:all) do
|
7
|
+
Pathname.new(index_path).parent.children.each do |f|
|
8
|
+
f.delete
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
# remove indeces before running spec.
|
14
|
+
Pathname.new(index_path).parent.children.each do |f|
|
15
|
+
f.delete
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it_should_behave_like "as adapter"
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "remote_index adapter" do
|
23
|
+
def index_path;remote_groonga_path;end
|
24
|
+
it_should_behave_like "as adapter"
|
25
|
+
end
|