Skip to content

Commit

Permalink
Merge pull request #117 from gabriel-curtino/master
Browse files Browse the repository at this point in the history
Use rowid instead id, allowing custom primary keys
  • Loading branch information
oldmoe authored Jun 22, 2024
2 parents d506c51 + 09452a6 commit c579a87
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 67 deletions.
8 changes: 4 additions & 4 deletions lib/litestack/litesearch/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,21 @@ def search(term, options = {})
generate_results(rs)
end

def similar(id, limit = 10)
def similar(rowid, limit = 10)
# pp term = @db.execute(@schema.sql_for(:similarity_query), id)
rs = if @schema.schema[:tokenizer] == :trigram
# just use the normal similarity approach for now
# need to recondisder that for trigram indexes later
@stmts[:similar].execute(id, limit) # standard:disable Style/IdenticalConditionalBranches
@stmts[:similar].execute(rowid, limit) # standard:disable Style/IdenticalConditionalBranches
else
@stmts[:similar].execute(id, limit) # standard:disable Style/IdenticalConditionalBranches
@stmts[:similar].execute(rowid, limit) # standard:disable Style/IdenticalConditionalBranches
end

generate_results(rs)
end

def clear!
@stmts[:delete_all].execute!(id)
@stmts[:delete_all].execute!(rowid)
end

def drop!
Expand Down
68 changes: 48 additions & 20 deletions lib/litestack/litesearch/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,18 @@ def search_models
end

module InstanceMethods
# rowid = id by default
def rowid
id
end

def similar(limit = 10)
conn = self.class.get_connection
idx = conn.search_index(self.class.send(:index_name))
r_a_h = conn.results_as_hash
conn.results_as_hash = true
rs = idx.similar(id, limit)
conn.results_as_hash = r_a_h
result = []
rs.each do |row|
obj = self.class.fetch_row(row["id"])
obj.search_rank = row["search_rank"]
result << obj
end
result
self.class.similar(rowid, limit)
end
end

module ClassMethods

def litesearch
# it is possible that this code is running when there is no table created yet
if !defined?(ActiveRecord::Base).nil? && ancestors.include?(ActiveRecord::Base)
Expand Down Expand Up @@ -90,6 +84,22 @@ def drop_index!
get_connection.search_index(index_name).drop!
end

def similar(rowid, limit = 10)
conn = get_connection
idx = conn.search_index(send(:index_name))
r_a_h = conn.results_as_hash
conn.results_as_hash = true
rs = idx.similar(rowid, limit)
conn.results_as_hash = r_a_h
result = []
rs.each do |row|
obj = fetch_row(row["rowid"])
obj.search_rank = row["search_rank"]
result << obj
end
result
end

def search_all(term, options = {})
options[:offset] ||= 0
options[:limit] ||= 25
Expand Down Expand Up @@ -136,6 +146,7 @@ def create_instance(row)
end

module ActiveRecordSchemaMethods

attr_accessor :model_class

def field(name, attributes = {})
Expand All @@ -161,17 +172,26 @@ def field(name, attributes = {})
def allowed_attributes
super + [:polymorphic, :as, :action_text]
end

end

module ActiveRecordInstanceMethods; end
module ActiveRecordInstanceMethods
def rowid
self.class.rowid(id)
end
end

module ActiveRecordClassMethods
def get_connection
connection.raw_connection
end

def fetch_row(id)
find(id)
def rowid(id)
where(primary_key => id).limit(1).pluck(:rowid)&.first
end

def fetch_row(rowid)
find_by("rowid = ?", rowid)
end

def search(term)
Expand All @@ -185,7 +205,7 @@ def search(term)
self.select(
"#{table_name}.*"
).joins(
"INNER JOIN #{index_name} ON #{table_name}.id = #{index_name}.rowid AND rank != 0 AND #{index_name} MATCH ", Arel.sql("'#{term}'")
"INNER JOIN #{index_name} ON #{table_name}.rowid = #{index_name}.rowid AND rank != 0 AND #{index_name} MATCH ", Arel.sql("'#{term}'")
).select(
"-#{index_name}.rank AS search_rank"
).order(
Expand All @@ -199,6 +219,10 @@ def create_instance(row)
end

module SequelInstanceMethods
def rowid
self.class.rowid(id)
end

def search_rank
@values[:search_rank]
end
Expand All @@ -209,8 +233,12 @@ def search_rank=(rank)
end

module SequelClassMethods
def fetch_row(id)
self[id]
def rowid(id)
where(primary_key => id).get(:rowid)
end

def fetch_row(rowid)
self[rowid] # where(Sequel.lit("rowid = ?", rowid)).first
end

def get_connection
Expand All @@ -221,7 +249,7 @@ def search(term)
dataset.select(
Sequel.lit("#{table_name}.*, -#{index_name}.rank AS search_rank")
).inner_join(
Sequel.lit("#{index_name}(:term) ON #{table_name}.id = #{index_name}.rowid AND rank != 0", {term: term})
Sequel.lit("#{index_name}(:term) ON #{table_name}.rowid = #{index_name}.rowid AND rank != 0", {term: term})
).order(
Sequel.lit("rank")
)
Expand Down
6 changes: 5 additions & 1 deletion lib/litestack/litesearch/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def table(table_name)
@schema[:table] = table_name
end

def primary_key(new_primary_key)
@schema[:primary_key] = new_primary_key
end

def fields(field_names)
field_names.each { |f| field f }
end
Expand Down Expand Up @@ -185,7 +189,7 @@ def clean
end

def allowed_attributes
[:weight, :col, :target, :source, :conditions, :reference]
[:weight, :col, :target, :source, :conditions, :reference, :primary_key]
end
end

Expand Down
17 changes: 9 additions & 8 deletions lib/litestack/litesearch/schema_adapters/backed_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def create_primary_triggers_sql(active = false)
DELETE FROM #{name} WHERE rowid = NEW.rowid;
END;
CREATE TRIGGER #{name}_delete AFTER DELETE ON #{table} BEGIN
DELETE FROM #{name} WHERE rowid = OLD.id;
DELETE FROM #{name} WHERE rowid = OLD.rowid;
END;
SQL
end
Expand All @@ -59,10 +59,10 @@ def drop_secondary_trigger_poly_sql(target_table, target_col, col)
"DROP TRIGGER IF EXISTS #{target_table}_#{target_col}_#{name}_update;"
end

def create_secondary_trigger_sql(target_table, target_col, col)
def create_secondary_trigger_sql(target_table, target_col, col, primary_key)
<<~SQL
CREATE TRIGGER IF NOT EXISTS #{target_table}_#{target_col}_#{col}_#{name}_update AFTER UPDATE OF #{target_col} ON #{target_table} BEGIN
#{rebuild_sql} AND #{table}.#{col} = NEW.id;
#{rebuild_sql} AND #{table}.#{col} = NEW.#{primary_key};
END;
SQL
end
Expand Down Expand Up @@ -98,7 +98,7 @@ def create_secondary_triggers_sql
@schema[:fields].each do |name, field|
if field[:trigger_sql]
if field[:col]
sql << create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
sql << create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col], field[:primary_key])
elsif field[:source]
sql << create_secondary_trigger_poly_sql(field[:target_table], field[:target_col], name, field[:conditions])
end
Expand All @@ -108,18 +108,19 @@ def create_secondary_triggers_sql
end

def rebuild_sql
"INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(", ")}) SELECT #{table}.id, #{select_cols_sql} FROM #{joins_sql} #{filter_sql}"
"INSERT OR REPLACE INTO #{name}(rowid, #{active_field_names.join(", ")}) SELECT #{table}.rowid, #{select_cols_sql} FROM #{joins_sql} #{filter_sql}"
end

def enrich_schema
@schema[:fields].each do |name, field|
if field[:target] && !field[:target].start_with?("#{table}.")
field[:target] = field[:target].downcase
target_table, target_col = field[:target].split(".")
field[:primary_key] = :id unless field[:primary_key]
field[:col] = :"#{name}_id" unless field[:col]
field[:target_table] = target_table.to_sym
field[:target_col] = target_col.to_sym
field[:sql] = "(SELECT #{field[:target_col]} FROM #{field[:target_table]} WHERE id = NEW.#{field[:col]})"
field[:sql] = "(SELECT #{field[:target_col]} FROM #{field[:target_table]} WHERE #{field[:primary_key]} = NEW.#{field[:col]})"
field[:trigger_sql] = true # create_secondary_trigger_sql(field[:target_table], field[:target_col], field[:col])
field[:target_table_alias] = "#{field[:target_table]}_#{name}"
elsif field[:source]
Expand Down Expand Up @@ -167,9 +168,9 @@ def joins_sql
join_table = +""
join_table << "#{field[:target_table]} AS #{field[:target_table_alias]} ON "
if field[:col]
join_table << "#{field[:target_table_alias]}.id = #{@schema[:table]}.#{field[:col]}" if field[:col]
join_table << "#{field[:target_table_alias]}.#{field[:primary_key]} = #{@schema[:table]}.#{field[:col]}" if field[:col]
elsif field[:source]
join_table << "#{field[:target_table_alias]}.#{field[:reference]} = #{@schema[:table]}.id"
join_table << "#{field[:target_table_alias]}.#{field[:reference]} = #{@schema[:table]}.rowid"
if field[:conditions]
join_table << " AND "
join_table << field[:conditions].collect { |k, v| "#{field[:target_table_alias]}.#{k} = '#{v}'" }.join(" AND ")
Expand Down
18 changes: 11 additions & 7 deletions lib/litestack/litesearch/schema_adapters/basic_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def table
@schema[:table]
end

def primary_key
@schema[:primary_key] || :id
end

def fields
@schema[:fields]
end
Expand Down Expand Up @@ -78,8 +82,8 @@ def sql_for(method, *args)
def generate_sql
@sql[:create_index] = :create_index_sql
@sql[:create_vocab_tables] = :create_vocab_tables_sql
@sql[:insert] = "INSERT OR REPLACE INTO #{name}(rowid, #{active_col_names_sql}) VALUES (:id, #{active_col_names_var_sql}) RETURNING rowid"
@sql[:delete] = "DELETE FROM #{name} WHERE rowid = :id"
@sql[:insert] = "INSERT OR REPLACE INTO #{name}(rowid, #{active_col_names_sql}) VALUES (:rowid, #{active_col_names_var_sql}) RETURNING rowid"
@sql[:delete] = "DELETE FROM #{name} WHERE rowid = :rowid"
@sql[:count] = "SELECT count(*) FROM #{name}(:term)"
@sql[:count_all] = "SELECT count(*) FROM #{name}"
@sql[:delete_all] = "DELETE FROM #{name}"
Expand All @@ -89,11 +93,11 @@ def generate_sql
@sql[:ranks] = :ranks_sql
@sql[:set_config_value] = "INSERT OR REPLACE INTO #{name}_config(k, v) VALUES (:key, :value)"
@sql[:get_config_value] = "SELECT v FROM #{name}_config WHERE k = :key"
@sql[:search] = "SELECT rowid AS id, -rank AS search_rank FROM #{name}(:term) WHERE rank !=0 ORDER BY rank LIMIT :limit OFFSET :offset"
@sql[:similarity_terms] = "SELECT DISTINCT term FROM #{name}_instance WHERE doc = :id AND FLOOR(term) IS NULL AND LENGTH(term) > 2 AND NOT instr(term, ' ') AND NOT instr(term, '-') AND NOT instr(term, ':') AND NOT instr(term, '#') AND NOT instr(term, '_') LIMIT 15"
@sql[:similarity_query] = "SELECT group_concat('\"' || term || '\"', ' OR ') FROM #{name}_row WHERE term IN (#{@sql[:similarity_terms]})"
@sql[:similarity_search] = "SELECT rowid AS id, -rank AS search_rank FROM #{name}(:term) WHERE rowid != :id ORDER BY rank LIMIT :limit"
@sql[:similar] = "SELECT rowid AS id, -rank AS search_rank FROM #{name} WHERE #{name} = (#{@sql[:similarity_query]}) AND rowid != :id ORDER BY rank LIMIT :limit"
@sql[:search] = "SELECT rowid, -rank AS search_rank FROM #{name}(:term) WHERE rank !=0 ORDER BY rank LIMIT :limit OFFSET :offset"
@sql[:similarity_terms] = "SELECT DISTINCT term FROM #{name}_instance WHERE doc = :rowid AND FLOOR(term) IS NULL AND LENGTH(term) > 2 AND NOT instr(term, ' ') AND NOT instr(term, '-') AND NOT instr(term, ':') AND NOT instr(term, '#') AND NOT instr(term, '_') LIMIT 15"
@sql[:similarity_query] = "SELECT group_concat(term, ' OR ') FROM #{name}_row WHERE term IN (#{@sql[:similarity_terms]})"
@sql[:similarity_search] = "SELECT rowid, -rank AS search_rank FROM #{name}(:term) WHERE rowid != :rowid ORDER BY rank LIMIT :limit"
@sql[:similar] = "SELECT rowid, -rank AS search_rank FROM #{name} WHERE #{name} = (#{@sql[:similarity_query]}) AND rowid != :rowid ORDER BY rank LIMIT :limit"
@sql[:update_index] = "UPDATE sqlite_schema SET sql = :sql WHERE name = '#{name}'"
@sql[:update_content_table] = "UPDATE sqlite_schema SET sql = :sql WHERE name = '#{name}_content'"
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ def generate_sql
@sql[:adjust_temp_content] = "UPDATE sqlite_schema SET sql (SELECT sql FROM sqlite_schema WHERE name = '#{name}_content') WHERE name = #{name}_content_temp"
@sql[:restore_content] = "ALTER TABLE #{name}_content_temp RENAME TO #{name}_content"
@sql[:rebuild] = "INSERT INTO #{name}(#{name}) VALUES ('rebuild')"
@sql[:similar] = "SELECT rowid AS id, *, -rank AS search_rank FROM #{name} WHERE #{name} = (#{@sql[:similarity_query]}) AND rowid != :id ORDER BY rank LIMIT :limit"
@sql[:similar] = "SELECT rowid, *, -rank AS search_rank FROM #{name} WHERE #{name} = (#{@sql[:similarity_query]}) AND rowid != :rowid ORDER BY rank LIMIT :limit"
@sql[:drop_content_table] = "DROP TABLE #{name}_content"
@sql[:drop_content_col] = :drop_content_col_sql
@sql[:create_content_table] = :create_content_table_sql
@sql[:search] = "SELECT rowid AS id, *, -rank AS search_rank FROM #{name}(:term) WHERE rank !=0 ORDER BY rank LIMIT :limit OFFSET :offset"
@sql[:search] = "SELECT rowid, *, -rank AS search_rank FROM #{name}(:term) WHERE rank !=0 ORDER BY rank LIMIT :limit OFFSET :offset"
end

private
Expand All @@ -26,6 +26,6 @@ def drop_content_col_sql(col_index)
def create_content_table_sql(count)
cols = []
count.times { |i| cols << "c#{i}" }
"CREATE TABLE #{name}_content(id INTEGER PRIMARY KEY, #{cols.join(", ")})"
"CREATE TABLE #{name}_content(rowid INTEGER PRIMARY KEY, #{cols.join(", ")})"
end
end
Loading

0 comments on commit c579a87

Please sign in to comment.