dm-groonga-adapter 0.1.0.pre
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/.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
|