Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/interpreter/collector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require File.expand_path('../../interpreter', __FILE__)

class Interpreter
class Collector
def call(param_builder)
{
where: [Interpreter.new(ast: param_builder.filters.to_ast).to_sql],
order: param_builder.order.first.to_a.join(' '),
from: param_builder.from.first,
limit: param_builder.limit,
offset: param_builder.offset,
search_query: param_builder.search_query
}
end
end
end
1 change: 1 addition & 0 deletions examples/interpreter/to_sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def visit_root(_value, left, _right)

# @api private
def visit_statement(value, left, right)
return visit(left) if right.nil?
case value
when :and
"#{visit(left)} AND #{visit(right)}"
Expand Down
2 changes: 1 addition & 1 deletion examples/interpreter/to_sql/expression_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def visit_course_category_ids(value, _left, right)
<<~SQL
EXISTS(
SELECT TRUE FROM category_courses
WHERE category_courses.category_id #{determine_values(value, right)})
WHERE category_courses.category_id #{determine_values(value, right)}
AND courses.id = category_courses.course_id
)
SQL
Expand Down
51 changes: 51 additions & 0 deletions examples/sql_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

class SqlBuilder
attr_reader :sql_struct_parts, :model_name

def initialize(sql_struct_parts:, model_name:)
@sql_struct_parts = sql_struct_parts
@model_name = model_name
end

def preloads
ssp.preloads
end

def sql_query
<<~SQL
SELECT #{select} FROM #{from} #{ssp.joins.join(' ')}
WHERE #{ssp.where.join(' AND ')}
ORDER BY #{ssp.order}
LIMIT #{ssp.limit}
OFFSET #{ssp.offset}
SQL
end

def count_query
<<~SQL
SELECT #{count_select} FROM #{from} #{ssp.joins.join(' ')}
WHERE #{ssp.where.join(' AND ')}
SQL
end

def count_select
"COUNT(#{select})"
end

def select
return '*' if ssp.select.empty?
ssp.select
end

def from
return model_name if ssp.from.empty?
ssp.from
end

def to_s
to_sql.to_s
end

alias ssp sql_struct_parts
end
50 changes: 50 additions & 0 deletions examples/sql_struct_parts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

class SqlStructParts
PARTS = %w[select from joins where search_query order limit offset preload].freeze

attr_reader :deps

def initialize(**opts)
@deps = opts[:deps] || {}
end

def with(deps)
self.class.new(deps: initialize_dependencies(deps))
end

PARTS.each do |sql_part|
define_method sql_part do
@deps[sql_part.to_sym] || []
end
end

# @api private
def initialize_dependencies(deps)
params = {}
params = initialize_arrays(params, deps)
params = initialize_constants(params, deps)
params
end

# @api private
def initialize_arrays(params, deps)
params[:select] = select.to_a | deps[:select].to_a
params[:from] = deps[:from].to_s
params[:joins] = joins.to_a | deps[:joins].to_a
params[:where] = where | deps[:where].to_a
params
end

# @api private
def initialize_constants(params, deps)
params[:search_query] = deps[:search_query].to_s
params[:order] = deps[:order].to_s
params[:limit] = deps[:limit].to_i
params[:offset] = deps[:offset].to_i
params[:preload] = preload | deps[:preload].to_a
params
end

alias << with
end
71 changes: 71 additions & 0 deletions examples/user_pipeline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

require 'filterly'
require_relative 'interpreter/collector'
require_relative 'user_query_pipeline'

class UserPipeline
attr_reader :params, :param_builder

def initialize(params:, param_builder: ::Filterly::Pipeline::ParamBuilder.new)
@params = params
@param_builder = param_builder
end

def call
build_params

count, query = UserQueryPipeline.new(params: interpret_to_sql).call
puts '---------- SQL COUNT query -----------'
puts
puts count
puts
puts
puts '---------- SQL main query ------------'
puts query
puts
end

# @api private
def build_params
param_builder << filters
param_builder << some_other_filters
end

# @api private
def interpret_to_sql
Interpreter::Collector.new.call(param_builder)
end

# @api private
def filters
params.to_h
end

def some_other_filters
{
filters: {
course_ids: [12, 34, 54]
},
select: ['courses.new_column as new_one'],
params: { search_query: 'adam' }
}
end
end

UserPipeline.new(
params: {
filters: {
category_ids: [12, 34, 54],
course_annual_id: 23,
course_semester_id: 123
},
from: 'courses',
order: { 'courses.id': 'asc' },
params: {
limit: 10,
offset: 0,
not_supported: 'omitted'
}
}
).call
30 changes: 30 additions & 0 deletions examples/user_query_pipeline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require_relative 'sql_struct_parts'
require_relative 'sql_builder'

class UserQueryPipeline
attr_reader :sql_struct_parts

def initialize(sql_struct_parts:)
@sql_struct_parts = sql_struct_parts
end

def self.new(params:)
super(sql_struct_parts: SqlStructParts.new(deps: params))
end

def call
sql_struct_parts << something_special

sql_builder = SqlBuilder.new(sql_struct_parts: sql_struct_parts, model_name: 'users')

[sql_builder.count_query, sql_builder.sql_query]
end

def something_special
{
where: ["user.ethnicity IN('celts', 'slavic')"]
}
end
end
1 change: 1 addition & 0 deletions lib/filterly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'filterly/parser'
require 'filterly/tree'
require 'filterly/node_builder'
require 'filterly/pipeline/param_builder'

module Filterly
# Your code goes here...
Expand Down
54 changes: 54 additions & 0 deletions lib/filterly/pipeline/param_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

module Filterly
module Pipeline
class ParamBuilder
attr_reader :filters, :from, :order, :params

def initialize(filters:, from:, order:, params:)
@filters = filters
@from = from
@order = order
@params = params
end

def limit
params[:limit]
end

def offset
params[:offset]
end

def search_query
params[:search_query]
end

def self.new
super(
filters: Filterly::Tree.initialize_with_filters,
from: Set.new,
order: Set.new,
params: { limit: 10, offset: 0, search_query: nil }
)
end

def append(deps)
deps.each do |k, v|
case k
when :filters
@filters.prepend_ast(Filterly::Parser.new(v).to_ast, :and)
when :from
@from << v
when :order
@order << v
when :params
@params = @params.merge(v)
end
end
end

alias << append
end
end
end
12 changes: 6 additions & 6 deletions lib/filterly/tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,13 @@ def recreate_node(node_attr_name, new_node, stmt_type)
[
ast_node.value,
self
.class
.new(ast_node.left)
.extend_ast(node_attr_name, new_node, stmt_type),
.class
.new(ast_node.left)
.extend_ast(node_attr_name, new_node, stmt_type),
self
.class
.new(ast_node.right)
.extend_ast(node_attr_name, new_node, stmt_type)
.class
.new(ast_node.right)
.extend_ast(node_attr_name, new_node, stmt_type)
]
)
end
Expand Down
4 changes: 2 additions & 2 deletions spec/examples/interpreter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require 'spec_helper'
require 'filterly/node'
require File.join Examples.root, 'interpreter'
require 'examples/interpreter'

RSpec.describe Interpreter do
subject do
Expand Down Expand Up @@ -128,7 +128,7 @@
(course_id='23' OR (course_id='7' OR course_id='56')) AND annual='2017-2018'
AND EXISTS(
SELECT TRUE FROM category_courses
WHERE category_courses.category_id IN('67','32','34'))
WHERE category_courses.category_id IN('67','32','34')
AND courses.id = category_courses.course_id
)
SQL
Expand Down
Loading