Kouhei Sutou
null+****@clear*****
Tue Mar 22 16:27:26 JST 2016
Kouhei Sutou 2016-03-22 16:27:26 +0900 (Tue, 22 Mar 2016) New Revision: 6d2c1d6a477624c29bf0b615a9fb86feb79d091f https://github.com/ranguba/groonga-client-rails/commit/6d2c1d6a477624c29bf0b615a9fb86feb79d091f Message: Add Groonga::Client::Searcher Added files: lib/groonga/client/searcher.rb lib/groonga/client/searcher/request.rb lib/groonga/client/searcher/result_set.rb lib/groonga/client/searcher/schema.rb lib/groonga/client/searcher/schema_synchronizer.rb Copied files: lib/groonga/client/searcher/source_definition.rb (from lib/groonga-client-rails.rb) Modified files: lib/groonga-client-rails.rb Modified: lib/groonga-client-rails.rb (+2 -0) =================================================================== --- lib/groonga-client-rails.rb 2016-03-22 16:25:54 +0900 (7954ec7) +++ lib/groonga-client-rails.rb 2016-03-22 16:27:26 +0900 (ef68533) @@ -16,6 +16,8 @@ require "groonga/client/rails/version" +require "groonga/client/searcher" + if defined?(Rails) require "groonga/client/railtie" end Added: lib/groonga/client/searcher.rb (+139 -0) 100644 =================================================================== --- /dev/null +++ lib/groonga/client/searcher.rb 2016-03-22 16:27:26 +0900 (af14499) @@ -0,0 +1,139 @@ +# Copyright (C) 2016 Kouhei Sutou <kou �� clear-code.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +require "groonga/client/searcher/request" +require "groonga/client/searcher/result_set" +require "groonga/client/searcher/schema" +require "groonga/client/searcher/schema_synchronizer" +require "groonga/client/searcher/source_definition" + +module Groonga + class Client + class Searcher + class << self + def schema + @schema ||= Schema.new(default_table_name) + end + + def add_source(model_class, columns:) + sources[model_class] = SourceDefinition.new(model_class, columns) + + searcher_class = self + model_class.after_create do |model| + searcher = searcher_class.new + searcher.create(model) + end + + model_class.after_update do |model| + searcher = searcher_class.new + searcher.update(model) + end + + model_class.after_destroy do |model| + searcher = searcher_class.new + searcher.destroy(model) + end + end + + def fetch_source_definition(source) + sources.fetch(source.class) + end + + def sync + sync_schema + sync_records + end + + private + def sources + @sources ||= {} + end + + def default_table_name + name.gsub(/Searcher\z/, "").tableize + end + + def sync_schema + current_schema = Client.open do |client| + client.schema + end + syncher = SchemaSynchronizer.new(schema, current_schema) + syncher.sync + end + + def sync_records + ensure_model_classes_loaded + sources.each do |model_class, definition| + all_records = model_class.all + if all_records.respond_to?(:find_each) + enumerator = all_records.find_each + else + enumerator = all_records.each + end + searcher = new + enumerator.each do |model| + searcher.upsert(model) + end + end + end + + def ensure_model_classes_loaded + ::Rails.application.eager_load! + end + end + + def inititalize + end + + def upsert(source) + definition = self.class.fetch_source_definition(source) + record = {} + definition.columns.each do |name, _| + record[name.to_s] = source.__send__(name) + end + record["_key"] = source_key(source) + Client.open do |client| + client.load(:table => self.class.schema.table, + :values => [record]) + end + end + + def create(source) + upsert(source) + end + + def update(source) + upsert(source) + end + + def destroy(source) + Client.open do |client| + client.delete(table: self.class.schema.table, + key: source_key) + end + end + + def search + TableRequest.new(self.class.schema.table) + end + + private + def source_key(source) + "#{source.class.name}-#{source.id}" + end + end + end +end Added: lib/groonga/client/searcher/request.rb (+109 -0) 100644 =================================================================== --- /dev/null +++ lib/groonga/client/searcher/request.rb 2016-03-22 16:27:26 +0900 (2b9b77e) @@ -0,0 +1,109 @@ +# Copyright (C) 2016 Kouhei Sutou <kou �� clear-code.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +module Groonga + class Client + class Searcher + class Request + def result_set + @result_set ||= Client.open do |client| + ResultSet.new(client.select(to_parameters)) + end + end + + def match_columns(value) + OverwriteRequest.new(self, + MatchColumnsRequest.new(value)) + end + + def query(value) + QueryMergeRequest.new(self, + QueryRequest.new(value)) + end + end + + class TableRequest < Request + def initialize(table) + @table = table + end + + def to_parameters + { + table: @table, + } + end + end + + # @private + class OverwriteRequest < Request + def initialize(request1, request2) + @request1 = request1 + @request2 = request2 + end + + def to_parameters + @request1.to_parameters.merge(@request2.to_parameters) + end + end + + # @private + class QueryMergeRequest < Request + def initialize(request1, request2) + @request1 = request1 + @request2 = request2 + end + + def to_parameters + params1 =****@reque*****_parameters + params2 =****@reque*****_parameters + params = params1.merge(params2) + query1 = params1[:query] + query2 = params2[:query] + if query1.present? and query2.present? + params[:query] = "(#{query1}) (#{query2})" + else + params[:query] = (query1 || query2) + end + params + end + end + + class MatchColumnsRequest < Request + def initialize(match_columns) + @match_columns = match_columns + end + + def to_parameters + { + match_columns: @match_columns, + } + end + end + + class QueryRequest < Request + def initialize(query) + @query = query + end + + def to_parameters + { + query: @query, + } + end + end + end + end +end Added: lib/groonga/client/searcher/result_set.rb (+74 -0) 100644 =================================================================== --- /dev/null +++ lib/groonga/client/searcher/result_set.rb 2016-03-22 16:27:26 +0900 (26a7ea8) @@ -0,0 +1,74 @@ +# Copyright (C) 2016 Kouhei Sutou <kou �� clear-code.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +module Groonga + class Client + class Searcher + class ResultSet + def initialize(response) + @response = response + end + + def n_hits + @response.n_hits + end + # For Kaminari + alias_method :total_count, :n_hits + + # For Kaminari + def limit_value + (@response.command[:limit] || 10).to_i + end + + # For Kaminari + def offset_value + (@response.command[:offset] || 0).to_i + end + + def records + @response.records + end + + def sources + @sources ||= fetch_sources + end + + def drilldowns + @response.drilldowns + end + + private + def fetch_sources + source_ids = {} + records.collect do |record| + model_name, id = record["_key"].split(/-/, 2) + source_ids[model_name] ||= [] + source_ids[model_name] << id + end + sources = {} + source_ids.each do |model_name, ids| + model_name.constantize.find(ids).each_with_index do |model, i| + sources["#{model_name}-#{ids[i]}"] = model + end + end + records.collect do |record| + sources[record["_key"]] + end + end + end + end + end +end Added: lib/groonga/client/searcher/schema.rb (+69 -0) 100644 =================================================================== --- /dev/null +++ lib/groonga/client/searcher/schema.rb 2016-03-22 16:27:26 +0900 (90ccbd9) @@ -0,0 +1,69 @@ +# Copyright (C) 2016 Kouhei Sutou <kou �� clear-code.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +module Groonga + class Client + class Searcher + class Schema + attr_reader :table + attr_reader :columns + def initialize(table) + @table = table + @columns = {} + end + + def table=(name) + name = name.to_s if name.is_a?(Symbol) + @table = name + end + + def column(name, options) + name = normalize_name(name) + @columns[name] = Column.new(name, options) + end + + private + def normalize_name(name) + if name.is_a?(Symbol) + name.to_s + else + name + end + end + + class Column + attr_reader :name + def initialize(name, options) + @name = name + @options = options + end + + def type + @options[:type] || "Text" + end + + def have_index? + @options[:index] + end + + def have_full_text_search_index? + have_index? and @options[:index_type] == :full_text_search + end + end + end + end + end +end Added: lib/groonga/client/searcher/schema_synchronizer.rb (+137 -0) 100644 =================================================================== --- /dev/null +++ lib/groonga/client/searcher/schema_synchronizer.rb 2016-03-22 16:27:26 +0900 (f3612bd) @@ -0,0 +1,137 @@ +# Copyright (C) 2016 Kouhei Sutou <kou �� clear-code.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +module Groonga + class Client + class Searcher + class SchemaSynchronizer + def initialize(schema, current_schema) + @schema = schema + @current_schema = current_schema + end + + def sync + sync_table + sync_columns + end + + private + def sync_table + current_table = find_current_table + return if current_table # TODO: validation + + Client.open do |client| + client.table_create(:name => @schema.table, + :flags => "TABLE_PAT_KEY", + :key_type => "ShortText") + end + end + + def sync_columns + current_columns = find_current_columns + @schema.columns.each do |_, column| + sync_column(column, current_columns[column.name]) + end + end + + def sync_column(column, current_column) + if current_column.nil? + Client.open do |client| + client.column_create(:table => @schema.table, + :name => column.name, + :type => column.type) + end + end + + sync_column_index(column, current_column) + end + + def sync_column_index(column, current_column) + if column.have_index? + lexicon_name = "lexicon_#{@schema.table}_#{column.name}" + index_column_name = "index" + if current_column + indexes = current_column.indexes + else + indexes = [] + end + indexes.each do |index| + return if index.full_name == "#{lexicon_name}.#{index_column_name}" + end + sync_lexicon(column, lexicon_name) + create_index_column(column, lexicon_name, index_column_name) + else + remove_indexes(current_column) + end + end + + def sync_lexicon(column, lexicon_name) + return if @current_schema.tables[lexicon_name] + + parameters = { + :name => lexicon_name, + :flags => "TABLE_PAT_KEY", + } + if column.have_full_text_search_index? + parameters[:key_type] = "ShortText" + parameters[:default_tokenizer] = "TokenBigram" + parameters[:normalizer] = "NormalizerAuto" + else + parameters[:key_type] = column.type + end + Client.open do |client| + client.table_create(parameters) + end + end + + def create_index_column(column, lexicon_name, index_column_name) + flags = "COLUMN_INDEX" + flags += "|WITH_POSITION" if column.have_full_text_search_index? + Client.open do |client| + client.column_create(:table => lexicon_name, + :name => index_column_name, + :flags => flags, + :type => @schema.table, + :source => column.name) + end + end + + def remove_indexes(current_column) + return if current_column.nil? + current_column.indexes.each do |index| + Client.open do |client| + clinet.column_remove(:table => index.table.name, + :name => index.name) + end + end + end + + def find_current_table + @current_schema.tables[@schema.table] + end + + def find_current_columns + current_table = find_current_table + if current_table.nil? + {} + else + current_table.columns + end + end + end + end + end +end Copied: lib/groonga/client/searcher/source_definition.rb (+13 -4) 73% =================================================================== --- lib/groonga-client-rails.rb 2016-03-22 16:25:54 +0900 (7954ec7) +++ lib/groonga/client/searcher/source_definition.rb 2016-03-22 16:27:26 +0900 (7a4e4d2) @@ -14,8 +14,17 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -require "groonga/client/rails/version" - -if defined?(Rails) - require "groonga/client/railtie" +module Groonga + class Client + class Searcher + class SourceDefinition + attr_reader :model_class + attr_reader :columns + def initialize(model_class, columns) + @model_class = model_class + @columns = columns + end + end + end + end end -------------- next part -------------- HTML����������������������������...Download