Skip to content

Commit

Permalink
index builder dsl
Browse files Browse the repository at this point in the history
  • Loading branch information
bibendi committed Apr 19, 2013
1 parent 3a38070 commit 8231d18
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 54 deletions.
63 changes: 30 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sphinx::Integration

Надор надстроек над ThinkingSphinx и Riddle
Набор надстроек над ThinkingSphinx и Riddle

Гем служит для использования real time индексов, а также для более хитрого написания sql запросов при описании индекса.

Expand All @@ -21,7 +21,7 @@
## Поддержка RT индексов
```ruby
define_index('model') do
set_property :rt => true
set_property :rt => true
end
```

Expand All @@ -40,91 +40,88 @@ Workflow:
+ после завершения полной индексации, очищается основной rt индекс
+ и в него перетекают данные из delta rt индекса

Если у модели существую MVA атрибуты, которые наполняются отдельными запросами (ranged-query), то необходимо определить методы,
которые будут возвращать их значения при сохранении модели. Существую определённые правила именования таких методов.
Метод должен начинаться с mva_sphinx_attributes_for_NAME, например:
```ruby
def mva_sphinx_attributes_for_rubrics
{:rubrics => rubrics.map(&:rubric_id)}
end
```

## Дополнительные возможности конфигурировани индекса

Предполагается, что весь код в примерах будет выполнятся в блоке `define_index('model') do ... end`

### Значения для MVA атрибутов при записи
Если у модели существую MVA атрибуты, которые наполняются отдельными запросами (ranged-query), то необходимо определить блоки,
которые будут возвращать их значения при сохранении модели.
```ruby
mva_attribute :rubrics do |product|
product.rubrics.map(&:rubric_id)
end
```

### Наполнение определённого индекса из другой базы, например со слэйва

Реквизиты базы из ключа {production}_slave
```ruby
set_property :use_slave_db => true
slave(true)
```

Реквизиты базы из ключа {production}_my-sphinx-slave
```ruby
set_property :use_slave_db => 'my-sphinx-slave'
slave('my-sphinx-slave')
```

### Common Table Expressions or CTEs
```ruby
set_property :source_cte => {
:_rubrics => <<-SQL,
with(:_rubrics) do
<<-SQL,
select companies.id as company_id, array_agg(company_rubrics.rubric_id) as rubrics_array
from companies
inner join company_rubrics on company_rubrics.company_id = companies.id
where {{where}}
group by companies.id
SQL
}
end
```

Условие {{where}} будет заменено на нужное из основного запроса

### Дополнительные joins, например с заданым CTE
```ruby
set_property :source_joins => {
:_rubrics => {
:as => :_rubrics,
:type => :left,
:on => '_rubrics.company_id = companies.id'
}
left_join(:_rubrics).on('_rubrics.company_id = companies.id')

left_join(:long_table_name => :alias).on('alias.company_id = companies.id')

inner_join(:long_table_name).as(:alias).on(...)
```

### Отключение группировки GROUP BY, которая делается по-умолчанию
```ruby
set_property :source_no_grouping => true
no_grouping
```

### Указание LIMIT
```ruby
set_property :sql_query_limit => 1000
limit(1000)
```

### Отключение индексации пачками
```ruby
set_property :disable_range => true
disable_range
```

### Указание своих минимального и максимального предела индексации
```ruby
set_property :sql_query_range => "SELECT 1::int, COALESCE(MAX(id), 1::int) FROM rubrics"
query_range("SELECT 1::int, COALESCE(MAX(id), 1::int) FROM rubrics")
```

### Указание набора условий для выборки из базы
### Отключение подстановок в WHERE $start >= ? and $end <= ?
```ruby
set_property :use_own_sql_query_range => true
where %[faq_posts.id in (select faq_post_id from faq_post_deltas where ("faq_post_deltas"."id" >= $start AND "faq_post_deltas"."id" <= $end))]
use_own_sql_query_range
```

### Указание полей при группировке
### Отменяет группировку по-умолчанию
```ruby
set_property :force_group_by => true
group_by 'search_hints.forms.id', 'search_hints.forms.value',
force_group_by
```

### Указание произвольного названия таблицы, например вьюшки
```ruby
set_property :source_table => 'prepared_table_view'
from('prepared_table_view')
```

### Наполнение MVA атрибутов из произволного запроса
Expand Down
2 changes: 1 addition & 1 deletion lib/sphinx/integration/extensions/thinking_sphinx/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Sphinx::Integration::Extensions::ThinkingSphinx::Index
autoload :Builder, 'sphinx/integration/extensions/thinking_sphinx/index/builder'

included do
attr_accessor :merged_with_core, :is_core_index
attr_accessor :merged_with_core, :is_core_index, :mva_sources
alias_method_chain :to_riddle, :merged
alias_method_chain :to_riddle_for_distributed, :merged
alias_method_chain :all_names, :rt
Expand Down
102 changes: 100 additions & 2 deletions lib/sphinx/integration/extensions/thinking_sphinx/index/builder.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,111 @@
# coding: utf-8
module Sphinx::Integration::Extensions::ThinkingSphinx::Index::Builder

def group_by!(*args)
source.groupings = args
autoload :Join, 'sphinx/integration/extensions/thinking_sphinx/index/builder/join'

# Отменяет группировку по-умолчанию
#
# fields - String поля, по которым будем группировать
def force_group_by(*fields)
raise ArgumentError unless fields.any?
source.groupings = fields
set_property :force_group_by => true
end
alias_method :group_by!, :force_group_by

# Задаёт лимит для выборки
#
# value - Integer
def limit(value)
set_property :sql_query_limit => value
end

# Блок, который будет вызывается при сохранении модели
#
# name - Symbol MVA attr name
# block - должен возвращать массив для MVA атрибута
# Yields Instance of ActiveRecord::Base
def mva_attribute(name, &block)
@index.mva_sources ||= {}
@index.mva_sources[name] = block
end

# Формирует CTE (WITH _alias AS (SELECT ...))
#
# name - String
def with(name)
raise ArgumentError unless block_given?
@index.local_options[:source_cte] ||= {}
@index.local_options[:source_cte][name] = yield
end

# Формирует LEFT JOIN
#
# name - Symbol or Hash
# если Symbol, то это название таблицы
# если Hash, то это {:table_name => :alias}
#
# Returns Sphinx::Integration::Extensions::ThinkingSphinx::Index::Builder::Join
def left_join(name)
Join.new(@index, name).type(:left)
end

# Формирует INNER JOIN
#
# name - Symbol or Hash
# если Symbol, то это название таблицы
# если Hash, то это {:table_name => :alias}
#
# Returns Sphinx::Integration::Extensions::ThinkingSphinx::Index::Builder::Join
def inner_join(name)
Join.new(@index, name).type(:inner)
end

# Заменяет дефолтное название таблицы
#
# value - String
def from(value)
set_property :source_table => value
end

# Наполнение индекса из другой базы
#
# value - Boolean or String
def slave(value)
set_property :use_slave_db => value
end

# Отключение группировки GROUP BY, которая делается по-умолчанию
#
# value - Boolean (default: true)
def no_grouping(value = true)
set_property :source_no_grouping => value
end

# Отключение индексации пачками
#
# value - Boolean (default: true)
def disable_range(value = true)
set_property :disable_range => value
end

# Указание своих минимального и максимального предела индексации
#
# value - String
#
# Examples
#
# query_range("SELECT 1::int, COALESCE(MAX(id), 1::int) FROM rubrics")
#
def query_range(value)
set_property :sql_query_range => value
end

# Отключение подстановок в WHERE $start >= ? and $end <= ?
#
# value - Boolean (default: true)
def use_own_sql_query_range(value = true)
set_property :use_own_sql_query_range => value
end

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# coding: utf-8
module Sphinx::Integration::Extensions::ThinkingSphinx::Index::Builder
class Join

attr_accessor :index, :key

# Создание =)
#
# index - ThinkingSphinx::Index
# name - String or Hash{:table_name => :table_alias}
def initialize(index, name)
self.index = index

if name.is_a?(Hash)
table_name, table_alias = name.first
else
table_name, table_alias = name
end

self.key = table_name

index.local_options[:source_joins] ||= {}
index.local_options[:source_joins][key] = {}
as(table_alias)

self
end

# Алиас для таблицы
#
# value - String or Symbol
#
# Returns self
def as(value)
index.local_options[:source_joins][key][:as] = value
self
end

# Тип связи
#
# value - String or Symbol
#
# Returns self
def type(value)
index.local_options[:source_joins][key][:type] = value
self
end

# По каким поля джойним
#
# value - String
#
# Returns self
def on(value)
index.local_options[:source_joins][key][:on] = value
self
end

# Вместо таблички можно указать sql подзапрос
#
# value - String
#
# Returns self
def query(value)
index.local_options[:source_joins][key][:query] = value
self
end

end
end
13 changes: 9 additions & 4 deletions lib/sphinx/integration/extensions/thinking_sphinx/source/sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,21 @@ module InstanceMethods
# позволяет использовать Common Table Expressions or CTEs
# Example
# set_property :source_cte => {
# :_contents => 'select blog_posts.id as blog_post_id, array_to_string(array_agg(blog_post_contents.content), \' \') as content from blog_posts inner join blog_post_contents on blog_post_contents.blog_post_id = blog_posts.id where {{where}} group by blog_posts.id'
# :_contents => <<-SQL
# select blog_posts.id as blog_post_id, array_to_string(array_agg(blog_post_contents.content), \' \') as content
# from blog_posts
# inner join blog_post_contents on blog_post_contents.blog_post_id = blog_posts.id
# where {{where}}
# group by blog_posts.id'
# }
def to_sql_with_cte(options = {})
sql = to_sql_without_cte(options)

if @index.local_options.key?(:source_cte)
cte_sql = []
@index.local_options[:source_cte].each do |name, value|
v = value.gsub('{{where}}', sql_where_clause(options)).gsub("\n", ' ')
cte_sql << "#{name} AS (#{v})"
as_sql = value.gsub('{{where}}', sql_where_clause(options)).gsub("\n", ' ')
cte_sql << "#{name} AS (#{as_sql})"
end

sql = 'WITH ' + cte_sql.join(', ') + ' ' + sql unless cte_sql.empty?
Expand Down Expand Up @@ -56,7 +61,7 @@ def to_sql_with_joins(*args)
if @index.local_options.key?(:source_joins)
join_sql = []
@index.local_options[:source_joins].each do |join_table, join_options|
join_table = "(#{join_options.fetch(:query, '')})" if join_table.to_s =~ /_sql$/
join_table = "(#{join_options.fetch(:query, '')})" if join_options[:query]

join_sql << "#{join_options[:type].to_s.upcase} JOIN #{join_table} AS #{join_options.fetch(:as, join_table)} ON #{join_options[:on]}"
end
Expand Down
Loading

0 comments on commit 8231d18

Please sign in to comment.