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 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.
@@ -0,0 +1,8 @@
1
+ require 'java'
2
+ begin
3
+ include_class "de.saumya.lucene.LuceneService"
4
+ rescue
5
+ require 'dm-lucene-adapter_ext'
6
+ retry
7
+ end
8
+ require 'dm_lucene_adapter/dm_lucene_adapter'
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,3 @@
1
+ module DmLuceneAdapter
2
+ VERSION = '1.0.0'
3
+ 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
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --loadby random
@@ -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
+