dm-lucene-adapter 0.1.0-java
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/lib/dm-lucene-adapter.rb +8 -0
- data/lib/dm-lucene-adapter_ext.jar +0 -0
- data/lib/dm_lucene_adapter/dm_lucene_adapter.rb +196 -0
- data/lib/dm_lucene_adapter/version.rb +3 -0
- data/spec/dm_lucene_adapter_spec.rb +148 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +24 -0
- metadata +81 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Kristian Meier
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Binary file
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
class LuceneAdapter < AbstractAdapter
|
4
|
+
|
5
|
+
private
|
6
|
+
def index_directory(model)
|
7
|
+
@path / "#{model.storage_name(name)}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def lucene(model)
|
11
|
+
LuceneService.new(java.io.File.new(index_directory(model).to_s))
|
12
|
+
end
|
13
|
+
|
14
|
+
public
|
15
|
+
|
16
|
+
def initialize(name, options = {})
|
17
|
+
super
|
18
|
+
(@path = Pathname(@options[:path]).freeze).mkpath
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Enumerable<Resource>] resources
|
22
|
+
# The list of resources (model instances) to create
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
25
|
+
# The number of records that were actually saved into the data-store
|
26
|
+
#
|
27
|
+
# @api semipublic
|
28
|
+
def create(resources)
|
29
|
+
count = 0
|
30
|
+
indexer = lucene(resources.first.model).create_indexer
|
31
|
+
resources.each do |resource|
|
32
|
+
resource.id = indexer.next_id
|
33
|
+
map = {}
|
34
|
+
resource.attributes.each { |k,v| map[k.to_s] = v.to_s}
|
35
|
+
indexer.index(map)
|
36
|
+
count += 1
|
37
|
+
end
|
38
|
+
count
|
39
|
+
ensure
|
40
|
+
indexer.close if indexer
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [Query] query
|
44
|
+
# the query to match resources in the datastore
|
45
|
+
#
|
46
|
+
# @return [Enumerable<Hash>]
|
47
|
+
# an array of hashes to become resources
|
48
|
+
#
|
49
|
+
# @api semipublic
|
50
|
+
def read(query)
|
51
|
+
is_sorting = query.order.size > 1 || query.order[0].target.name != :id || query.order[0].operator != :asc
|
52
|
+
offset, limit = is_sorting ? [0, -1] : [query.offset, query.limit || -1]
|
53
|
+
|
54
|
+
resources = do_read(offset, limit, query)
|
55
|
+
resources.each do |resource|
|
56
|
+
query.fields.each do |field|
|
57
|
+
attribute = field.name.to_s
|
58
|
+
resource[attribute] = field.typecast(resource[attribute])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if is_sorting
|
63
|
+
#records = records.uniq if unique?
|
64
|
+
resources = query.sort_records(resources)
|
65
|
+
resources = query.limit_records(resources)
|
66
|
+
end
|
67
|
+
resources
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def do_read(offset, limit, query)
|
73
|
+
reader = lucene(query.model).create_reader
|
74
|
+
if(query.conditions.nil?)
|
75
|
+
result = []
|
76
|
+
|
77
|
+
#p query
|
78
|
+
# TODO set limit, offset to default of sorting is non default
|
79
|
+
reader.read_all(offset, limit).each do |resource|
|
80
|
+
map = {}
|
81
|
+
resource.each do |k,v|
|
82
|
+
map[k] = v
|
83
|
+
end
|
84
|
+
result << map
|
85
|
+
end
|
86
|
+
result
|
87
|
+
else
|
88
|
+
ops = query.conditions.operands
|
89
|
+
if(ops.size == 1 && ops[0].class == DataMapper::Query::Conditions::EqualToComparison && ops[0].subject.name == :id)
|
90
|
+
map = {}
|
91
|
+
reader.read(query.conditions.operands[0].value).each do |k,v|
|
92
|
+
map[k] = v
|
93
|
+
end
|
94
|
+
[map]
|
95
|
+
else
|
96
|
+
op = query.conditions.slug.to_s.upcase
|
97
|
+
lquery = make_query("", ops, op)
|
98
|
+
lquery.sub!(/#{op} $/, '')
|
99
|
+
result = []
|
100
|
+
reader.read_all(offset, limit, lquery).each do |resource|
|
101
|
+
map = {}
|
102
|
+
resource.each do |k,v|
|
103
|
+
map[k] = v
|
104
|
+
end
|
105
|
+
result << map
|
106
|
+
end
|
107
|
+
result
|
108
|
+
end
|
109
|
+
end
|
110
|
+
ensure
|
111
|
+
reader.close if reader
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def make_query(lquery, ops, operator)
|
117
|
+
ops.each do |comp|
|
118
|
+
case comp.slug
|
119
|
+
when :like
|
120
|
+
lquery += "#{comp.subject.name.to_s}:#{comp.value}"
|
121
|
+
unless comp.value =~ /%|_|\?|\*/
|
122
|
+
lquery += "~"
|
123
|
+
end
|
124
|
+
lquery += " #{operator} "
|
125
|
+
when :eql
|
126
|
+
lquery += "#{comp.subject.name.to_s}:\"#{comp.value}\" #{operator} "
|
127
|
+
when :not
|
128
|
+
if lquery.size == 0
|
129
|
+
lquery = "NOT "
|
130
|
+
else
|
131
|
+
lquery.sub!(/#{operator}/, "NOT")
|
132
|
+
end
|
133
|
+
lquery = make_query(lquery, comp.operands, operator)
|
134
|
+
else
|
135
|
+
warn "ignore unsupported operand #{comp.slug}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
lquery
|
139
|
+
end
|
140
|
+
|
141
|
+
public
|
142
|
+
|
143
|
+
# @param [Hash(Property => Object)] attributes
|
144
|
+
# hash of attribute values to set, keyed by Property
|
145
|
+
# @param [Collection] collection
|
146
|
+
# collection of records to be updated
|
147
|
+
#
|
148
|
+
# @return [Integer]
|
149
|
+
# the number of records updated
|
150
|
+
#
|
151
|
+
# @api semipublic
|
152
|
+
def update(attributes, collection)
|
153
|
+
count = 0
|
154
|
+
service = lucene(collection.model)
|
155
|
+
deleter = service.create_deleter
|
156
|
+
resources = read(collection.query)
|
157
|
+
resources.each do |resource|
|
158
|
+
deleter.delete(resource["id"])
|
159
|
+
end
|
160
|
+
deleter.close
|
161
|
+
deleter = nil
|
162
|
+
indexer = service.create_indexer
|
163
|
+
attributes = attributes_as_fields(attributes)
|
164
|
+
resources.each do |resource|
|
165
|
+
resource.update(attributes)
|
166
|
+
map = {}
|
167
|
+
resource.each { |k,v| map[k.to_s] = v.to_s}
|
168
|
+
indexer.index(map)
|
169
|
+
end
|
170
|
+
count
|
171
|
+
ensure
|
172
|
+
indexer.close if indexer
|
173
|
+
deleter.close if deleter
|
174
|
+
end
|
175
|
+
|
176
|
+
# @param [Collection] collection
|
177
|
+
# collection of records to be deleted
|
178
|
+
#
|
179
|
+
# @return [Integer]
|
180
|
+
# the number of records deleted
|
181
|
+
#
|
182
|
+
# @api semipublic
|
183
|
+
def delete(collection)
|
184
|
+
count = 0
|
185
|
+
indexer = lucene(collection.model).create_deleter
|
186
|
+
collection.each do |resource|
|
187
|
+
indexer.delete(resource.id)
|
188
|
+
count += 1
|
189
|
+
end
|
190
|
+
count
|
191
|
+
ensure
|
192
|
+
indexer.close if indexer
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe DataMapper::Adapters::LuceneAdapter do
|
4
|
+
|
5
|
+
it "should create" do
|
6
|
+
Book.create(:author => "kristian", :title => "me and the corner").new?.should be_false
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should read all" do
|
10
|
+
size = Book.all.size
|
11
|
+
(1..3).each do
|
12
|
+
Book.create(:author => "kristian", :title => "me and the corner")
|
13
|
+
end
|
14
|
+
b = Book.all
|
15
|
+
b.size.should == size + 3
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should delete" do
|
19
|
+
books = []
|
20
|
+
(1..4).each do
|
21
|
+
books << Book.create(:author => "kristian", :title => "me and the corner")
|
22
|
+
end
|
23
|
+
id = books.last.id
|
24
|
+
size = Book.all.size
|
25
|
+
books.each do |b|
|
26
|
+
b.destroy if b.id % 2 == 0
|
27
|
+
end
|
28
|
+
b = Book.all
|
29
|
+
size.should == b.size + 2
|
30
|
+
book = Book.create(:author => "kristian", :title => "me and the corner")
|
31
|
+
id.should < book.id
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should read a single' do
|
35
|
+
book = Book.create(:author => "kristian", :title => "me and the corner")
|
36
|
+
b = Book.get(book.id)
|
37
|
+
b.id.should == book.id
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should update' do
|
41
|
+
book = Book.create(:author => "kristian", :title => "me and the corner")
|
42
|
+
book.author = "sanuka"
|
43
|
+
book.save
|
44
|
+
b = Book.get(book.id)
|
45
|
+
b.author.should == "sanuka"
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "search" do
|
49
|
+
|
50
|
+
before :all do
|
51
|
+
@size = Book.all(:author => "sanuka").size
|
52
|
+
@len = 5
|
53
|
+
(1..@len).each do |i|
|
54
|
+
Book.create(:author => "kristian", :title => "me and the corner #{i}")
|
55
|
+
Book.create(:author => "sanuka", :title => "me and the corner #{i}")
|
56
|
+
Book.create(:author => "#{i}sanuka", :title => "me and the corner")
|
57
|
+
Book.create(:author => "#{i}sanuka#{i}", :title => "me and the corner")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "ascending" do
|
62
|
+
|
63
|
+
after :each do
|
64
|
+
id = 0
|
65
|
+
@books.each do |b|
|
66
|
+
b.id.should > id
|
67
|
+
id = b.id
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should find 5 with simple query' do
|
72
|
+
@books = Book.all(:author => "sanuka")
|
73
|
+
@books.size.should == @size + @len
|
74
|
+
@books.each { |b| b.author.should == "sanuka" }
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should find 5 with not query' do
|
78
|
+
@books = Book.all(:author.not => "sanuka")
|
79
|
+
@books.each { |b| b.author.should_not == "sanuka" }
|
80
|
+
(@books.size + @size + @len).should == Book.all.size
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should find nothing with simple query' do
|
84
|
+
@books = Book.all(:author => "saumya")
|
85
|
+
@books.size.should == 0
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should find 5 with fuzzy query' do
|
89
|
+
@books = Book.all(:author.like => "sanaku")
|
90
|
+
@books.size.should == @size + @len
|
91
|
+
@books.each { |b| b.author.should =~ /sanuka/ }
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should find 5 with single char wildcards query' do
|
95
|
+
@books = Book.all(:author.like => "san?k?")
|
96
|
+
@books.size.should == @size + @len
|
97
|
+
@books.each { |b| b.author.should =~ /sanuka/ }
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should find 5 with wildcards query' do
|
101
|
+
@books = Book.all(:author.like => "sa*ka")
|
102
|
+
@books.size.should == @size + @len
|
103
|
+
@books.each { |b| b.author.should =~ /sanuka/ }
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should obey offset' do
|
107
|
+
@books = Book.all(:author.like => "san?k?", :limit => @size)
|
108
|
+
@books.size.should == @size
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should obey offset with empty query' do
|
112
|
+
@books = Book.all(:limit => @size)
|
113
|
+
@books.size.should == @size
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should obey offset and limit' do
|
117
|
+
@books = Book.all(:author.like => "san?k?", :offset => @size, :limit => @len)
|
118
|
+
@books.size.should == @len
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should obey offset and limit with empty query' do
|
122
|
+
@books = Book.all(:offset => @size, :limit => @len)
|
123
|
+
@books.size.should == @len
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "descending" do
|
128
|
+
|
129
|
+
after :each do
|
130
|
+
id = 12311230123321
|
131
|
+
@books.each do |b|
|
132
|
+
b.id.should < id
|
133
|
+
id = b.id
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should obey offset and limit descending order' do
|
138
|
+
@books = Book.all(:author.like => "san?k?", :offset => @size, :limit => @len, :order => [:id.desc])
|
139
|
+
@books.size.should == @len
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should obey offset and limit descending order with emtpy query' do
|
143
|
+
@books = Book.all(:offset => @len, :limit => @size, :order => [:id.desc])
|
144
|
+
@books.size.should == @size
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'dm-core', ">0.10.0"
|
4
|
+
require 'pathname'
|
5
|
+
$LOAD_PATH << Pathname(__FILE__).dirname.parent.expand_path + 'lib'
|
6
|
+
|
7
|
+
require 'dm-core'
|
8
|
+
require 'dm-lucene-adapter'
|
9
|
+
|
10
|
+
class Book
|
11
|
+
|
12
|
+
include DataMapper::Resource
|
13
|
+
|
14
|
+
property :id, Serial
|
15
|
+
|
16
|
+
property :author, String, :length => 128
|
17
|
+
|
18
|
+
property :title, String, :length => 255
|
19
|
+
|
20
|
+
property :published, Boolean, :default => true
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
DataMapper.setup(:default, :adapter => :lucene, :path => "tmp")
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-lucene-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: java
|
6
|
+
authors: []
|
7
|
+
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-28 00:00:00 +05:30
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.10.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "1.2"
|
34
|
+
version:
|
35
|
+
description: datamapper adapter for search index lucene
|
36
|
+
email:
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/dm-lucene-adapter.rb
|
45
|
+
- lib/dm-lucene-adapter_ext.jar
|
46
|
+
- lib/dm_lucene_adapter/dm_lucene_adapter.rb
|
47
|
+
- lib/dm_lucene_adapter/version.rb
|
48
|
+
- spec/dm_lucene_adapter_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
- spec/spec.opts
|
51
|
+
- MIT-LICENSE
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/mkristian/dm-lucene-adapter
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.5
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: dm adapter for lucene
|
80
|
+
test_files: []
|
81
|
+
|