diff --git a/lib/polecat.rb b/lib/polecat.rb index 4f5c672..40775cc 100644 --- a/lib/polecat.rb +++ b/lib/polecat.rb @@ -3,4 +3,6 @@ class Polecat require 'polecat/index_reader' require 'polecat/index_searcher' require 'polecat/document' + require 'polecat/query' + require 'polecat/term' end diff --git a/lib/polecat/index_searcher.rb b/lib/polecat/index_searcher.rb index 7ad0f7e..b9f0edd 100644 --- a/lib/polecat/index_searcher.rb +++ b/lib/polecat/index_searcher.rb @@ -35,10 +35,50 @@ class Polecat @reader.path end + # searches through all documents + # + # Run the query against the @default_field@ of every stored document to get + # a list of all matching documents. + # @param [String] query a String which get's matched against the documents + # @return [Array] a list of all matching documents def search query @reader.read.select do |doc| - doc.attributes.fetch(@default_field).fetch(:value) == query + #doc.attributes.fetch(@default_field).fetch(:value) == query + rs = [] + query.terms.each do |term| + val = doc.attributes.fetch(term.field.to_sym).fetch(:value) + if compare val, term.operator, term.value + rs << true + end + end + if query.relation == :and + rs.count == query.terms.count + else + rs.empty? + end end end + + # compare the document value with the searched value + # + # This compares the two values with the operator + # @return [Any] trueish for matches or falsey + # @private + def compare ival, op, tval + if op == :eq + if tval.class == Regexp + ival.match tval + else + ival == tval + end + elsif op == :gt + ival < tval + elsif op == :lt + ival > tval + else + false + end + end + private :compare end end diff --git a/lib/polecat/query.rb b/lib/polecat/query.rb new file mode 100644 index 0000000..aebf3c8 --- /dev/null +++ b/lib/polecat/query.rb @@ -0,0 +1,35 @@ +class Polecat + # The Query manages a number of terms or queries which are set into a + # relation. A relation is needed to say, which documents shall be + # returned. + # In a @and@ relation only the documents, which are returned of the query + # parts get returned. For @or@ all documents found in a part get returned. + class Query + # returns the relation of the terms + # @return [Symbol] :and, :or + attr_accessor :relation + + # returns the list of all terms + attr_reader :terms + + # creates a new query object + # + # Create a new query object. As a default, the relation is set to @:and@ + # (@see Query#relation) + def initialize relation = :and + if relation == :and || relation == :or + @relation = relation + else + raise ArgumentError, 'no valid relation' + end + + @terms = [] + end + + # add a new term or query + def add term + @terms << term + self + end + end +end diff --git a/lib/polecat/term.rb b/lib/polecat/term.rb new file mode 100644 index 0000000..16b54d7 --- /dev/null +++ b/lib/polecat/term.rb @@ -0,0 +1,21 @@ +class Polecat + class Term + # the field name which should be found + attr_reader :field + # the operator to match the field with the value + attr_reader :operator + # the search value which get's matched against the document field + attr_reader :value + + # create a new Term for a query + def initialize field, operator, value + @field = field + @operator = operator + if @operator == :eq && value.class == String + @value = /^#{value}$/ + else + @value = value + end + end + end +end diff --git a/spec/index_searcher/search_spec.rb b/spec/index_searcher/search_spec.rb index c2accda..85cc0dc 100644 --- a/spec/index_searcher/search_spec.rb +++ b/spec/index_searcher/search_spec.rb @@ -5,16 +5,16 @@ describe "IndexSearcher#search" do let(:w) { Polecat::IndexWriter.new(path) } let(:s) { Polecat::IndexSearcher.new :path => path } - it "returns an empty array when nothing was found" do - s.search("foo").should == [] + it "returns an empty array when the query is empty" do + s.search(Polecat::Query.new).should == [] end context "searching on a filled index" do before do - w.add Spec::FooDocument.new(:name => 'foo') - w.add Spec::FooDocument.new(:name => 'bar') - w.add Spec::FooDocument.new(:name => 'baz') - w.add Spec::FooDocument.new(:name => 'foobar') + w.add Spec::FooDocument.new(:id => 0, :name => 'foo') + w.add Spec::FooDocument.new(:id => 1, :name => 'bar') + w.add Spec::FooDocument.new(:id => 2, :name => 'baz') + w.add Spec::FooDocument.new(:id => 3, :name => 'foobar') w.write end @@ -25,8 +25,33 @@ describe "IndexSearcher#search" do ) end + let (:q1) { Polecat::Query.new.add(Polecat::Term.new(:id, :eq, 1)) } it "returns an array of documents, when a document was found" do - s.search('foo').count.should == 1 + s.search(q1).count.should == 1 + end + + let (:q2) { Polecat::Query.new.add(Polecat::Term.new(:name, :eq, 'foo')) } + it "returns only matches for a String query" do + s.search(q2).count.should == 1 + end + + let (:q3) { Polecat::Query.new.add(Polecat::Term.new(:name, :eq, /foo/)) } + it "returns all documents when an regexp is given" do + s.search(q3).count.should == 2 + end + + let (:q4) { Polecat::Query.new.add(Polecat::Term.new(:id, :eq, 33)) } + it "returns an empty array when no document matched" do + s.search(q4).count.should == 0 + end + + let (:q5) { + Polecat::Query.new. + add(Polecat::Term.new(:id, :eq, 3)). + add(Polecat::Term.new(:name, :eq, 'foobar')) + } + it "returns a document for a query with multiple terms" do + s.search(q5).count.should == 1 end end end diff --git a/spec/query/add_spec.rb b/spec/query/add_spec.rb new file mode 100644 index 0000000..53738b1 --- /dev/null +++ b/spec/query/add_spec.rb @@ -0,0 +1,16 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Query#add" do + let (:term1) { Polecat::Term.new(:id, :eq, 23) } + let (:term2) { Polecat::Term.new(:name, :eq, 'foo') } + let (:term3) { Polecat::Term.new(:lastname, :eq, /foo/) } + let (:query) { Polecat::Query.new } + + it "returns the query object for chaining" do + query.add(term1).should be(query) + end + + it "adds the term to the list of terms" do + query.add(term1).terms.count.should be(1) + end +end diff --git a/spec/query/new_spec.rb b/spec/query/new_spec.rb new file mode 100644 index 0000000..7aaf4a6 --- /dev/null +++ b/spec/query/new_spec.rb @@ -0,0 +1,17 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Query#new" do + it "uses 'and' as an default" do + q = Polecat::Query.new + q.relation.should be(:and) + end + + it "takes a relation operator as an argument" do + q = Polecat::Query.new :or + q.relation.should be(:or) + end + + it "raises an error, when the relation is not known" do + lambda { Polecat::Query.new :foo }.should raise_error(ArgumentError) + end +end diff --git a/spec/term/new_spec.rb b/spec/term/new_spec.rb new file mode 100644 index 0000000..c846262 --- /dev/null +++ b/spec/term/new_spec.rb @@ -0,0 +1,19 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Term#new" do + it "takes a field, a operator and a value" do + t = Polecat::Term.new :id, :eq, 23 + t.field.should be(:id) + t.operator.should be(:eq) + t.value.should be(23) + end + + it "converts Strings to Regexps, if the operator is :eq" do + t = Polecat::Term.new :name, :eq, "foo" + t.value.should == /^foo$/ + end + + it "raises an error if no argument is given" do + lambda { Polecat::Term.new }.should raise_error + end +end